A State mixin that automatically handles subscription and cleanup of signals and effects created locally within a StatefulWidget.
For superior, self-contained reactivity without mixin overhead, migrate to modern, high-performance APIs:
- Use SignalWidget for stateless reactive widgets.
- Use SignalStatefulWidget for stateful reactive widgets.
- Use SignalBuilder for surgical, localized rebuilding.
Legacy Usage Example#
class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> with SignalsMixin {
late final count = createSignal(0);
late final doubled = createComputed(() => count.value * 2);
@override
void initState() {
super.initState();
createEffect(() {
print('Count: ${count.value}, Doubled: ${doubled.value}');
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: ${count.value}'),
Text('Doubled: ${doubled.value}'),
ElevatedButton(
onPressed: () => count.value++,
child: const Text('Increment'),
),
],
);
}
}
Modern Migration Example#
// Modern alternative using SignalStatefulWidget:
class CounterWidget extends SignalStatefulWidget {
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
final count = signal(0);
late final doubled = computed(() => count.value * 2);
@override
void initState() {
super.initState();
// For non-widget effects, use the standard `effect` function:
effect(() {
print('Count: ${count.value}, Doubled: ${doubled.value}');
});
}
@override
Widget build(BuildContext context) {
// Implicitly tracks both signals and rebuilds on change:
return Column(
children: [
Text('Count: ${count.value}'),
Text('Doubled: ${doubled.value}'),
ElevatedButton(
onPressed: () => count.value++,
child: const Text('Increment'),
),
],
);
}
}
Methods#
View Methods
void disposeSignal(int id)
Dispose and remove signal
FutureSignal<S> createComputedFrom(List<ReadonlySignal<A>> signals, Future<S> Function(List<A> args) fn, {S? initialValue, String? debugLabel, bool lazy = true})
Async Computed is syntax sugar around FutureSignal.
Inspired by computedFrom from Angular NgExtension.
computedFrom takes a list of signals and a callback function to compute the value of the signal every time one of the signals changes.
final movieId = signal('id');
late final movie = computedFrom(args, ([movieId]) => fetchMovie(args.first));
Since all dependencies are passed in as arguments there is no need to worry about calling the signals before any async gaps with await.
FutureSignal<S> createComputedAsync(Future<S> Function() fn, {S? initialValue, String? debugLabel, List<ReadonlySignal<dynamic>> dependencies = const [], bool lazy = true})
Async Computed is syntax sugar around FutureSignal.
Inspired by computedAsync from Angular NgExtension.
computedAsync takes a callback function to compute the value of the signal. This callback is converted into a Computed signal.
final movieId = signal('id');
late final movie = computedAsync(() => fetchMovie(movieId()));
It is important that signals are called before any async gaps with await.
Any signal that is read inside the callback will be tracked as a dependency and the computed signal will be re-evaluated when any of the dependencies change.
FutureSignal<S> createFutureSignal(Future<S> Function() fn, {S? initialValue, String? debugLabel, List<ReadonlySignal<dynamic>> dependencies = const [], bool lazy = true})
Create a signal from a future
StreamSignal<S> createStreamSignal(Stream<S> Function() callback, {S? initialValue, String? debugLabel, List<ReadonlySignal<dynamic>> dependencies = const [], void Function()? onDone, bool? cancelOnError, bool lazy = true})
Create a signals from a stream
AsyncSignal<S> createAsyncSignal(AsyncState<S> value, {String? debugLabel})
Create a signal holding an async value
FlutterSignal<V> createSignal(V val, {String? debugLabel})
Create a signal and watch for changes
ListSignal<V> createListSignal(List<V> list, {String? debugLabel})
Create a ListSignal and watch for changes
SetSignal<V> createSetSignal(Set<V> set, {String? debugLabel})
Create a SetSignal and watch for changes
QueueSignal<V> createQueueSignal(Queue<V> queue, {String? debugLabel})
Create a QueueSignal and watch for changes
MapSignal<K, V> createMapSignal(Map<K, V> value, {String? debugLabel})
Create a MapSignal <K, V> and watch for changes
FlutterComputed<V> createComputed(V Function() cb, {String? debugLabel})
Create a computed and watch for changes
S bindSignal(S val)
Bind an existing signal and watch for changes
S unbindSignal(S val)
Unbind an existing signal changes
V watchSignal(S val)
Watch signal value
V unwatchSignal(S val)
Unwatch an existing signal value changes
void listenSignal(ReadonlySignal<dynamic> target, void Function() callback, {String? debugLabel})
Watch signal value
void unlistenSignal(ReadonlySignal<dynamic> target, void Function() callback)
Stop listening to a signal value
EffectCleanup createEffect(dynamic Function() cb, {String? debugLabel, dynamic Function()? onDispose})
Create a effect.
Do not call inside the build method.
Calling this method in build() will create a new effect every render.
void clearSignalsAndEffects()
Reset all stored signals and effects