resultex 2.4.0
resultex: ^2.4.0 copied to clipboard
A robust core architecture ecosystem for Flutter and Dart, providing unified dependency injection and error management utilities.
Resultex #
A robust, type-safe error handling ecosystem for Dart and Flutter that implements the Result pattern for clean, predictable error handling without exceptions.
๐ฆ Overview #
resultex brings a functional programming approach to error handling by wrapping your operational
outcomes in a type-safe container. Instead of throwing heavy exceptions and messing up your call
stack, every function returns a Result that is either a SuccessResult wrapping your value or a
FailureResult containing structured error details.
Why Resultex? #
- ๐ก๏ธ Zero Try-Catch Boilerplate - Handle operational errors exactly where they matter.
- ๐ Type-Safe Destructuring - The Dart compiler ensures you handle both success and failure states.
- ๐งฉ Pattern Matching - Fully optimized for Dart 3+ exhaustive switch expressions.
- ๐ Composable Pipelines - Chain complex operations effortlessly with
mapandflatMap. - ๐งช Highly Testable - Predictable control flow, making Unit Testing a breeze without unexpected runtime crashes.
- ๐ Clean Architecture Approved - Follows SOLID principles, separating business logic outcomes from presentation layers.
๐ Installation #
Prerequisites #
- Dart SDK:
>=3.0.0 <4.0.0 - Flutter SDK:
>=3.10.0
Add resultex to your pubspec.yaml:
dependencies:
resultex: ^2.2.0
๐ฏ Core Concepts #
- Successful Result
final result = Result.success(User(id: 1, name: "Hassan"));
// Contains a SuccessResult wrapping a Success<User> container
- Failure Result
final result = Result.failure(Failure(message: "Network request timeout"));
// Contains a FailureResult with messages, codes, and optional stackTraces
- Exhaustive Pattern Matching (Dart 3+)
switch (result) {
case SuccessResult<User>(success: Success(:final value)):
print('Welcome back, ${value.name}!');
case FailureResult<User>(failure: final fail):
print('Error occurred: ${fail.message}');
}
๐จ Reactive UI Layer (New in v2.3.0) #
๐ฆ State Management via ResultNotifier
A lightweight, lifecycle-aware alternative to heavy state management boilerplate. Track any
asynchronous operation safely inside your controllers or state classes.
final userNotifier = ResultNotifier<User>();
// Automatically handles loading state, catches unexpected errors, and updates the UI
userNotifier.track
(
repository
.
fetchUserProfile
(
)
);
๐งฑ Declarative Layouts via ResultBuilder
Eliminate bloated conditions inside your widget tree. Separate your UI cleanly into three
predictable layout paths.
@override
Widget build(BuildContext context) {
return ResultBuilder<User>(
notifier: _userNotifier,
onLoading: (context) => const CircularProgressIndicator(),
onFailure: (context, failure) => Text('Error: ${failure.message}'),
onSuccess: (context, user) => Text('Hello, ${user.name}'),
);
}
๐ Advanced Functional Pipelines #
๐ Type-Safe Record Zipping (Dart 3+) #
Resultex provides an elegant, compile-time type-safe ecosystem to consolidate completely dynamic and
heterogeneous Result instances concurrently using modern Dart 3 Records.
Instead of manual index casting (as User), you can wrap your independent operations inside a
record tuple and call .zip() directly. If any internal operation yields a FailureResult, the
entire pipeline short-circuits instantly, propagating that precise failure downstream.
Heterogeneous Zip Example (Up to 5 Elements) #
import 'package:resultex/resultex.dart';
Future<void> loadDashboard() async {
// 1. Fire asynchronous requests or continuous operations
final Result<User> userRes = await authRepository.getProfile();
final Result<CryptoWallet> walletRes = await cryptoRepository.getBalance();
// 2. Zip disparate types seamlessly with 100% type safety
final Result<DashboardView> dashboardResult = (userRes, walletRes).zip(
(User user, CryptoWallet wallet) {
// Both parameters are strongly typed based on the input record tuple!
return DashboardView(user: user, wallet: wallet);
},
);
// 3. Evaluate and fold into your presentation tier
dashboardResult.fold(
onSuccess: (view) => renderDashboard(view),
onFailure: (failure) => showErrorSnackBar(failure.message),
);
}
๐ Asynchronous Chaining (asyncMap & asyncFlatMap)
Chain multiple asynchronous dependencies sequentially without running into await callback hell.
// Streamline complex database/network pipelines elegantly
Future<Result<Orders>> ordersResult = repository.getUser(1) // Future<Result<User>>
.asyncMap((user) => user.id) // Future<Result<int>>
.asyncFlatMap((id) => orderRepository.getOrders(id)); // Future<Result<Orders>>
๐ญ Adapting Errors downstream (mapFailure)
Transform internal exceptions into localized or presentation-friendly error messages before hitting
the UI.
final uiResult = apiResult.mapFailure(
(fail) => Failure(message: 'Localized Error: ${fail.message}'),
);
๐ Resilient Operation Retries (withRetry)
Attach configurable recovery retries to any transient network dispatch with support for exponential
backoff.
final networkResult = await
Result.guardAsync
(
() => (() => http.get(uri)).withRetry(
const RetryOptions(maxAttempts: 3, delay: Duration(seconds: 2), backoffFactor: 2.0),
),
);
๐จ Advanced Features & Extensions #
- UI Layer Clean Mapping (.when()) The package provides a tailored Flutter extension to directly map your Result states into widgets inside the build method without bloated conditions.
import 'package:resultex/core/utils/result_flutterx_extension.dart';
@override
Widget build(BuildContext context) {
return userResult.when(
onSuccess: (user) => Text('Hello, ${user.name}'),
onFailure: (failure) => Text('Error: ${failure.message}'),
);
}
- Concurrent Parallel Execution (ResultUtils.combineAll) When executing multiple operations simultaneously, ResultUtils.combineAll fires them in parallel. If any operation fails, it aggregates all intercepted errors into a unified MultiFailure contract instead of short-circuiting on the first error.
import 'package:resultex/core/utils/result_utils.dart';
final Result<List<dynamic>> dashboardResult = await
ResultUtils.combineAll
([repository.fetchUserProfile(), // Future<Result<User>>
repository.fetchNotifications(), // Future<Result<List<Notif>>>
repository.fetchCryptoWallet(), // Future<Result<Wallet>>
]);
dashboardResult.when(
onSuccess: (data) {
final user = data[0] as User;
final wallet = data[2] as CryptoWallet;
// Render your screen using safely casted data
},
onFailure: (failure) {
if (failure is MultiFailure) {
print('${failure.failures.length} operations failed concurrently.');
}
},
);
- Operational Recovery Path (.recover()) Intercept an operational Failure and route it through an alternative backup execution pipeline smoothly.
Result<User> userResult = await
authRepository.fetchRemoteUser
();
Result<User> finalizedResult = userResult.recover((failure) {
print('Remote fetch failed: ${failure.message}. Falling back to local cache...');
return Result.success(localDatabase.getCachedUser());
});
๐ Fluid Usage Guide
Safe Execution Closures (guard / guardAsync)
Automatically intercept synchronous or asynchronous unexpected exceptions and encapsulate them into
safe Result variants.
// Synchronous parsing guard
final result = Result.guard(() => jsonDecode(rawJsonString));
// Asynchronous API call guard
final networkResult = await
Result.guardAsync
(
() => http.get(Uri.parse(url
)
)
);
Functional Chaining (map & flatMap)
Transform successful values or sequentially chain multiple business operations without nesting
blocks.
// Map values on success
final nameResult = result.map((user) => user.name); // Result<String>
// Chain dependencies flatly
final ordersResult = await
repository.getUser
(1)
.then((res) => res.flatMap((user) => orderRepository.getOrders(user.id)
)
);
๐ง Advanced ResultExecutor Architecture
Wrap execution scopes into monitored contexts complete with automated structured logging and error
tracking capabilities.
final executor = ResultExecutor(logger: AppLogger());
final result = await
executor.executeAsync
(
() async {
`final data = await api.fetchData();
`return processData(data);
},
context
:
fetchAndProcessDashboardData
);
๐๏ธ Best Practices #
โ DO
- Use ResultUtils.combineAll for maximizing parallel performance across independent network dispatches.
- Use the .when() extension inside Flutter layout building pipelines for pristine scannability.
- Provide meaningful context tags within ResultExecutor blocks to maintain bulletproof debug logs.
โ DON'T
- Don't force-extract values without evaluating state via pattern matching or explicit folds.
- Don't catch generic raw exceptions manually inside execution blocks managed by guard hooks.
๐ License #
This project is licensed under the MIT License - see the LICENSE file for details. Open Source development is respected; feel free to modify, distribute, and implement this package in both public repositories and enterprise closed-source commercial systems.