tool_result 0.0.7 copy "tool_result: ^0.0.7" to clipboard
tool_result: ^0.0.7 copied to clipboard

PlatformAndroid

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> and PaginationResult<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 streams
  • skipWhileLoading() — Skip loading states
  • distinctContent() — Only emit when content changes
  • mapContent() — Transform content values
  • switchMapContent() — 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