usecase_forge_flutter 0.1.0-dev.1 copy "usecase_forge_flutter: ^0.1.0-dev.1" to clipboard
usecase_forge_flutter: ^0.1.0-dev.1 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_forge version

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:

  1. use the explicit useCase: argument when it is supplied;
  2. otherwise read the nearest UseCaseProvider of 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:

  1. counter;
  2. catalog search with debounce and restart;
  3. login with build and listener behavior;
  4. a dashboard with several UseCases;
  5. cooperative cancellation;
  6. Provider and Owner lifecycle;
  7. command and stream error handling;
  8. 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.

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.

0
likes
0
points
204
downloads

Publisher

verified publisherarktelos.dev

Weekly Downloads

Flutter widgets for building and reacting to usecase_forge snapshots.

Repository (GitLab)
View/report issues

Topics

#architecture #state-management #use-case #widget

License

unknown (license)

Dependencies

flutter, usecase_forge

More

Packages that depend on usecase_forge_flutter