Result Utils
A lightweight Flutter package that implements the Result pattern for type-safe error handling using Dart's sealed classes. Instead of throwing exceptions, functions can return a FutureResult<T> that is either a Success with a value or an Error with a message.
Based on Flutter's recommended Result pattern.
Features
- π― Type-safe error handling β Compile-time guarantees with sealed classes
- π Simple API β Easy-to-use
SuccessandErrortypes - π Future integration β Helper function to wrap async operations
- π± Flutter optimized β Lightweight with minimal dependencies
- β Well-tested β Comprehensive test coverage
Why the Result Pattern?
The Result pattern is a type-safe alternative to exceptions for handling errors. Instead of throwing exceptions (which can be unhandled), functions return a FutureResult<T> that explicitly indicates success or failure.
Benefits
- Explicit error handling β Errors are part of the function's contract, not hidden
- No try-catch boilerplate β Cleaner code without nested try-catch blocks
- Compile-time safety β The compiler ensures you handle all cases (Success/Error)
- Better control flow β Easier to compose and chain operations
For a detailed explanation, see Flutter's guide on improving control flow with the Result pattern.
Getting started
Add this to your pubspec.yaml:
dependencies:
result_utils: ^0.0.1
Then run:
flutter pub get
Usage
Creating Results
Return a FutureResult<T> that is either a Success or an Error:
import 'package:result_utils/result_utils.dart';
FutureResult<int> divide(int a, int b) {
if (b == 0) {
return FutureResult.error('Division by zero');
}
return FutureResult.success(a ~/ b);
}
// Usage
final result = divide(10, 2);
if (result.hasError) {
print('Error: ${result.error}');
} else {
print('Result: ${result.value}');
}
Wrapping Async Operations
Use futureToResult to automatically handle exceptions in futures:
Future<String> fetchData() async {
final response = await http.get(Uri.parse('https://api.example.com/data'));
if (response.statusCode == 200) {
return response.body;
} else {
throw Exception('Failed to load data');
}
}
// Convert the future to a result
final result = await futureToResult(fetchData());
if (result.hasError) {
print('Error: ${result.error}');
} else {
print('Data: ${result.value}');
}
Checking Results
final result = FutureResult<String>.success('Hello');
// Check for errors
if (result.hasError) {
// Handle error
print(result.error);
} else {
// Access the value
print(result.value); // 'Hello'
}
API Reference
FutureResult
A sealed class representing either a success or error state.
Factories
FutureResult.success(T value)β Create a successful resultFutureResult.error(String error)β Create an error result
Getters
hasErrorβboolβ Returnstrueif this is an errorerrorβStringβ Gets the error message (only use whenhasErroristrue)valueβT?β Gets the value (only available on success)
Success
A subclass of FutureResult<T> representing a successful result with a value.
Error
A subclass of FutureResult<T> representing an error with a message.
futureToResult
Converts a Future<T> into a Future<FutureResult<T>>, automatically catching exceptions.
Future<FutureResult<T>> futureToResult<T>(Future<T> future)
Best Practices
- β
Return
FutureResultfrom functions that can fail - β
Use
hasErrorto check the result before accessingvalue - β Prefer meaningful error messages over generic exceptions
- β
Use
futureToResultfor all async operations to avoidtry-catchboilerplate
See Also
- neverthrow_dart β A more feature-rich Result pattern implementation with functional methods like
.map(),.flatMap(), and.fold(). Chooseresult_utilsif you prefer a lightweight, straightforward API; chooseneverthrow_dartif you need advanced functional composition features.
Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
License
This project is licensed under the MIT License - see the LICENSE file for details.