fluent_result

pub package codecov likes style: lint Dart

fluent_result is a lightweight Dart library developed to solve a common problem. It returns an object indicating success or failure of an operation instead of throwing/using exceptions.

  • Store multiple errors in one Result object
  • Store powerful and elaborative Error object instead of only error messages in string format
  • Designing Errors in an object-oriented way

Usage

Creating a Result

Create a result which indicates success

Result result = Result.success();
Result sameResult = Result.ok;
Result sameResult2 = success();

Create a result which indicates failure

Result errorResult1 = Result.failWith('a fail reason');
Result errorResult2 = Result.failWith(ResultError('my error message'));
Result errorResult3 = Result.failWith(MyException('exception description'));
Result errorResult4 = Result.failWith(['a fail reason', ResultError('my error message')]);

Result same = fail(MyException('exception description'));

Generic ResultOf<T>

Success result with value:

ResultOf<MyObject> result = ResultOf.success(MyObject());
ResultOf<MyObject> sameResult = successWith(MyObject());
MyObject value = result.value;

Fail result with error and without value:

ResultOf<MyObject> result = ResultOf.failWith<MyObject>(ResultError('a fail reason'));
MyObject value = result.value; // is null because of the fail result

failIf() and okIf()

With the methods failIf() and okIf() you can also write in a more readable way:

final result1 = Result.failIf(() => firstName.isEmpty, "First Name is empty");
final result2 = Result.okIf(() => firstName.isNotEmpty, 'First name should not be empty');

Try

Sync

final res = Result.trySync(() {
  throw 'Some exception';
});

res.isFail.should.beTrue();
res.errorMessage.should.be('Some exception');

Async

final res = await Result.tryAsync(() async {
  await Future.delayed(const Duration(seconds: 2));
  print('Done');
});

res.isSuccess.should.beTrue();

Fold

Result res = fail('error reason');
res.fold(
  onFail: (errors) {
    // process errors
  },
  onSuccess: () {
    // process success path
  },
);
ResultOf<String> resultWithData = successWith('someData');
resultWithData.foldWithValue(
  onFail: (errors) {
    // process errors
  },
  onSuccess: (data) {
    // process success path with data
  },
);

Converting Result to another

To convert one success result to another success result has to be provided a valueConverter function.

final anotherResult =
    result.map((customer) => User(customer.id));

To convert one fail result to another fail result

final anotherResult = failResult.map<Customer>();

Custom errors

To make your codebase more robust. Create your own error collection of the App by extending ResultError.


class InvalidPasswordError extends ResultError {
  const InvalidPasswordError(String message)
      : super(message);
}

class CustomerNotFound extends ResultError {
  const CustomerNotFound({
    required this.customerId,
  }) : super('Customer not found with ID $customerId');

  final int customerId;

  @override
  String toString() => message;
}

Collect errors

For example, easy to work with errors which comes from HTTP API.

final err1 = CustomerNotFound(customerId: 1);
final res = Result.fail(err1);

final err2 = InvalidPasswordError('The password 123456 is invalid');
res.add(err2);

res.contains<InvalidPasswordError>(); // true
res.get<InvalidPasswordError>().should.not.beNull();

Exception handler matchers

ResultConfig.exceptionHandlerMatchers = {
  DioError: (e) {
    print('🟠 DIO FAIL RESULT: $e');
    final failure = ResultOf.failWith(DioErrorResult(e as DioError));
    return failure;
  },
};

Contributing

We accept the following contributions:

  • Improving documentation
  • Reporting issues
  • Fixing bugs

Maintainers

Libraries

fluent_result