tool_result 0.0.7
tool_result: ^0.0.7 copied to clipboard
Contains result pattern implementation
tool_result #
A Dart/Flutter library for handling Loading-Content-Error (LCE) state in a reactive, ergonomic, and type-safe way using the Result<T> and PaginationResult<T> patterns.
Features #
- Simple and explicit LCE state management with
Result<T>andPaginationResult<T> - Stream and Future extensions for easy LCE handling
- Ergonomic API for pattern matching, mapping, and extracting content
- RxDart and plain Dart Streams compatibility
- View model adapters for UI state management
- Fetcher architecture for reactive, cache-aware data loading
- List view adapters for pagination UI components
- Shared request utilities for deduplication
Getting Started #
Add to your pubspec.yaml:
dependencies:
tool_result:
path: tools/tool_result
The LCE Pattern #
Result<T> and PaginationResult<T> represent one of three states:
- Loading: The operation is in progress
- Content: The operation succeeded and data is available
- Error: The operation failed
Usage #
Creating Results #
final loading = Result<String>.loading();
final content = Result<String>.content('Hello');
final error = Result<String>.error(Exception('Oops'), StackTrace.current);
final initial = Result<String>.initial();
Creating PaginationResults #
final loading = PaginationResult<List<String>>.loading();
final content = PaginationResult<List<String>>.content(['A', 'B']);
final error = PaginationResult<List<String>>.error(Exception('Oops'), StackTrace.current);
final initial = PaginationResult<List<String>>.initial();
Pattern Matching #
result.when(
content: (data) => print('Content: $data'),
loading: (content) => print('Loading...'),
error: (err, content) => print('Error: $err'),
);
final value = result.maybeWhen(
content: (data) => data,
orElse: () => 'default',
);
paginationResult.when(
content: (data) => print('Page Content: $data'),
loading: (content) => print('Page Loading...'),
error: (err, content) => print('Page Error: $err'),
);
Streams #
// Convert a stream to a Result stream
final resultStream = myStream.asResultStream();
// Listen for LCE events
resultStream.listenLce(
onContent: (data) => print('Content: $data'),
onError: (err, content) => print('Error: $err'),
onLoading: (content) => print('Loading...'),
);
// Extract only content values
resultStream.extractContent().listen(print);
// Convert a stream to a PaginationResult stream
final pageStream = myPageStream.asPaginationResultStream();
// Listen for pagination LCE events
pageStream.listenLce(
onContent: (data) => print('Page Content: $data'),
onError: (err, content) => print('Page Error: $err'),
onLoading: (content) => print('Page Loading...'),
);
// Extract only content values from pagination
pageStream.map((pr) => pr.content).listen(print);
Futures #
final resultFuture = myFuture.mapWithResult();
Stream Utilities #
extractContent()— Extract only content values from Result streamsskipWhileLoading()— Skip loading statesdistinctContent()— Only emit when content changesmapContent()— Transform content valuesswitchMapContent()— Switch to new Result streams based on content
Fetchers: Reactive Data Loading #
The fetcher architecture provides a unified, reactive, and cache-aware way to load data from network, memory, and storage. Fetchers expose a stream of LCE states and handle caching, storage sync, and network requests for you.
ResultFetcherDelegate #
For single-result data (not paginated):
final fetcher = ResultFetcherDelegate<MyParams, MyData>(
fromNetwork: (params) => api.getData(params),
observeFromStorage: (params) => db.observeData(params),
toStorage: (params, data) => db.saveData(params, data),
);
fetcher.observe(params: myParams).listen((result) {
if (result.isContent) print(result.content);
});
PaginationResultFetcherDelegate #
For paginated data:
final fetcher = PaginationResultFetcherDelegate<MyParams, List<MyData>>(
fromNetwork: (params) => api.getPage(params),
concatContent: (prev, next) => [...?prev, ...next],
contentLength: (content) => content?.length ?? 0,
);
fetcher.observe(params: myParams).listen((result) {
if (result.content != null) print(result.content);
});
PaginationListResultFetcherDelegate #
For paginated lists (convenience wrapper):
final fetcher = PaginationListResultFetcherDelegate<MyParams, MyData>(
fromNetwork: (params) => api.getPage(params),
);
fetcher.observe(params: myParams).listen((result) {
if (result.content != null) print(result.content);
});
View Models #
The library provides view model adapters for UI state management:
// Convert Result to ResultViewModel
final viewModel = result.toResultViewModel(
loadingMessage: 'Loading data...',
errorInfoMapper: (error, stackTrace) => ErrorInfo.fromError(error),
);
// Convert PaginationResult to PaginationResultViewModel
final pageViewModel = paginationResult.toPaginationResultViewModel(
loadingMessage: 'Loading page...',
errorInfoMapper: (error, stackTrace) => ErrorInfo.fromError(error),
);
List View Adapters #
For Flutter UI integration:
// Result list adapter
final adapter = ListViewAdapterResult<MyData>(
resultStream: myResultStream,
itemBuilder: (context, item) => MyItemWidget(item),
loadingBuilder: (context) => LoadingWidget(),
errorBuilder: (context, error) => ErrorWidget(error),
);
// Pagination list adapter
final paginationAdapter = ListViewAdapterPaginationResult<MyData>(
resultStream: myPaginationStream,
itemBuilder: (context, item) => MyItemWidget(item),
loadingBuilder: (context) => LoadingWidget(),
errorBuilder: (context, error) => ErrorWidget(error),
onLoadMore: () => fetcher.loadNextPage(params: myParams),
);
Advanced Features #
Shared Requests #
Use SharedFutureRequest and SharedStreamRequest for deduplicating concurrent requests:
final sharedRequest = SharedFutureRequest<String>();
final future = sharedRequest.execute(() => api.getData());
Pagination Utilities #
// Load next page with fetcher
await fetcher.loadNextPage(
params: myParams.copyWith(offset: currentOffset + pageSize),
reloadStrategy: ReloadStrategy.whenBothEmpty,
);
// Check pagination state
if (result.allPagesAreLoaded) {
print('All pages loaded');
}
if (result.isPageLoading) {
print('Loading next page...');
}
Best Practices #
- Use fetchers for all async data sources to unify loading, content, and error handling
- Use storage and memory caching for performance and offline support
- Use stream extensions for reactive UIs
- Use pattern matching (
when,maybeWhen) for ergonomic state handling - Leverage view models for Flutter UI integration
- Use shared requests to prevent duplicate network calls
License #
MIT