usecase_forge_flutter 0.1.0-dev.2
usecase_forge_flutter: ^0.1.0-dev.2 copied to clipboard
Flutter widgets for building and reacting to usecase_forge snapshots.
usecase_forge_flutter #
Russian translation
English is the primary language of the ecosystem. README.ru.md is a
supplementary Russian translation.
Flutter bindings connect a pure Dart UseCase to the widget lifecycle.
Command orchestration and business rules stay outside the UI layer.
Pre-release: the API may change before 1.0. Pin the version and read the changelog before upgrading.
There is no dependency on the provider package. Context lookup, ownership,
subscriptions, list-based providers, and list-based listeners are implemented
directly with Flutter widgets.
Requirements #
- Dart SDK
^3.12.2 - Flutter
>=3.44.7 - a compatible
usecase_forgeversion
Installation #
dependencies:
usecase_forge: ^0.1.0-dev.2
usecase_forge_flutter: ^0.1.0-dev.1
flutter pub add usecase_forge_flutter:^0.1.0-dev.1
Declare usecase_forge as a direct dependency when application code imports
core APIs such as UseCase, commands, snapshots, or execution policies.
What the widgets receive #
A UseCaseSnapshot<S> is the immutable current view of a UseCase. It contains
the current business State and lifecycle information such as the execution
phase, terminal result, execution identifier, and reason.
The Flutter package listens to the same replay-latest stream exposed by core. It does not create a second State, a second history, or a separate execution model.
Basic usage #
import 'package:flutter/widgets.dart';
import 'package:usecase_forge/usecase_forge.dart';
import 'package:usecase_forge_flutter/usecase_forge_flutter.dart';
UseCaseProvider<CatalogUseCase>(
create: (context) => CatalogUseCase(repository),
child: UseCaseBuilder<CatalogUseCase, CatalogState>(
builder: (context, snapshot) {
return CatalogView(
items: snapshot.state.items,
isLoading:
snapshot.phase == UseCaseExecutionPhase.processing,
onRefresh: () {
context
.readUseCase<CatalogUseCase>()
.add(const RefreshCatalog());
},
);
},
),
)
The Provider creates and owns CatalogUseCase. Descendant widgets find that
exact UseCase type through BuildContext. The Builder subscribes to its
snapshot stream and rebuilds when a new snapshot is published.
Choosing a widget #
| Widget | Use it when |
|---|---|
UseCaseProvider |
Descendants need one exact UseCase type and the widget tree should own or expose it |
MultiUseCaseProvider |
The same subtree needs several UseCase types |
UseCaseBuilder |
UI must rebuild from the complete latest snapshot |
UseCaseSelector |
UI needs one derived value and should rebuild only when that value changes by == |
UseCaseListener |
A new snapshot should cause a one-time UI effect such as navigation or a message |
MultiUseCaseListener |
One subtree needs several independent one-time listeners |
UseCaseConsumer |
One subscription should both rebuild UI and perform one-time effects |
UseCaseOwner |
A widget must create and close a UseCase without exposing it through BuildContext |
Provider ownership #
The default constructor owns the UseCase:
UseCaseProvider<ProfileUseCase>(
create: (context) => ProfileUseCase(repository),
child: const ProfileScreen(),
)
It creates the instance once and calls close() when the Provider is removed.
If closing fails, onCloseError receives the asynchronous error. Without that
callback, the error is reported through Flutter's error reporting mechanism.
The .value constructor exposes an externally owned instance:
UseCaseProvider<ProfileUseCase>.value(
value: profileUseCase,
child: const ProfileScreen(),
)
It never closes that instance. The code that created it remains responsible for
closing it. Use .value for a service-locator instance, an object owned above
this subtree, or an instance passed from another lifecycle boundary.
Context lookup and explicit instances #
Widgets resolve their UseCase in this order:
- use the explicit
useCase:argument when it is supplied; - otherwise read the nearest
UseCaseProviderof the exact requested type.
Use a non-reactive context read to dispatch a command:
context.readUseCase<CheckoutUseCase>().add(const SubmitOrder());
Use UseCaseProvider.of<CheckoutUseCase>(context) only when the widget must
also depend on Provider identity and rebuild its dependency relationship when
the exposed instance changes.
An exact type is required. Providing CatalogUseCase does not also register it
as an unrelated base UseCase type.
Building, selecting, and listening #
UseCaseBuilder starts with the current snapshot and then listens for new
ones. buildWhen(previous, current) can skip an unnecessary rebuild.
UseCaseSelector calculates one value from each snapshot. It rebuilds only
when the selected value changes by ==:
UseCaseSelector<CartUseCase, CartState, int>(
selector: (snapshot) => snapshot.state.items.length,
builder: (context, itemCount) => Text('$itemCount'),
)
UseCaseListener skips the replayed snapshot that exists when
the subscription begins. Its callback runs only for later snapshots, so opening
a screen does not repeat an old navigation or message effect.
UseCaseListener<LoginUseCase, LoginState>(
listenWhen: (previous, current) =>
previous.state.session != current.state.session,
listener: (context, snapshot) {
if (snapshot.state.session != null) {
Navigator.of(context).pop();
}
},
child: const LoginForm(),
)
UseCaseConsumer combines Builder and Listener behavior with one stream
subscription. It accepts independent buildWhen and listenWhen filters.
Multiple UseCases and listeners #
List-based widgets reduce nesting without adding a dependency on
package:provider:
MultiUseCaseProvider(
providers: [
UseCaseProvider<AuthUseCase>(
create: (context) => AuthUseCase(authRepository),
),
UseCaseProvider<CartUseCase>(
create: (context) => CartUseCase(cartRepository),
),
],
child: MultiUseCaseListener(
listeners: [
UseCaseListener<AuthUseCase, AuthState>(
listener: handleAuthSnapshot,
),
UseCaseListener<CartUseCase, CartState>(
listener: handleCartSnapshot,
),
],
child: const CheckoutScreen(),
),
)
The order in each list is the nesting order: earlier entries are placed above later entries.
Stream and close errors #
Builder, Selector, Listener, and Consumer expose an onError callback for
errors delivered by the UseCase stream. Without it, the error is reported
through Flutter's error reporting mechanism.
This callback does not replace core command error handling. A command-specific
registerCommand(..., onError: ...) and the global UseCase.onError run in
core before an unhandled error reaches the stream.
Provider and Owner expose onCloseError for asynchronous failures from
UseCase.close().
UseCaseOwner #
UseCaseOwner is useful when a widget needs a locally owned instance but
context lookup would be unnecessary:
UseCaseOwner<SearchUseCase>(
create: () => SearchUseCase(repository),
builder: (context, useCase) {
return UseCaseBuilder<SearchUseCase, SearchState>(
useCase: useCase,
builder: buildSearch,
);
},
)
It creates one instance, passes it directly to the builder, and closes it when
removed. It does not register the instance in BuildContext.
History #
The canonical terminal history remains useCase.history from core. Flutter
widgets can read that public API directly when needed. This package does not
add a second reactive history abstraction.
Runnable examples #
example/main.dart is one Flutter application with eight scenarios:
- counter;
- catalog search with debounce and restart;
- login with build and listener behavior;
- a dashboard with several UseCases;
- cooperative cancellation;
- Provider and Owner lifecycle;
- command and stream error handling;
- replacement of an externally owned instance.
Run it from the package root:
flutter run example/main.dart
See the scenario guide for the purpose of each screen.
Project and publisher #
This package is created and maintained by Petr Orlov and published through the verified ArkTelos publisher. For package and ecosystem questions, contact packages@arktelos.dev or use the protected ArkTelos contact form.
Scope #
The bindings connect core snapshots and lifecycle to Flutter widgets. Command policies stay in core; repositories, persistence, and navigation stay in the application. The only dependency lookup provided here is the UseCase-by-type lookup in the widget tree.
License #
Licensed under the Apache License, Version 2.0. Copyright 2026 Orlov Petr Petrovich. See LICENSE and NOTICE. Flutter and other dependencies retain their own licenses.