composable_architecture_widgets

Flutter bindings for composable_architecture_core. Provides reactive widgets that connect the Composable Architecture's unidirectional data flow to Flutter's widget tree.

Re-exports everything from composable_architecture_core, so you only need this one dependency in Flutter apps.

Widgets

WithStore

The primary widget for connecting a Store to the UI. Rebuilds only when state changes:

WithStore<int, CounterAction>(
  store: store,
  builder: (state, send, context) {
    return Column(
      children: [
        Text('$state'),
        ElevatedButton(
          onPressed: () => send(CounterAction.increment),
          child: Text('Increment'),
        ),
      ],
    );
  },
);

Observe (scoped rebuilds)

Project state to a subset so the widget only rebuilds when that subset changes:

WithStore.observe(
  store: store,
  toLocalState: (AppState state) => state.counter,
  toGlobalAction: (CounterAction action) => AppAction.counter(action),
  builder: (count, send, context) => Text('$count'),
);

Custom Equality

Control when rebuilds happen with a custom equality function:

WithStore<MyState, MyAction>(
  store: store,
  isEqual: (prev, curr) => prev.name == curr.name, // only rebuild when name changes
  builder: (state, send, context) => Text(state.name),
);

ForEachStore

Render a collection where each item gets its own scoped store. Items are cached by ID for performance:

ForEachStore.id(
  store: store,
  getIterable: (state) => state.contacts,
  embedAction: (id, action) => AppAction.contact(id, action),
  toID: (contact) => contact.id,
  iterableBuilder: IterableBuilder.listViewBuilder(),
  builder: (context, contactStore, id) => ContactCard(store: contactStore),
);

Layout Builders

Builder Widget
IterableBuilder.listViewBuilder() ListView.builder with optional separators
IterableBuilder.sliverListBuilder() SliverList with optional separators
IterableBuilder.sliverReorderableList() Draggable reorderable sliver list
IterableBuilder.pageController() PageView
IterableBuilder.column() Column
IterableBuilder.row() Row
MaterialIterableBuilder.reorderableList() Material ReorderableListView

IfLetStore

Conditionally render content based on optional (nullable) state:

IfLetStore<UserProfile, ProfileAction>(
  store: profileStore, // Store<UserProfile?, ProfileAction>
  builder: (context, store) {
    // store is Store<UserProfile, ProfileAction> (non-nullable)
    return WithStore(
      store: store,
      builder: (profile, send, context) => Text(profile.name),
    );
  },
  orElse: (context) => Text('No profile loaded'),
);

With state projection:

IfLetStore.observe(
  store: appStore,
  toLocalState: (AppState s) => s.selectedUser, // nullable projection
  toGlobalAction: (UserAction a) => AppAction.user(a),
  builder: (context, store) => UserDetailView(store: store),
);

SwitchStore

Render different widgets based on the runtime type of the state:

SwitchStore<AuthState, AuthAction>(
  store: authStore,
  typeMap: {
    LoggedIn: (context, store) => HomeScreen(store: store),
    LoggedOut: (context, store) => LoginScreen(store: store),
    Loading: (context, store) => LoadingIndicator(),
  },
);

TextEditingController Integration

Two-way binding between a Store and TextEditingController:

WithStore.textEditingController(
  store: store,
  toText: (state) => state.searchQuery,
  fromTextEditingAction: (action) => switch (action) {
    TextEditingAction.edit(:final text) => SearchAction.updateQuery(text),
    _ => null,
  },
  builder: (controller, focusNode) => TextField(
    controller: controller,
    focusNode: focusNode,
  ),
);

TextEditingAction is a sealed class with three cases:

  • TextEditingAction.edit(String text) - text content changed
  • TextEditingAction.onKeyEvent(FocusNode, KeyEvent) - key pressed
  • TextEditingAction.onFocusChange(FocusNode) - focus changed

Dependency Injection

InheritedWidget-based service locator for providing dependencies to the widget tree:

// Register
Dependency(
  provider: DependencyProvider()
    ..register<ApiClient>(ApiClient())
    ..register<Database>(Database()),
  child: MyApp(),
);

// Resolve
final api = Dependency.of(context).resolve<ApiClient>();

App Builders

Pre-built app widgets that integrate store state with MaterialApp/CupertinoApp, including automatic handling of platform events (locale changes, brightness, app lifecycle, memory pressure, etc.).

Material

import 'package:composable_architecture_widgets/material.dart';

ComposableMaterialApp<AppState, AppAction>(
  title: 'My App',
  store: appStore,
  toMaterialAppState: (state) => state.materialAppState,
  toThemeData: (appState) => ThemeData(brightness: appState.brightness),
  routerConfig: routerConfig,
);

Cupertino

import 'package:composable_architecture_widgets/cupertino.dart';

ComposableCupertinoApp<AppState, AppAction>(
  title: 'My App',
  store: appStore,
  toCupertinoAppState: (state) => state.cupertinoAppState,
);

Platform events dispatched automatically:

  • Locale changes
  • Brightness / dark mode changes
  • App lifecycle (paused, resumed, detached)
  • Accessibility feature changes
  • Screen metrics changes
  • Memory pressure warnings

Full Example

A counter feature from state definition to UI:

// -- State & Actions --
enum CounterAction { increment, decrement, reset }

final counterReducer = Reducer<int, CounterAction, EmptyEnvironment>.transform(
  (state, action, env) => switch (action) {
    CounterAction.increment => state + 1,
    CounterAction.decrement => state - 1,
    CounterAction.reset => 0,
  },
);

// -- UI --
class CounterPage extends StatelessWidget {
  final store = Store.emptyEnvironment(0, counterReducer);

  @override
  Widget build(BuildContext context) {
    return WithStore<int, CounterAction>(
      store: store,
      builder: (state, send, context) => Scaffold(
        body: Center(child: Text('$state')),
        floatingActionButton: FloatingActionButton(
          onPressed: () => send(CounterAction.increment),
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

Installation

dependencies:
  composable_architecture_widgets: ^0.1.0

Requirements

  • Dart SDK ^3.0.6
  • Flutter SDK ^3.0.0

Libraries

composable_architecture_widgets
Flutter bindings for the Composable Architecture.
cupertino
Cupertino (iOS-style) integration for the Composable Architecture.
material
Material Design integration for the Composable Architecture.