Result vs Either (fpdart)
Either comes from functional programming and is widespread in the Flutter community's "clean architecture" templates. Its most-used Dart implementation is the fpdart package.
The convention: `Left` is failure, `Right` is success. So Either<Failure, List<Order>> means "either a Failure or a list of orders".
Structurally Either and Result are the same idea: unite two cases in one type and force the caller to consider both. The differences are in the details:
Resultis Flutter's official design pattern, lives in your own file (10-15 lines), needs no dependency, and is opened with Dart's ownswitch.Eitherbrings a package, and in exchange a rich composition API:map,mapLeft,flatMap,getOrElse,match,tryCatch. fpdart also brings related types such asOption,TaskandTaskEither— the last meaning "an asynchronous operation that may fail".
The choice is not about technical superiority but about your team and your project.
// ══════ RESULT (rəsmi pattern) ══════
abstract interface class OrderRepository {
Future<Result<List<Order>>> fetchMine();
}
class OrderRepositoryRemote implements OrderRepository {
@override
Future<Result<List<Order>>> fetchMine() async {
try {
final dtos = await _api.getOrders();
return Result.ok(dtos.map((d) => d.toDomain()).toList());
} on SocketException {
return const Result.error(NetworkFailure());
}
}
}
// Notifier-də:
state = switch (await _repository.fetchMine()) {
Ok(:final value) => OrdersState.loaded(value),
Error(:final error) => OrdersState.failed(error),
};
// ══════ EITHER (fpdart) ══════
import 'package:fpdart/fpdart.dart';
abstract interface class OrderRepository {
// Left = Failure, Right = uğur. Sıra konvensiyadır.
Future<Either<Failure, List<Order>>> fetchMine();
}
class OrderRepositoryRemote implements OrderRepository {
@override
Future<Either<Failure, List<Order>>> fetchMine() async {
try {
final dtos = await _api.getOrders();
// `Either.of` Right (uğur) yaradır.
return Either.of(dtos.map((d) => d.toDomain()).toList());
} on SocketException {
return Either.left(const NetworkFailure());
}
}
}
// Notifier-də, variant 1 — fpdart-ın `match`-i:
state = (await _repository.fetchMine()).match(
(failure) => OrdersState.failed(failure),
(orders) => OrdersState.loaded(orders),
);
// Notifier-də, variant 2 — Dart-ın öz pattern-ləri:
state = switch (await _repository.fetchMine()) {
Left(value: final failure) => OrdersState.failed(failure),
Right(value: final orders) => OrdersState.loaded(orders),
};
// ══════ EITHER-in əsl gücü: kompozisiya ══════
// Ardıcıl əməliyyatlar `flatMap` ilə zəncirlənir; ilk uğursuzluqda
// zəncir dayanır və `Left` dəyəri sona qədər daşınır.
Future<Either<Failure, Receipt>> checkout(Cart cart) async {
final reserved = await _inventory.reserve(cart);
return reserved.flatMap((reservation) {
// Bu blok yalnız `reserved` Right olduqda işləyir.
return _pricing
.quote(reservation)
.map((quote) => Receipt(reservation: reservation, quote: quote));
});
}
// Müqayisə üçün: `mapLeft` xətanı çevirir, dəyərə toxunmur.
final Either<String, List<Order>> forLogging =
(await _repository.fetchMine()).mapLeft((f) => f.runtimeType.toString());The same repository in both approaches. Note that fpdart's `Either` can also be opened with Dart's patterns.
| Criterion | `Result` (official) | `Either` (fpdart) |
|---|---|---|
| Dependency | None — 15 lines in your own file | The `fpdart` package |
| Official position | A design pattern in the Flutter docs | Not present in the official docs |
| Readability for a new team member | `Ok` / `Error` — immediately obvious | `Left` / `Right` — you have to know the convention |
| Composition (chaining) | By hand: `switch` plus early `return` | `flatMap`, `map`, `mapLeft` — its strength |
| Error type | `Exception` (in the official implementation) | Any type: `Either<Failure, T>` |
| Additional types | None | `Option`, `Task`, `TaskEither` and more |
| Learning curve | Practically none | Familiarity with FP concepts (functors, monads) helps |
Recommendation. For this branch's stack (Riverpod plus freezed), Result fits better, and the reasons are concrete:
1. It is the official pattern: documented in the Flutter docs, so the team debate stays short.
2. No dependency: a 15-line file. No package version, no breaking changes, no pub outdated question.
3. Little overlap with `AsyncValue`: Riverpod's AsyncValue already models loading/error/data, so Result is only needed for writes. Pulling in a package for that looks expensive.
4. `switch` is enough: Dart 3's patterns work on Left/Right too, so the core of fpdart's API (match, fold) is already in the language.
There are cases where Either is the right call:
- The team is comfortable with FP and
flatMapchains are everyday language. - Multi-step flows with sequential failures (validate → compute → write) — there
flatMapis far shorter than a stack ofswitches. - fpdart is already in the project and types like
Optionare in use.
The worst choice is running both: one repository returns a Result, another an Either. Then every new file raises "which one here?" and conversion helpers start appearing.
A practical note on migration: moving from Either to Result (or the other way) is mechanical and should be done once. The simplest start is to count the boundaries: grep -rn "Either<" lib | wc -l. Under 20 and it is a day's work; at 200 it has to be gradual (repository by repository), with a converter function (Either<Failure,T> → Result<T>) kept for the transition.
Practice. Write the same repository method twice: once with Result, once with Either (adding fpdart to dev_dependencies just to try it is fine). Then write the notifier-side usage for both and decide for yourself.
Done means: both variants compile, and ARCHITECTURE.md carries a one-line decision: "this project uses X for error flow, because …".
📚 Sources and documentation
- The fpdart packageofficialpub.dev
`Either`, `Option`, `Task`, `TaskEither` and their API — `Either.of`, `Either.left`, `map`, `mapLeft`, `flatMap`, `match`.
- Error handling with Result objectsofficialdocs.flutter.dev
The official alternative — and why it needs no dependency.
- Dart: patternsofficialdart.dev
Object patterns like `Left(value: final f)` — they work with fpdart too.