composable_architecture_generator
Code generator for the Composable Architecture in Dart. Automatically generates Lens, ActionLens, Prism, and utility extensions from annotations provided by composable_architecture_core.
Installation
Add to your pubspec.yaml:
dependencies:
composable_architecture_core: ^0.1.0
dev_dependencies:
composable_architecture_generator: ^0.2.0
build_runner: ^2.13.0
Run code generation:
dart run build_runner build
Annotations
@tcaState
Generates a Lens for every field in the annotated class, enabling modular state composition via Reducer.pullback.
@tcaState
class CounterState {
final int count;
final String label;
const CounterState({required this.count, required this.label});
CounterState copyWith({int? count, String? label}) =>
CounterState(count: count ?? this.count, label: label ?? this.label);
}
Generated output:
class CounterStateLens {
static Lens<CounterState, int> count = (
get: (state) => state.count,
set: (state, count) => state.copyWith(count: count),
);
static Lens<CounterState, String> label = (
get: (state) => state.label,
set: (state, label) => state.copyWith(label: label),
);
}
List and Set fields also get an Iterable variant lens (e.g. itemsIterable) for use with ForEachIterableReducer.
@tcaAction
Generates ActionLens / Prism for child-action parameters, and utility extensions for pattern matching on sealed action classes.
@tcaAction
sealed class AppAction {
const AppAction();
}
class AppActionIncrement extends AppAction {
const AppActionIncrement();
}
class AppActionSetLabel extends AppAction {
final String label;
const AppActionSetLabel(this.label);
}
With freezed
When using @freezed, the generator uses the declared subclasses from the redirecting factory constructors:
@tcaAction
@freezed
sealed class AppAction with _$AppAction {
const factory AppAction.increment() = AppActionIncrement;
const factory AppAction.setLabel(String label) = AppActionSetLabel;
}
With dart_mappable
When using @MappableClass(), the generator creates concrete subclasses automatically from factory constructors:
@tcaAction
@MappableClass(discriminatorKey: 'type')
sealed class AppAction with AppActionMappable {
const AppAction();
const factory AppAction.increment() = AppActionIncrement;
const factory AppAction.setLabel(String label) = AppActionSetLabel;
}
Generated utilities
Optionally generates an extension on the sealed action class with helpers for working with subclasses without manual is / as casts:
| Method | Description |
|---|---|
mapEvery<T> |
Exhaustive pattern match — every subclass must be handled. Returns T. |
mapAny<T> |
Partial match with a required orElse fallback. Returns T. |
mapAnyOrNull<T> |
Partial match returning T? — unhandled cases return null. |
onEvery |
Like mapEvery but returns void — for side effects. |
onAny |
Like mapAny but returns void. |
is<Subclass> |
Type-check getters (e.g. isAppActionIncrement). |
<factoryName> |
Nullable cast getters (e.g. action.increment returns AppActionIncrement?). |
extension AppActionUtils on AppAction {
T mapEvery<T>({
required T Function(AppActionIncrement) increment,
required T Function(AppActionSetLabel) setLabel,
}) => ...;
T mapAny<T>({required T Function() orElse, ...}) => ...;
T? mapAnyOrNull<T>({...}) => ...;
void onEvery({...}) => ...;
void onAny({...}) => ...;
bool get isAppActionIncrement => ...;
bool get isAppActionSetLabel => ...;
AppActionIncrement? get increment => ...;
AppActionSetLabel? get setLabel => ...;
}
Utility generation is disabled by default. To enable it per-class:
@TCAAction(generateUtils: true)
sealed class AppAction { ... }
Or enable it globally via build.yaml:
targets:
$default:
builders:
composable_architecture_generator:
options:
generate_utils: true
Local annotation always takes priority over the global option. The resolution order is: local annotation > global option > default (false).
ActionLens & Prism
When a subclass parameter is itself annotated with @tcaAction, the generator produces an ActionLens (single child-action parameter) or Prism (child-action + ID parameter for collections):
@tcaAction
sealed class ParentAction {
const ParentAction();
}
class ParentActionChild extends ParentAction {
final ChildAction childAction;
const ParentActionChild(this.childAction);
}
Generated:
class ParentActionLens {
static ActionLens<ParentAction, ChildAction> child = (
extract: (action) => action is ParentActionChild ? action.childAction : null,
embed: (value) => ParentAction.child(value),
);
}
@tcaRoute
Generates RouteLens and Prism for classes extending Routable, used with composable_architecture_router for Navigator 2.0 integration.
@tcaRoute
class MyRoute extends Routable<AppState, RouteState, LocalState?, AppAction, LocalAction> {
// implement buildLocalState, setBackFromLocalState, extractAction, toAppAction
}
Generated:
extension on MyRoute {
RouteLens<AppState, RouteState, LocalState?> get routeLens => ...;
Prism<AppAction, LocalAction, RouteID> get actionPrism => ...;
}
Requirements
- Dart SDK
>=3.1.2 <4.0.0 composable_architecture_core^0.1.0- State classes must implement
copyWith - Action classes must be
sealed