Learn

Getting started

Install usecase_forge and execute a typed command through a complete lifecycle.

v1.2.1Beginnerusecase_forge

Getting started

SDK security baseline

Build and distribute applications with Dart 3.13.7+ or Flutter 3.47.8+. These are the first patched stable versions for the eMudhra incident; for later builds, use a currently supported SDK with security fixes.

The pubspec minimum describes API compatibility, not the security of an older runtime. Packages were validated with Dart 3.13.7 and Flutter 3.47.8 without changing their public API. Updating a library does not repair an installed application: affected Android, Windows, and Linux applications must be rebuilt and redistributed.

Check the SDK selected by the IDE, local builds, and CI. Updating a global SDK does not ensure that a particular project or CI uses it. Do not disable certificate verification to work around TLS errors.

This guide creates a counter UseCase. The example is intentionally small, but it still uses the same command, execution context, snapshot, terminal phase, and close lifecycle as a production operation.

Requirements

  • Dart SDK 3.13.7 or a later supported stable version with security fixes.
  • A Dart project with a pubspec.yaml.

Install the package

Terminal
shell
dart pub add usecase_forge

Import the public library:

lib/counter_usecase.dart
import 'package:usecase_forge/usecase_forge.dart';

Model state and command

State is an application value. UseCase Forge does not impose a state base class, but meaningful equality prevents redundant publication.

lib/counter_usecase.dart
final class CounterState {
  const CounterState(this.value);

  final int value;

  @override
  bool operator ==(Object other) =>
      other is CounterState && other.value == value;

  @override
  int get hashCode => value.hashCode;
}

final class Increment extends UseCaseCommand {
  const Increment({this.by = 1});

  final int by;
}

The command describes intent. It does not execute itself and does not hold UI state.

Register the handler

lib/counter_usecase.dart
final class CounterUseCase extends UseCase<CounterState> {
  CounterUseCase() : super(initialState: const CounterState(0)) {
    registerCommand<Increment>(_increment);
  }

  Future<void> _increment(
    Increment command,
    UseCaseExecutionContext<CounterState> context,
  ) async {
    final current = context.snapshot.state.value;
    context.publish(CounterState(current + command.by));
  }
}

Registration connects one command type to one handler. context.snapshot provides the Snapshot available to the running Execution; context.publish sends new State through the ordered output path.

Execute and close

bin/main.dart
Future<void> main() async {
  final counter = CounterUseCase();
  final finished = counter.stream.firstWhere(
    (snapshot) => snapshot.phase == UseCaseExecutionPhase.finished,
  );

  counter.add(const Increment(by: 2));
  await finished;

  print(counter.state.state.value);
  await counter.close();
}

add submits intent. The returned lifecycle is observed through snapshots and execution handles; close terminates owned resources and must be awaited by the owner.

Next

Read Core concepts to distinguish a command from its execution and a state value from its lifecycle snapshot. Then build the Dart task processor tutorial to apply rate limiting, debounce, grouping, and typed rejection.