LogoSignals.dart
Copy Markdown
rodydavis/signals.dart 999999

SignalsMixin

A State mixin that automatically handles subscription and cleanup of signals.

A State mixin that automatically handles subscription and cleanup of signals and effects created locally within a StatefulWidget.

**DEPRECATED**: This mixin is deprecated. While fully supported for backward compatibility, it adds extra stateful widget lifecycle overhead and manual binding.

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

void dispose()