either dart ยท 
The library for error handling and railway oriented programming.
This library supports async "map" and async "then" hiding the boilerplate of working with asynchronous computations Future[Either].
Installation
Add to pubspec.yml:
dependencies:
either_dart: ... // latest package version
Documentation
https://pub.dev/documentation/either_dart/latest/either/either-library.html
Pub dev
https://pub.dev/packages/either_dart
How to use it?
Sections:
Basic usage
Create two entities for example, you can use your own abstractions for your project.
enum AppError {
NotFound,
// some errors codes
}
class MyError {
final AppError key;
final String? message;
const MyError({
required this.key,
this.message,
});
}
We can use Either as shown below:
Either<MyError, String> getCityNameByCode(int code) {
const cities = <int, String>{
/// some cities
};
if (cities.contains(code)) {
return Right(cities[code]!);
} else {
return Left(
key: AppError.NotFound,
message: '[getCityNameByCode] can`t convert code:$code to city name',
);
}
}
Too, you can use Either.cond and Either.condLazy for simple cases:
return Either.condLazy(cities.contains(code),
() => cities[code]!,
() => MyError(
key: AppError.NotFound,
message: '[getCityNameByCode] can`t convert code:$code to city name',
),
);
Either has the following methods:
note:
L - current Left type
TL - new generic Left type
R - current Right type
TR - new generic Right type
| name | result | description |
|---|---|---|
isLeft |
bool |
Represents the left side of Either class which by convention is a "Failure". |
isRight |
bool |
Represents the right side of Either class which by convention is a "Success" |
left |
L |
Get Left value, may throw an exception when the value is Right. read-only |
right |
R |
Get Right value, may throw an exception when the value is Left. read-only |
either<TL, TR>(TL fnL(L left), TR fnR(R right)) |
Either<TL, TR> |
Transform values of Left and Right, equal of bimap in fp-libraries |
fold<T>(T fnL(L left), T fnR(R right)) |
T |
Fold Left and Right into the value of one type |
map<TR>(TR fnR(R right)) |
Either<L, TR> |
Transform value of Right |
mapLeft<TL>(TL fnL(L left)) |
Either<TL, R> |
Transform value of Left |
mapAsync<TR>(FutureOr<TR> fnR(R right)) |
Future<Either<L, TR>> |
Transform value of Right |
mapLeftAsync<TL>(FutureOr<TL> fnL(L left)) |
Future<Either<TL, R>> |
Transform value of Left |
swap() |
Either<R, L> |
Swap Left and Right |
then<TR>(Either<L, TR> fnR(R right)) |
Either<L, TR> |
Transform value of Right when transformation may be finished with an error |
thenLeft<TL>(Either<TL, R> fnL(L left)) |
Either<TL, R> |
Transform value of Left when transformation may be finished with an Right |
thenAsync<TR>(FutureOr<Either<L, TR>> fnR(R right)) |
Future<Either<L, TR>> |
Transform value of Right when transformation may be finished with an error |
thenLeftAsync<TL>(FutureOr<Either<TL, R>> fnL(L left)) |
Future<Either<TL, R>> |
Transform value of Left when transformation may be finished with an Right |
Advanced usage
This library provides an FutureEither extension which is designed to handle asynchronous computation with ease.
You don't need to import or use new classes to use it - just use Future<Either<L, R>>
| name | result | description |
|---|---|---|
either<TL, TR>(TL fnL(L left), TR fnR(R right)) |
Future<Either<TL, TR>> |
Transform values of Left and Right |
fold<T>(T fnL(L left), T fnR(R right)) |
Future<T> |
Fold Left and Right into the value of one type |
mapRight<TR>(FutureOr<TR> fnR(R right)) |
Future<Either<L, TR>> |
Transform value of Right |
mapLeft<TL>(FutureOr<TL> fnL(L left)) |
Future<Either<TL, R>> |
Transform value of Left |
swap() |
Future<Either<R, L>> |
Swap Left and Right |
thenRight<TR>(FutureOr<Either<L, TR>> fnR(R right)) |
Future<Either<L, TR>> |
Async transform value of Right when transformation may be finished with an error |
thenLeft<TL>(FutureOr<Either<TL, R>> fnL(L left)) |
Future<Either<TL, R>> |
Async transform value of Left when transformation may be finished with an Right |
Example:
/// --- helpers ---
import 'package:either_dart/either.dart';
import 'package:http/http.dart' as http;
import 'package:flutter/foundation.dart';
import 'dart:convert';
Future<Either<AppError, http.Response>> safe(Future<http.Response> request) async {
try {
return Right(await request);
} catch (e) {
return Left(MyError(
key: AppError.BadRequest,
message: "Request executing with errors:$e"));
}
}
Either<AppError, http.Response> checkHttpStatus(http.Response response) {
if (response.statusCode == 200)
return Right(response);
if (response.statusCode >= 500)
return Left(MyError(
key: AppError.ServerError,
message: "Server error with http status ${response.statusCode}"));
return Left(MyError(
key: AppError.BadResponse,
message: "Bad http status ${response.statusCode}"));
}
Future<Either<AppError, dynamic>> parseJson(http.Response response) async {
try {
return Right(await compute((body) {
final json = JsonCodec();
return json.decode(body));
}, response.body);
} catch (e) {
return Left(MyError(
key: AppError.JsonParsing,
message: 'failed on json parsing'));
}
}
/// --- app code ---
//// network
Future<Either<AppError, Data>> getDataFromServer() {
return
safe(http.get(Uri('some uri')))
.thenRight(checkHttpStatus)
.thenRight(parseJson)
.mapRight(Data.fromJson)
}
Case - Solution
- How I can use the value of
Either?
You can use right or left getters, but you should check what value is stored inside (isLeft or isRight)
Also, my favorite methods fold, either
fold- used when you need to transform two rails to one typeeither- used for two situations: 1. when you need transform left and right. 2. when you need to use stored value without next usage (see example).
Example:
/// either method
showNotification(Either<MyError, String> value) {
return value.either(
(left) => showWarning(left.message ?? left.key.toString()),
(right) => showInfo(right.toString()),
);
/// equal
if (value.isLeft) {
final left = value.left;
showWarning(left.message ?? left.key.toString()
} else {
showInfo(value.right.toString())
}
}
/// fold method
class MyWidget {
final Either<MyError, List<String>> value;
const MyWidget(this.value);
Widget build(BuildContext context) {
return Text(
value.fold(
(left) => left.message,
(right) => right.join(', ')),
);
/// or
return value.fold(
(left) => _buildSomeErrorWidget(context, left),
(right) => _buildSomeRightWidget(context, right),
);
}
}