A Flutter Result type that feels like a Freezed union.

Overview

Freezed Result

A Result<Success, Failure> that feels like a Freezed union. It represents the output of an action that can succeed or fail. It holds either a value of type Success or an error of type Failure.

Failure can be any type, and it usually represents a higher abstraction than just Error or Exception. It's very common to use a Freezed Union for Failure (e.g. AuthFailure) with cases for the different kinds of errors that can occur (e.g. AuthFailure.network, AuthFailure.storage, AuthFailure.validation).

Because of this, we've made Result act a bit like a Freezed union (it has when(success:, failure:)). The base class was generated from Freezed, then we removed the parts that don't apply (maybe*) and adapted the others (map*) to feel more like a Result. We'll get into the details down below.

Usage

There are 3 main ways to interact with a Result: process it, create it, and transform it.

Processing Values and Errors

Process the values by handling both success and failure cases using when. This is preferred since you explicitly handle both cases.

final result = fetchPerson(12);
result.when(
  success: (person) => state = MyState.personFound(person);
  failure: (error) => state = MyState.error(error);
);

Or create a common type from both cases, also using when.

final result = fetchPerson(12);
final description = result.when(
  success: (person) => 'Found Person ${person.id}';
  failure: (error) => 'Problem finding a person.';
);

Or ignore the error and do something with maybeValue, which returns null on failures.

final person = result.maybeValue;
if (person != null) {}

Or ignore both the value and the error by simply using the outcome.

if (result.isSuccess) {}
// elsewhere
if (result.isFailure) {}

Or throw failure cases and return success cases using valueOrThrow.

try {
  final person = result.valueOrThrow();
} on ApiFailure catch(e) {
  // handle ApiFailure
}

Creating Results

Create the result with named constructors Result.success and Result.failure.

Result.success(person)
Result.failure(AuthFailure.network())

Declare both the Success and Failure types with typed variables or function return types.

Result<Person, AuthFailure> result = Result.success(person);
Result<Person, AuthFailure> result = Result.failure(AuthFailure.network());
Result<Person, FormatException> parsePerson(String json) {
  return Result.failure(FormatException());
}

Results are really useful as return values for async operations.

Future<Result<Person, ApiFailure>> fetchPerson(int id) async {
  try {
    final person = await api.getPerson(12);
    return Result.success(person);
  } on TimeoutException {
    return Result.failure(ApiFailure.timeout());
  } on FormatException {
    return Result.failure(ApiFailure.invalidData());
  }
}

Sometimes you have a function which may have errors, but returns void when successful. Variables can't be void, so use Nothing instead. The singleton instance is nothing.

Result<Nothing, DatabaseError> vacuumDatabase() {
  try {
    db.vacuum();
    return Result.success(nothing);
  } on DatabaseError catch(e) {
    return Result.failure(e);
  }
}

You can use catching to create a success result from the return value of a closure. Unlike the constructors, you'll need to await the return value of this call.

Without an explicit type parameters, any Object thrown by the closure is caught and returned in a failure result.

final Result<String, Object> apiResult = await Result.catching(() => getSomeString());

With type parameters, only that specific type will be caught. The rest will pass through uncaught.

final result = await Result.catching<String, FormatException>(
  () => formatTheThing(),
);

Transforming Results

Process and transform this Result into another Result as needed.

map

Change the type and value when the Result is a success. Leave the error untouched when it's a failure. Most useful for transformations of success data in a pipeline with steps that will never fail.

Result<DateTime, ApiFailure> bigDay = fetchPerson(12).map((person) => person.birthday);

mapError

Change the error when the Result is a failure. Leave the value untouched when it's a success. Most useful for transforming low-level exceptions into more abstact failure classes which classify the exceptions.

Result<Person, ApiError> apiPerson(int id) {
  final Result<Person, DioError> raw = await dioGetApiPerson(12);
  return raw.mapError((error) => _interpretDioError(error));
}

mapWhen

Change both the error and the value in one step. Rarely used.

Result<Person, DioError> fetchPerson(int id) {
  // ...
}
Result<String, ApiFailure> fullName = fetchPerson(12).mapWhen(
    success: (person) => _sanitize(person.firstName, person,lastName),
    failure: (error) => _interpretDioError(error),
);

mapToResult

Use this to turn a success into either another success or to a compatible failure. Most useful when processing the success value with another operation which may itself fail.

final Result<Person, FormatError> personResult = parsePerson(jsonString);
final Result<DateTime, FormatError> bigDay = personResult.mapToResult(
  (person) => parse(person.birthDateString),
);

Parsing the Person may succeed, but parsing the DateTime may fail. In that case, an initial success is transformed into a failure. Aliased to flatMap as well for newcomers from Swift.

mapErrorToResult

Use this to turn an error into either a success or another error. Most useful for recovering from errors which have a workaround.

Here, mapErrorToResult is used to ignore errors which can be resolved by a cache lookup. An initial failure is transformed into a success whenever the required value is available in the local cache. The _getPersonCache function also translates both unrecoverable original DioErrors, and any internal errors accessing the cache, into the more generic FetchError.

final Result<Person, DioError> raw = await dioGetApiPerson(id);
final Result<Person, FetchError> output = raw.mapErrorToResult((error) => _getPersonCache(id, error));

Result<Person, FetchError> _getPersonCache(int id, DioError error) {
  // ...
}

Aliased to flatMapError for Swift newcomers.

mapToResultWhen

Rarely used. This allows a single action to both try another operation on a success value which may fail in a new way with a new error type, and to recover from any original error with a success or translate the error into the new type of Failure.

Result<Person, DioError> fetchPerson(int id) {
  // ...
}
Result<String, ProcessingError> fullName = fetchPerson(12).mapToResultWhen(
    success: (person) => _fullName(person.firstName, person,lastName),
    failure: (dioError) => _asProcessingError(dioError),
);

Aliased to flatMapWhen, though Swift doesn't have this equivalent.

Alternatives

  • Result matches most of Swift's Result type.
  • result_type which fully matches Swift, and some Rust.
  • fluent_result allows multiple errors in a failure, and allows custom errors by extending a ResultError class.
  • Dartz is a functional programming package whose Either type can be used as a substitute for Result. It has no concept of success and failure. Instead it uses left and right. It uses the functional name fold to accomplish what we do with when.
You might also like...

Christianlyrics - Flutter plugin that allows you build lyrics srt type of song

Christianlyrics - Flutter plugin that allows you build lyrics srt type of song

christian_lyrics Flutter plugin that allows you build lyrics srt type of song. G

Dec 5, 2022

Multi type list view - A flutter customer ListView that displays multiple widget types.

Multi type list view - A flutter customer ListView that displays multiple widget types.

MultiTypeListView A light weight flutter customer ListView that displays multiple widget types. Screenshot home chat Getting Started dependencies: m

Jun 28, 2022

Animated Type Ahead SearchBar With Flutter

Animated Type Ahead SearchBar With Flutter

Animated Type Ahead SearchBar This package merges animated searchbar and flutter typeahead to give a animated type ahead search bar. Features TODO: Li

Jun 3, 2022

Type - Yet another typing test made in Flutter

Type - Yet another typing test made in Flutter

another typing test Yet another typing test made in Flutter, because why not. Tr

Jul 9, 2022

FlutterQRcode - Flutter QR Code Scanner app for a specific type of QR using GetX State Management Architecture

FlutterQRcode - Flutter QR Code Scanner app for a specific type of QR using GetX State Management Architecture

qrcode A new Flutter QR Scanner Project for Data Entry using GetX state manageme

Dec 11, 2022

This project has the vision to assist the officials for Forest trees census and tagging each tree with proper location (latitude and longitude), tree type, and other arguments. and further had the plan to apply data analysis over-collected data.

🌳 Trees 🌳 πŸ”– Tagger πŸ”– App & Analysis Software The vision of this project is to assist forest officials for tree census by tagging each tree with pr

Sep 29, 2022

This is my way to build a Tagged Search Field that display a list with suggestions while the user type.

This is my way to build a Tagged Search Field that display a list with suggestions while the user type.

tagged_search_field This is my way to build a Tagged Search Field that display a list with suggestions while the user type. A regular search field at

Nov 5, 2021

Netflix type clone app to learn animation and basic UI components..

Netflix type clone app to learn animation and basic UI components..

netflix_clone A new Flutter application to learn animation and basic ui components.. Assets credit to dribbble artist Getting Started This project is

Dec 9, 2022

POS plugging to connect with each type of pos device.

flutter_pos_printer A new flutter plugin project. Getting Started This project is a starting point for a Flutter plug-in package, a specialized packag

Aug 25, 2022
Flutter Cryptocurrency App with Riverpod & Freezed + Dio for API REST

Flutter Crypto APP Complete Flutter Application with Riverpod & Freezed + Dio for API REST. Features API REST (CryptoWatch) Linear Graph View (Hour, D

Salvador Valverde 312 Jan 2, 2023
Complete Flutter Application with Riverpod & Freezed + Dio for API REST

Flutter Crypto APP Complete Flutter Application with Riverpod & Freezed + Dio fo

Xtenso 32 Dec 26, 2022
A rewrite of Bloc tutorial: Flutter Weather Tutorial using freezed

A rewrite of Bloc tutorial: Flutter Weather Tutorial using freezed. Bloc was used instead of Cubit (Cubit vs Bloc)

Yu-Han Luo 17 Nov 23, 2022
Return a result ErrorOr with either a value T or an error Object.

ErrorOr Return a result ErrorOr with either a value T or an error Object. Features Always return a value ErrorOr from an async function. Let the calle

Erlend 6 Nov 6, 2022
🎯 This library automatically generates object classes from JSON files that can be parsed by the freezed library.

The Most Powerful Way to Automatically Generate Model Objects from JSON Files ⚑ 1. Guide ?? 1.1. Features ?? 1.1.1. From 1.1.2. To 1.2. Getting Starte

KATO, Shinya / εŠ θ—€ 真也 14 Nov 9, 2022
will cover the GetX Named Route, GetX Route Transition, GetX Route Result, GetX Route Argument, GetX Route Parameter etc.

getx_playground 1-navigation #2-reactiv A new Fl 3-SimpleStateManagement 4-GetXControllerExample 5- DependencyExample 6-TranslationExample 7-ThemeExam

null 4 Nov 11, 2022
Ruqe brings the convenient types and methods found in Rust into Dart, such as the Result, Option, pattern-matching, etc.

ruqe Ruqe brings the convenient types and methods found in Rust into Dart, such as the Result, Option, pattern-matching, etc. Additionally, the librar

Alexander Nitiola 12 Dec 28, 2022
Flutter form fields designed to take much of the burden of form-related coding off the programmer's back β€” masks, validations, keyboard type, etc.

well_formed Contents Overview Getting Started Demo application References Overview Well-Formed Widget Fields - Well-Formed - is a collection of Flutte

Dartoos 7 Nov 2, 2022
Whatsapp UI clone made with Flutter; it is compatible with any type of Android and iOS devices.

whats_app Whatsapp UI built with Flutter; it is compatible and responsive with any type of Android and iOS devices. Getting Started This project is a

Mustafa Nabavi 4 Sep 23, 2021
A TypeAhead (autocomplete) widget for Flutter, where you can show suggestions to users as they type

Starlight Type Ahead FLUTTER | ANDROID, IOS, LINUX, MACOS, WEB, WINDOWS A TypeAhead (autocomplete) widget for Flutter, where you can show suggestions

Ye Myo Aung 4 Dec 15, 2021