# Choosing a tool for state and command execution

Status: comparison of Dart packages for `usecase_forge 0.1.0-dev.1`. JIT and
AOT measurements are available in
[`results_2026-08-01.md`](results_2026-08-01.md) and the later reports. This
document compares features present in released code, not plans for future
versions.

## Short answer

These packages overlap, but they start from different problems.

- UseCase Forge runs typed commands. It applies queue rules, invokes a handler,
  publishes state, handles cancellation, and records the result in bounded
  history.
- Bloc turns events into new states. Cubit offers a shorter form in which class
  methods change the state.
- Riverpod stores and connects data and dependencies. A provider can read
  another provider, while Riverpod tracks which values must be recomputed or
  disposed.
- MobX tracks reads of observable values and reruns the calculations and
  reactions that depend on them.
- Redux sends actions through a single store. A reducer is a regular function
  that receives the previous state and an action, then returns a new state.

UseCase Forge is best described as a UseCase runtime that also publishes state.
It does not have to replace every way of storing local UI state. Its main unit
is a command execution with a lifecycle. The other packages center on an event,
provider, observable value, or reducer action.

## Terms used in package comparisons

**Provider graph** means the dependency links between providers. For example,
a `cartTotal` provider reads `cartItems`. When the item list changes, Riverpod
knows that it must calculate the total again. The graph therefore serves both
dependency lookup and updates of related data.

**Computed state** means a value calculated from other values. In MobX, a
`Computed` object derives its result from observable values and recalculates it
when they change. A regular getter can also calculate a total, but MobX records
which values were read and tells the relevant reactions about changes.

**Fine-grained reactivity** means updating only the code that read the changed
observable value, instead of notifying every subscriber to the whole object.

**Reducer** means a function with the shape
`previous state + action -> new state`. It does not perform I/O. Redux usually
puts asynchronous work and other side effects in middleware.

These features are useful, but they answer different questions from command
queuing, cancellation, and terminal history.

## Capability comparison

| Question | usecase_forge | bloc | riverpod | mobx | redux |
|---|---|---|---|---|---|
| What it processes | Typed command execution | Event or Cubit method | Provider or Notifier | Observable, Action, and Reaction | Action, Reducer, and Middleware |
| Where state lives | One Snapshot per UseCase | Bloc/Cubit state | Connected provider graph | Observable and computed values | Usually one Store tree |
| Admission queue for commands | Included in core | No shared queue contract; the event stream can be extended | No shared command queue | No shared command queue | Defined by middleware |
| Concurrency and ordering | concurrent, sequential, restart, coexist, rejectNew | Events are concurrent by default; transformers are available | Async providers and methods exist, but there is no shared rule for command groups | The application defines async behavior | Defined by middleware |
| Debounce, throttle, rate limit, and conflicts | Built in and bound to command type and key | Through a transformer or custom stream layer | Through provider lifecycle or application code | Through a reaction scheduler or application code | Through middleware or application code |
| Cancellation | Cooperative signal and terminal result | A transformer can drop or switch an event; the resource still needs its own cancellation support | Disposal and invalidation can release a resource | Reactions have disposers; cancelling an operation remains an application concern | Defined by middleware |
| Shared terminal command result | completed, failed, cancelled, terminated | None | `AsyncValue` describes async data, not command history | None | Application convention |
| Bounded execution history | Included | Not in core | No command history | No command history | Not in core; DevTools may retain state history |
| A new subscriber immediately sees the current value | Yes | A getter exists; the regular stream does not replay on subscription | Yes | Yes, through a read or configured reaction | A getter exists; `onChange` does not replay the value |
| Automatically connected calculations | Application code | Usually selectors in an integration layer | Providers can depend on each other; `select` is available | `Computed` and automatic read tracking | Selectors; memoization is added separately |
| Creating and replacing dependencies | Outside core | Outside Bloc core | A primary feature | Store composition remains an application choice | Usually constructors and middleware |
| Errors | Stage, context, command and shared `onError`, stream error | Hooks, observer, and state conventions | Loading/error in async values and observers | Reaction errors, spy, and application conventions | Reducer and middleware conventions |
| Code generation | Not required | Not required in core | Optional | Common, but not required for basic objects | Not required |

The table describes features supplied by each Dart package. A missing core
feature does not mean an application cannot implement it.

Flutter integrations are outside the measurements. Bloc, Riverpod, MobX, and
Redux have separate Flutter packages. The UseCase Forge ecosystem already has
preview versions of `usecase_forge_flutter`, `usecase_forge_test`, and
`usecase_forge_devtools`. None of them is a dependency of the pure Dart core.

## UseCase Forge

### Good fit

- A command represents a distinct business operation, and the UI must not
  mutate the state object directly.
- The phase, result, and termination reason matter alongside the latest value.
- Every caller must apply the same debounce, rate limit, sequencing,
  replacement, or restart rules.
- A handler needs a small cooperative cancellation API.
- The same code must run on the Dart VM, Flutter, the command line, and in
  tests.
- Bounded terminal history helps diagnose application behavior.

### Costs and limits

- The package is a preview and has no public adoption history yet.
- Users must learn commands, entries, phases, instructions, hooks, and history.
  That is excessive for a small local counter.
- The runtime creates execution entries, snapshots, and diagnostic data.
  Writing directly to an Observable or calling a reducer naturally does less
  work.
- Cancellation is cooperative: Dart cannot forcibly stop an arbitrary Future.
- Core has no provider graph, automatic computed values, persistence, or time
  travel. Another ecosystem package or application code must supply them when
  needed.

Early queue-scaling and instruction-configuration problems are recorded as
`UF-PERF-001` and `UF-PERF-003`. Later core changes and new measurements appear
in the optimization, hardening, and stable-instructions reports. A public site
should show the latest comparable results instead of presenting the old
baseline as current behavior.

UseCase Forge is unnecessary when the task is one local observable value or a
graph of cached data with no command lifecycle.

## Bloc

Bloc offers an event-driven Bloc and a shorter, method-based Cubit. It is a
mature way to describe state transitions, especially for teams that value a
familiar model, established testing tools, observers, and Flutter integration.

Bloc core has no single Entry contract covering admission phase, terminal
result, bounded history, keyed rate/conflict rules, and cancellation context.
Applications can build these features on events and transformers, but then the
application owns the contract. In the other direction, UseCase Forge cannot
yet match Bloc's maturity or adoption history.

Official material: [bloc](https://pub.dev/packages/bloc),
[core concepts](https://bloclibrary.dev/bloc-concepts/), and
[bloc_test](https://pub.dev/packages/bloc_test).

## Riverpod

Riverpod is useful for creating dependencies, caching asynchronous data,
replacing providers in tests, releasing provider-owned resources when they are
no longer used, and recalculating connected values. Releasing a resource does
not mean that Riverpod manages memory instead of the Dart VM. Riverpod closes
the resource it controls and stops retaining it; Dart's garbage collector then
reclaims memory normally.

Riverpod has no universal Admission/Pending queue, command Entry, or bounded
history of command completion. `AsyncValue` describes the state of asynchronous
data, not a log of typed commands.

The packages can work together: Riverpod creates repositories and keeps a
cache, while UseCase Forge runs commands. The combination is useful only when a
project needs both models. An extra layer has no value by itself.

Official material: [riverpod](https://pub.dev/packages/riverpod),
[providers](https://riverpod.dev/docs/concepts2/providers),
[testing](https://riverpod.dev/docs/how_to/testing), and
[code generation](https://riverpod.dev/docs/concepts/about_code_generation).

## MobX

MobX suits applications that benefit from automatic links between observable
fields, calculated values, and reactions. Code that read a changed Observable
can update without subscribing to the entire state object. This works well for
complex forms and screens with many independently changing fields.

Reading one field does not make MobX better at managing a business command. It
addresses a different boundary. An Observable/Action pair has no UseCase queue,
terminal result, or command history. Conversely, UseCase Forge has no automatic
`Computed` graph or MobX reactions.

MobX may manage presentation data while UseCase Forge runs longer business
operations. Many projects will need only one of these approaches.

Official material: [MobX concepts](https://mobx.netlify.app/concepts),
[mobx](https://pub.dev/packages/mobx), and
[mobx_codegen](https://pub.dev/packages/mobx_codegen).

## Redux

Redux keeps state in a Store and changes it through actions and reducers. A
pure reducer is easy to test: it performs no I/O and returns the same result for
the same arguments. Middleware usually handles asynchronous work and side
effects.

Redux does not define one lifecycle for a business command. Queues,
cancellation, terminal results, and async orchestration become middleware
conventions. Time travel in Redux DevTools shows state changes, but it is not
the same record as bounded Entry history with execution results.

The base Dart API accepts dynamically typed actions, although an application
can add typed helpers. A low core release frequency alone does not prove that a
package is abandoned. Check the required platforms, current maintenance, and
the state of its Flutter integration before choosing it.

Official material: [Redux Dart API](https://pub.dev/documentation/redux/latest/),
[redux versions](https://pub.dev/packages/redux/versions), and
[redux_dev_tools](https://pub.dev/documentation/redux_dev_tools/latest/).

## Combining packages

Packages can be combined when their responsibilities are clear. For example:

- UseCase Forge runs business commands and keeps terminal history;
- Riverpod creates dependencies and caches data;
- Bloc, MobX, or Redux manages presentation state;
- `usecase_forge_flutter` connects a UseCase to widget lifecycle;
- `usecase_forge_devtools` displays command execution during debugging.

Every extra layer adds concepts and state transitions. Choose such a
combination for a concrete project need and validate it in a real use case.

## Reading performance results

The report separates JIT and AOT results. Each row names the scenario,
versions, environment, median, p95, minimum/maximum, spread, and source
artifacts. Different operations are not reduced to a single score, and the
packages are not ranked from first to last.

A lower time in one row means only that the measured operation cost less in
that run. It does not prove that one architecture is better. Likewise, the
higher cost of UseCase Forge must be considered alongside the work it performs:
queues, phases, hooks, cancellation, and history.
