Dart Result Handling Library
A simple and powerful library for handling results (successes and failures) in Dart, inspired by Either
from functional programming libraries like dartz
. This library uses Success
and Failure
classes to represent outcomes.
Features
- Explicit Success/Failure: Clearly distinguish between successful operations and those that resulted in an error.
- Type Safety: Leverages Dart's type system to enforce correct handling of both successful and failed results.
- Functional Operations: Provides methods like
map
,mapFailure
,flatMap
,bind
andwhen
for chaining and transforming results in a concise and readable way. - Error Handling: Provides methods
getOrElse
andgetOrThrow
for gracefully handle errors - Convenience Methods: Type checking with
isSuccess
/isFailure
getters and safe value access withgetOrNull
/getErrorOrNull
- Side Effects: Execute side-effect operations with
tap
andtapError
methods - Error Recovery: Recover from failures using
orElse
method - Filtering: Filter success values with
filterOrElse
method - Collection Operations: Work with multiple results using
sequence
static method - Factory Methods: Create results safely with
tryCatch
,tryCatchAsync
, andfromNullable
- Combining Results: Combine multiple results with
zipWith
method - Unit Type: Use
Unit
type for operations that don't return meaningful values - Simple API: Easy to learn and integrate into your projects.
Usage
Here's how to use the library:
import 'package:result_handler/result_handler.dart';
// Using the new tryCatchAsync factory method
Future<Result<String, int>> fetchData() async {
return Result.tryCatchAsync(
() async {
await Future.delayed(const Duration(seconds: 1)); // Simulate work
if (DateTime.now().second % 2 == 0) {
return 42; // Success case
} else {
throw Exception("Data fetch failed"); // This will be caught
}
},
(error, stackTrace) => "Network error: ${error.toString()}",
);
}
// Example of a validation function
Result<String, int> validateAge(int? age) {
return Result.fromNullable(age, () => "Age cannot be null")
.filterOrElse(
(value) => value >= 0 && value <= 150,
(value) => "Invalid age: $value",
);
}
void main() async {
final result = await fetchData();
// Basic pattern matching
result.when(
success: (value) => print('Success: $value'),
failure: (error) => print('Error: $error'),
);
// Using convenience getters
if (result.isSuccess) {
print('Operation succeeded with value: ${result.getOrNull()}');
}
// Chain operations with tap for side effects
final processedResult = result
.tap((value) => print('Processing value: $value')) // Side effect
.map((value) => value * 2)
.tapError((error) => print('Logging error: $error')); // Error side effect
// Error recovery
final recoveredResult = result.orElse((error) => Success(0));
print('Recovered value: ${recoveredResult.getOrElse((_) => -1)}');
// Working with multiple results
final results = [Success<String, int>(1), Success<String, int>(2), Success<String, int>(3)];
final sequenceResult = Result.sequence(results);
sequenceResult.when(
success: (values) => print('All values: $values'),
failure: (error) => print('One failed: $error'),
);
// Combining results
final result1 = Success<String, int>(10);
final result2 = Success<String, int>(20);
final combined = result1.zipWith(result2, (a, b) => a + b);
print('Combined: ${combined.getOrElse((_) => 0)}');
// Validation example
final ageValidation = validateAge(25);
ageValidation.when(
success: (age) => print('Valid age: $age'),
failure: (error) => print('Validation error: $error'),
);
// Using Unit for operations without meaningful return values
final saveResult = Result.tryCatch<String, Unit>(
() {
// Simulate a save operation
print('Saving data...');
return const Unit();
},
(error, stackTrace) => 'Save failed: ${error.toString()}',
);
saveResult.when(
success: (_) => print('Save completed successfully'),
failure: (error) => print('Save failed: $error'),
);
}
API
Result<E, T>
(Abstract Class)
Represents the result of an operation, which can be either a Success<E, T>
or a Failure<E, T>
.
Core Methods
bind<R>(Result<E, R> Function(T value) transform)
: Binds a function to theResult
.flatMap<R>(Result<E, R> Function(T value) transform)
: Similar to map but the transformation returns another Result.map<R>(R Function(T value) transform)
: Transforms the value inside aSuccess
, otherwise does nothing.mapFailure<R>(R Function(E error) transform)
: Transforms the error inside aFailure
, otherwise does nothing.when<R>({required R Function(T data) success, required R Function(E error) failure})
: Executes thesuccess
callback if it's aSuccess
orfailure
if it's aFailure
.
Value Access Methods
getOrElse(T Function(E error) orElse)
: Returns the value if it's aSuccess
, otherwise returns the result of the orElse callback.getOrThrow()
: Returns the value if it's aSuccess
, otherwise throws the error.getOrNull()
: Returns the value if it's aSuccess
, otherwise returnsnull
.getErrorOrNull()
: Returns the error if it's aFailure
, otherwise returnsnull
.
Type Checking
isSuccess
: Returnstrue
if this is aSuccess
.isFailure
: Returnstrue
if this is aFailure
.
Side Effects
tap(void Function(T value) fn)
: Executes a function with the success value for side effects.tapError(void Function(E error) fn)
: Executes a function with the error value for side effects.
Error Recovery
orElse(Result<E, T> Function(E error) recoveryFn)
: Attempts to recover from a failure.
Filtering
filterOrElse(bool Function(T value) predicate, E Function(T value) errorIfFalse)
: Filters success values based on a predicate.
Combining Results
zipWith<U, R>(Result<E, U> other, R Function(T a, U b) combiner)
: Combines this result with another result.
Static Factory Methods
Result.fromNullable<E, T>(T? value, E Function() errorIfNull)
: Creates a Result from a nullable value.Result.tryCatch<E, T>(T Function() fn, E Function(Object error, StackTrace stackTrace) onError)
: Safely executes a function and wraps the result.Result.tryCatchAsync<E, T>(Future<T> Function() fn, E Function(Object error, StackTrace stackTrace) onError)
: Safely executes an async function and wraps the result.Result.sequence<E, T>(Iterable<Result<E, T>> results)
: Transforms multiple results into a single result containing a list.
Success<E, T>
(Class)
Represents a successful result, holding a value of type T
.
data
: The successful value of typeT
.- Implements all
Result
methods with success-specific behavior. - Supports equality comparison and proper
toString()
representation.
Failure<E, T>
(Class)
Represents a failed result, holding an error of type E
.
error
: The error value of typeE
.- Implements all
Result
methods with failure-specific behavior. - Supports equality comparison and proper
toString()
representation.
Unit
(Class)
Represents a unit type for operations that complete successfully but don't return meaningful values.
- Useful for
Result<E, Unit>
when you only care about success/failure, not the return value. - All
Unit
instances are considered equal. - Commonly used for operations like save, delete, or update that don't return data.
License
This library is licensed under the MIT License. See the LICENSE file for more details.
Changelog
See the CHANGELOG.md file for a detailed history of changes.
Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
Libraries
- result_handler
- A library for handling results of operations.