Reactivity In Depth
This document explains how Flutter Compositions builds fine-grained reactivity on top of Flutter’s widget system.
Building Blocks
Ref
ref(value) returns a Ref<T>—a wrapper that tracks reads and writes to .value.
- Reads register the current reactive observer (builder, computed, or effect).
- Writes mark the ref as dirty and notify subscribers.
Computed
computed(() => ...) lazily evaluates a function and caches the result until any dependency changes. It behaves like a memoized getter.
Effect
Effects are registered by builders, watch, or watchEffect. Each effect captures the refs it touches and reruns when any of them change.
Dependency Tracking
- Before a reactive function executes, the runtime pushes it onto a “current observer” stack.
- When a ref’s getter runs, it attaches the current observer to its subscriber list.
- Once the function finishes, the observer is popped.
- When
.valuemutates, every subscriber is queued for re-execution.
This is the same model popularized by Vue’s reactivity system.
Scheduler
Updates are batched in a microtask queue:
- A setter marks the ref dirty and enqueues observers.
- Flutter Compositions deduplicates observers to avoid redundant work.
- Once the microtask runs, each observer executes in order.
Builders wrap their work in setState, so Flutter sees them as ordinary widget updates.
Integration with Flutter
CompositionWidgetrunssetup()once, grabs the returned builder, and registers it as an effect.- When dependencies change, the builder triggers
setState, scheduling a rebuild that Flutter diffs like any other widget. - Lifecycle hooks (
onMounted,onUnmounted,onBuild) piggyback on Flutter’s lifecycle and the reactive scheduler.
Avoiding Common Pitfalls
- Stale props: Access props via
widget<T>()so you get a reactive wrapper around the latest widget instance. - Mutable collections: Replace lists or maps wholesale (
todos.value = [...todos.value, todo]) so the runtime sees a new reference. - Async gaps: When mixing async callbacks and refs, read the latest value inside the callback instead of capturing stale data.
Tooling Hooks
The runtime exposes onCleanup behind the scenes so every effect can register teardown logic. Composables like watch and useStream rely on it to remove listeners automatically.
Performance Characteristics
- Accessing refs is O(1).
- Each write is proportional to the number of subscribed observers.
- Builders stay granular: only the widgets that read a changing ref rebuild.
ComputedBuilder Utility
ComputedBuilder wraps a section of UI in its own reactive effect. It observes the refs that are read inside the provided builder and only rebuilds that subtree when one of those refs changes.
- Use it to isolate hot spots that update frequently from parents that should stay static.
- Pair it with
computedvalues so expensive derivations only re-run when their dependencies change. - Prefer small builders—
ComputedBuilderis most effective when it owns a focused fragment of the widget tree.
Internally it registers an effect during initState, triggers setState on change, and automatically disposes itself when unmounted, so there is no manual cleanup required.
For the widget constructor and parameters, consult the ComputedBuilder API reference.