resultX
A Future Aware Result class to grcefully handle success and error and their Futures without the need to await on each step. It focuses on functional usage and method chaining to handle the results.
Explore the docs »
Report Bug
·
Request Feature
resultx
A dart Future aware, functional (FP) result package to handle success and error inspired by kotlin's Result class and Either class.
Features
- A
Resultclass that can be either aSuccessor anError. - Focuses on functional programing and handeling of Results instead of imperative programing.
- A
FutureawareResultclass that can be either aSuccessor anError.Feature awaremeans you can keep on chaining callbacks and mapping methodswithoutthe need toawaiton each callback.- e.g.
asyncResult.mapSuccess((data) => data.length).mapSuccess((len) => len > 5) - More example can be found below
- Fully typesafe - All the types are typed and checked at compile time.
- Supports
nullableandgetOrThrowmethods or convert todartrecord(data, error)using.flat()method.
Installation
Add this to your package's pubspec.yaml file:
dependencies:
results: ^1.0.5 # add the latest version
Usage
Creating a Result
A Result<S, E> can be either a Success or an Error. A Success is a result that contains a value of type S and an error of type E. An Error is a result that contains an error of type E.
Example:
Result<User, String> getUser([bool success = true]) {
if (!success) {
return Error('Error getting user'); // or Result.error('Error getting user');
}
final user = User('Dart User', 30);
return Success(user); // or Result.success(user);
}
or With Future:
// typedef FtrResult<S, E> = Future<Result<S, E>>;
FtrResult<User, String> getUser([bool success = true]) async {
await Future.delayed(Duration(milliseconds: 10));
if (!success) {
return Error('Error getting user');
}
return Success(User('Dart User', 30));
}
Resolve a result or get a value
When trying to get the value of a Result, we must tell the system how to handle the error. .resolve() is one of the ways to do that.
Result<int, String> getStatusCode([bool success = true]) {
if (!success) {
return Error('Error getting Status');
}
return Success(1);
}
// we must tell the system how to handle the error. Resolve is one of the ways
final statusCode = await getStatusCode().resolve(onError: (_) => -1).data;
print('Status code: $statusCode');
.resovle()returns aSuccessclass and hence you can directly call.dataon it.
Use when to better handle the result
.when() is a way to handle the result. It takes two functions as parameters, one for success and one for error. You can either return a value or do something else.
import 'package:resultx/resultx.dart';
await getUser().when(
success: (user) => print('showWhenDemo :: Success Occured: $user'),
error: (e) => print('showWhenDemo :: Error Occured: $e'),
);
// you can also return a value from when clause if needed
final res = await getUser(false).when(
success: (user) => 'showWhenDemo :: Success Occured: $user',
error: (e) => 'showWhenDemo :: Error Occured: $e',
);
print(res);
Use getOrThrow to throw an error
.getOrThrow() is a way to get the value of a Success class. If the result is an Error class, it will throw an error.
import 'package:resultx/resultx.dart';
try {
final user = await getUser().getOrThrow();
print('Success Occured: $user');
} catch (e) {
print('Error Occured: $e');
}
Use the flat() method to get the data and error as a dart record
You can use this to get the data and error as a dart record.
import 'package:resultx/resultx.dart';
final (user, error) = await getUser().flat();
print('User: $user, Error: $error');
Use the .execute() method when you dont care about the result
Use .execute() to when you dont care about the result.
import 'package:resultx/resultx.dart';
// dont care if it succeeds or fails
await updateAnalytics().execute();
Use the .nullable() method to make it nullable
Sometimes you want to make a Result nullable even if the original result was not nullable. You can use .nullable() to make it nullable first and handle other mappings on it.
import 'package:resultx/resultx.dart';
// ❌ this will result in compilation error as the getUser()
// returns a Result<User, String> and not a Result<User?, String>
final user = await getUser().resolve(onError: (_) => null).data;
// ✅ this will work as the .nullable converts the type to Result<User?, String>
final user = await getUser().nullable().resolve(onError: (_) => null).data;
print('User Unchanged: $user');
Chaining callbacks with the onSuccess and onError methods
You can chain callbacks with the onSuccess and onError methods. No need to await on each callback.
import 'package:resultx/resultx.dart';
// chain as many callbacks as you want
// no need to call await on each callback
// results handle the futures internally
final user = await getUser()
.onSuccess((user) => print('showCallBackDemo :: Success Occured: $user'))
.onError((e) => print('showCallBackDemo :: Error Occured: $e'))
.onSuccess(
(user) => print('showCallBackDemo 2ndCallBack :: User : $user'),
)
.nullable()
.resolve(onError: (_) => null)
.data;
print('User : $user');
// Note how we used a single `await` on the `getUser()`
Mapping methods to to map the data and error of a Result
you can use the methods .mapSuccess and .mapError to map the data and error of a Result.
import 'package:resultx/resultx.dart';
// no need of async or await. Simply map the value to something else
FtrResult<int, int> getStatusCode([bool success = true]) {
// getUser() returns a Result<User, String>
// we can map the data to int and error to int
return getUser(success).mapSuccess((_) => 1).mapError((_) => -1);
}
Mapping methods to to map the data and error of a Result to another Result
import 'package:resultx/resultx.dart';
FtrResult<bool, String> isUserActive(User user) async {
await Future.delayed(Duration(milliseconds: 10));
return Success(user.name.length > 5); // dummy
}
final user = await getUser()
.mapOnSuccess(
(user) => isUserActive(user).mapSuccess(
(isActive) => UserWithActiveStatus(user.name, user.age, isActive),
),
)
.nullable()
.resolve(onError: (_) => null)
.data;
// Note how we used a single `await` on the `getUser()`
Future aware Result class
Notice above that for every scenerio, we did not have to resolve a Future first to use Results methods. This is because Results was built with Future awareness in mind. You can use any methods available on Result class also on Future<Result<S, E>> class. without the need to await on each callback.
❌ Bad Example:
import 'package:resultx/resultx.dart';
// first await on Future
final userResult = await getUser();
// execute other methods
final userActiveResult = await userResult.mapOnSuccess(
(user) => isUserActive(user).mapSuccess(
(isActive) => UserWithActiveStatus(user.name, user.age, isActive),
),
)
final userWithActiveNullable = await userActiveResult.nullable();
final userWithActive = await userWithActiveNullable.resolve(onError: (_) => null);
print('User : $userWithActive');
✅ Good Example:
import 'package:resultx/resultx.dart';
final user = await getUser()
.mapOnSuccess(
(user) => isUserActive(user).mapSuccess(
(isActive) => UserWithActiveStatus(user.name, user.age, isActive),
),
)
.nullable()
.resolve(onError: (_) => null)
.data;
Happy Coding 😁
Libraries
- resultx
- Support for doing something awesome.