The Composition Widget
Flutter Compositions provides three widget types that integrate with the reactive system. This page explains how each works and when to use them.
CompositionWidget
CompositionWidget is the primary building block. It looks like a StatelessWidget but features a setup() method that runs once during the widget's lifetime.
class UserCard extends CompositionWidget {
const UserCard({super.key, required this.name});
final String name;
@override
Widget Function(BuildContext) setup() {
// Runs once — declare state, computed values, watchers, and lifecycle hooks here
final props = widget();
final greeting = computed(() => 'Hello, ${props.value.name}!');
return (context) => Text(greeting.value);
}
}The Golden Rule of setup()
setup()runs only once in the widget's lifecycle (equivalent toStatefulWidget'sinitState).
This means you can safely initialize state, create controllers, and register listeners here without worrying about them being recreated on every widget rebuild.
The builder function returned from setup() is different — it re-executes whenever any of its reactive dependencies change.
Rules for setup()
- Must be synchronous — it must return a builder function directly. Use
onMounted()for async initialization. - Runs only once — state persists across rebuilds via signals.
- No conditional composition APIs — always call
ref(),computed(),watch(), etc. in a consistent order (similar to React Hooks rules). - No
BuildContextaccess — access context only in the returned builder function or inonBuildcallbacks.
CompositionBuilder
CompositionBuilder brings the same reactive experience to inline/one-off usages without creating a dedicated widget class:
CompositionBuilder(
setup: () {
final count = ref(0);
return (context) => Scaffold(
body: Center(child: Text('${count.value}')),
floatingActionButton: FloatingActionButton(
onPressed: () => count.value++,
child: const Icon(Icons.add),
),
);
},
)Use CompositionBuilder when:
- You need reactive state in a one-off location (e.g., a dialog, a test harness)
- You want to provide dependencies to a subtree without a dedicated widget class
ComputedBuilder
ComputedBuilder wraps a section of UI in its own reactive effect. It observes the refs read inside its builder and only rebuilds that subtree when one of them changes:
@override
Widget Function(BuildContext) setup() {
final count = ref(0);
final name = ref('Alice');
return (context) => Column(
children: [
// This rebuilds when count OR name changes
Text('${name.value}: ${count.value}'),
// This only rebuilds when count changes
ComputedBuilder(
builder: (context) => Text('Count only: ${count.value}'),
),
// This never rebuilds (no reactive deps)
const ExpensiveWidget(),
],
);
}Use ComputedBuilder to:
- Isolate hot spots that update frequently from parents that should stay static
- Pair with
computedvalues so expensive derivations only re-run when their dependencies change - Wrap focused fragments of the widget tree for maximum granularity
How It Works Under the Hood
All three widgets extend StatelessWidget with custom Element implementations:
| Widget | Element |
|---|---|
CompositionWidget | _CompositionElement extends StatelessElement |
CompositionBuilder | _CompositionBuilderElement extends StatelessElement |
ComputedBuilder | _ComputedBuilderElement extends StatelessElement |
This architecture eliminates the need for State objects. The custom Elements:
- On mount: create a
SetupContext, runsetup(), store the builder, register it as a reactive effect - On dependency change: the reactive system calls
markNeedsBuild()directly (nosetStateoverhead) - On unmount: dispose all effects, run
onUnmountedcallbacks, clean up controllers
Memory and Performance
- ~15-20% less memory per instance (no
Stateobject overhead) - 5-25% lower update latency (direct
markNeedsBuild()instead ofsetState) - Automatic batching — multiple ref writes in the same microtask collapse into a single rebuild
Choosing the Right Widget
| Scenario | Widget |
|---|---|
| Reusable component with props | CompositionWidget |
| One-off reactive UI (tests, dialogs) | CompositionBuilder |
| Isolate a subtree for performance | ComputedBuilder |
| Static UI without reactive state | Regular StatelessWidget / const widgets |
Next Steps
- Reactivity Fundamentals — learn
ref,computed, and reactive collections - Lifecycle Hooks —
onMounted,onUnmounted,onBuild - Reactive Props — how to react to prop changes with
widget()