zeba_academy_result
A lightweight, type-safe Result type for Dart and Flutter applications.
zeba_academy_result provides a simple way to represent operations that can either succeed with a value or fail with an error, without relying on exceptions for normal application flow.
Features
- ✅ Type-safe success and failure results
- ✅ Generic success value type
- ✅ Generic failure error type
- ✅
Success<T, E> - ✅
Failure<T, E> - ✅
map()successful values - ✅
mapError()failure values - ✅
fold()pattern-style handling - ✅
when()convenient result handling - ✅ Safe
valueOrNullaccess - ✅ Safe
errorOrNullaccess - ✅
getOrThrow()support - ✅
getOrElse()fallback values - ✅
getOrElseGet()dynamic fallbacks - ✅ Value-based equality
- ✅ Useful
toString()output - ✅ No runtime dependencies
- ✅ Pure Dart compatible
- ✅ Flutter compatible
- ✅ Small and easy to integrate
Why use Result?
Many application operations have two possible outcomes:
Success → Value
Failure → Error
For example:
Result<User, LoginError>
can represent:
Success(User)
or:
Failure(LoginError.network)
Instead of throwing exceptions for expected failures, you can explicitly model the result.
This makes APIs easier to understand and handle.
Installation
Add zeba_academy_result to your pubspec.yaml:
dependencies:
zeba_academy_result: ^0.0.1
Then run:
flutter pub get
For Dart-only projects:
dart pub get
Import
import 'package:zeba_academy_result/zeba_academy_result.dart';
Basic Usage
Success
const Result<int, String> result = Success(100);
Failure
const Result<int, String> result = Failure(
'Something went wrong',
);
The same Result type can therefore represent both outcomes.
Success
Create a successful result:
const result = Success<int, String>(42);
Access the value:
print(result.value);
Output:
42
Check its state:
print(result.isSuccess); // true
print(result.isFailure); // false
Failure
Create a failed result:
const result = Failure<int, String>(
'Network error',
);
Access the error:
print(result.error);
Output:
Network error
Check its state:
print(result.isSuccess); // false
print(result.isFailure); // true
Safe Value Access
Use valueOrNull when you don't want an exception:
const Result<int, String> result = Failure(
'Something went wrong',
);
final value = result.valueOrNull;
print(value); // null
For a successful result:
const Result<int, String> result = Success(42);
print(result.valueOrNull); // 42
Safe Error Access
Use errorOrNull:
const Result<int, String> result = Failure(
'Network error',
);
print(result.errorOrNull);
Output:
Network error
For success:
const Result<int, String> result = Success(42);
print(result.errorOrNull); // null
fold()
fold() lets you handle both possible outcomes in one place:
const Result<int, String> result = Success(42);
final message = result.fold(
onSuccess: (value) => 'Value: $value',
onFailure: (error) => 'Error: $error',
);
print(message);
Output:
Value: 42
For a failure:
const Result<int, String> result = Failure(
'Network error',
);
final message = result.fold(
onSuccess: (value) => 'Value: $value',
onFailure: (error) => 'Error: $error',
);
print(message);
Output:
Error: Network error
when()
when() provides pattern-style result handling:
final result = getUser();
result.when(
success: (user) {
print('User: $user');
},
failure: (error) {
print('Error: $error');
},
);
This is useful when building application and repository layers.
map()
Use map() to transform a successful value.
const Result<int, String> result = Success(10);
final mapped = result.map(
(value) => value * 2,
);
print(mapped);
Output:
Success(20)
Failures are automatically preserved:
const Result<int, String> result = Failure(
'Network error',
);
final mapped = result.map(
(value) => value * 2,
);
print(mapped);
Output:
Failure(Network error)
mapError()
Use mapError() to transform an error.
const Result<int, String> result = Failure('404');
final mapped = result.mapError(
(error) => int.parse(error),
);
print(mapped);
Output:
Failure(404)
Successful values are preserved when using mapError().
getOrElse()
Return a fallback value when the result is a failure:
const Result<int, String> result = Failure(
'Something went wrong',
);
final value = result.getOrElse(0);
print(value);
Output:
0
For success:
const Result<int, String> result = Success(42);
final value = result.getOrElse(0);
print(value);
Output:
42
getOrElseGet()
Use getOrElseGet() when the fallback depends on the error:
const Result<int, String> result = Failure(
'Network error',
);
final value = result.getOrElseGet(
(error) => error.length,
);
print(value);
The failure error is passed directly to the fallback function.
getOrThrow()
If you explicitly need the successful value:
const Result<int, String> result = Success(42);
final value = result.getOrThrow();
print(value);
Output:
42
Calling getOrThrow() on a failure throws a StateError:
const Result<int, String> result = Failure(
'Network error',
);
result.getOrThrow();
Use this method only when a failure is genuinely unexpected.
Typed Errors
One of the biggest advantages of Result<T, E> is that errors don't have to be strings.
Define an error type:
enum LoginError {
invalidCredentials,
network,
server,
unknown,
}
Then return:
Result<User, LoginError>
For example:
Result<User, LoginError> login() {
return const Failure(
LoginError.invalidCredentials,
);
}
Handle it safely:
final result = login();
result.when(
success: (user) {
print('Welcome ${user.name}');
},
failure: (error) {
switch (error) {
case LoginError.invalidCredentials:
print('Invalid credentials');
break;
case LoginError.network:
print('Network error');
break;
case LoginError.server:
print('Server error');
break;
case LoginError.unknown:
print('Unknown error');
break;
}
},
);
Repository Example
A repository method can explicitly describe its possible outcome:
Future<Result<User, String>> getUser() async {
try {
final user = await fetchUser();
return Success(user);
} catch (error) {
return Failure(error.toString());
}
}
Then:
final result = await repository.getUser();
result.when(
success: (user) {
print('Loaded user: $user');
},
failure: (error) {
print('Failed to load user: $error');
},
);
Flutter Example
Result works well with Flutter state management and UI layers.
final result = await repository.getUser();
result.when(
success: (user) {
setState(() {
currentUser = user;
});
},
failure: (error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(error),
),
);
},
);
Chaining Operations
map() can be chained:
const Result<int, String> result = Success(10);
final finalResult = result
.map((value) => value * 2)
.map((value) => value + 5)
.map((value) => 'Final value: $value');
print(finalResult);
Output:
Success(Final value: 25)
Failures continue through the chain without executing the success transformation.
Equality
Success and Failure support value equality.
const first = Success<int, String>(42);
const second = Success<int, String>(42);
print(first == second); // true
Failures also support equality:
const first = Failure<int, String>('Error');
const second = Failure<int, String>('Error');
print(first == second); // true
API Overview
Result
| API | Description |
|---|---|
isSuccess |
Whether the result is successful |
isFailure |
Whether the result is a failure |
valueOrNull |
Safely retrieves the success value |
errorOrNull |
Safely retrieves the error |
map() |
Transforms the success value |
mapError() |
Transforms the failure |
fold() |
Handles success and failure |
when() |
Pattern-style result handling |
getOrThrow() |
Retrieves the value or throws |
getOrElse() |
Retrieves value or fallback |
getOrElseGet() |
Retrieves value or computed fallback |
Success
Success<T, E>(value)
Failure
Failure<T, E>(error)
Design Goals
zeba_academy_result is intentionally small.
The package focuses on:
- Explicit success/failure modeling
- Type safety
- Simple APIs
- No unnecessary dependencies
- Pure Dart compatibility
- Flutter compatibility
- Easy testing
- Predictable behavior
The package does not attempt to replace complete functional-programming libraries.
Requirements
- Dart SDK
^3.12.0 - Flutter compatible
- No runtime dependencies
Development
Clone the repository and install dependencies:
git clone https://github.com/zeba-academy/zeba_academy_result.git
cd zeba_academy_result
flutter pub get
Format the project:
dart format lib test
Run static analysis:
flutter analyze
Run tests:
flutter test
Validate package publishing:
flutter pub publish --dry-run
Project Structure
zeba_academy_result/
├── lib/
│ ├── src/
│ │ ├── result.dart
│ │ ├── success.dart
│ │ └── failure.dart
│ └── zeba_academy_result.dart
│
├── test/
│ └── zeba_academy_result_test.dart
│
├── CHANGELOG.md
├── LICENSE
├── README.md
└── pubspec.yaml
License
This project is licensed under the GNU General Public License v3.0.
See the LICENSE file for the complete license text.
About Me
✨ I’m Sufyan bin Uzayr, an open-source developer passionate about building and sharing meaningful projects.
You can learn more about me and my work at:
Zeba Academy
🚀 Zeba Academy is a learning platform dedicated to coding, technology, and development.
Explore courses, tutorials, real-world projects, and hands-on learning resources.
➡️ Main website: zeba.academy
➡️ Courses and resources: code.zeba.academy
➡️ YouTube: Zeba Academy
➡️ Instagram: @zeba.academy
Contributing
Contributions are welcome.
If you find a bug, have an improvement, or want to suggest a feature:
- Open an issue.
- Describe the problem or proposed improvement.
- Fork the repository.
- Create a feature branch.
- Add or update tests.
- Run
dart format lib test. - Run
flutter analyze. - Run
flutter test. - Submit a pull request.
Please keep contributions focused, tested, and consistent with the package's lightweight design.
Support
If this package is useful to you, consider:
⭐ Starring the repository 📦 Using the package on pub.dev 🐛 Reporting issues 💡 Suggesting improvements 🤝 Contributing code or documentation
Made with ❤️ by Zeba Academy