AsyncSignal<T> class

The signal function creates a new signal. A signal is a container for a value that can change over time. You can read a signal's value or subscribe to value updates by accessing its .value property.

import 'package:signals/signals.dart';

final counter = signal(0);

// Read value from signal, logs: 0
print(counter.value);

// Write to a signal
counter.value = 1;

Signals can be created globally, inside classes or functions. It's up to you how you want to structure your app.

It is not recommended to create signals inside effects or computed, as this will create a new signal every time the effect or computed is triggered. This can lead to unexpected behavior.

In Flutter do not create signals inside build methods, as this will create a new signal every time the widget is rebuilt.

Writing to a signal

Writing to a signal is done by setting its .value property. Changing a signal's value synchronously updates every computed and effect that depends on that signal, ensuring your app state is always consistent.

.peek()

In the rare instance that you have an effect that should write to another signal based on the previous value, but you don't want the effect to be subscribed to that signal, you can read a signals's previous value via signal.peek().

final counter = signal(0);
final effectCount = signal(0);

effect(() {
	print(counter.value);

	// Whenever this effect is triggered, increase `effectCount`.
	// But we don't want this signal to react to `effectCount`
	effectCount.value = effectCount.peek() + 1;
});

Note that you should only use signal.peek() if you really need it. Reading a signal's value via signal.value is the preferred way in most scenarios.

.value

The .value property of a signal is used to read or write to the signal. If used inside an effect or computed, it will subscribe to the signal and trigger the effect or computed whenever the signal's value changes.

final counter = signal(0);

effect(() {
	print(counter.value);
});

counter.value = 1;

.previousValue

The .previousValue property of a signal is used to read the previous value of the signal. If used inside an effect or computed, it will not subscribe to the signal and not trigger the effect or computed whenever the signal's value changes.

final counter = signal(0);

effect(() {
	print('Current value: ${counter.value}');
	print('Previous value: ${counter.previousValue}');
});

counter.value = 1;

Force Update

If you want to force an update for a signal, you can call the .set(..., force: true) method. This will trigger all effects and mark all computed as dirty.

final counter = signal(0);
counter.set(1, force: true);

Disposing

Auto Dispose

If a signal is created with autoDispose set to true, it will automatically dispose itself when there are no more listeners.

final s = signal(0, autoDispose: true);
s.onDispose(() => print('Signal destroyed'));
final dispose = s.subscribe((_) {});
dispose();
final value = s.value; // 0
// prints: Signal destroyed

A auto disposing signal does not require its dependencies to be auto disposing. When it is disposed it will freeze its value and stop tracking its dependencies.

final s = signal(0);
s.dispose();
final c = computed(() => s.value);
// c will not react to changes in s

You can check if a signal is disposed by calling the .disposed method.

final s = signal(0);
print(s.disposed); // false
s.dispose();
print(s.disposed); // true

On Dispose Callback

You can attach a callback to a signal that will be called when the signal is destroyed.

final s = signal(0);
s.onDispose(() => print('Signal destroyed'));
s.dispose();

Flutter

In Flutter if you want to create a signal that automatically disposes itself when the widget is removed from the widget tree and rebuilds the widget when the signal changes, you can use the createSignal inside a stateful widget.

import 'package:flutter/material.dart';
import 'package:signals/signals_flutter.dart';

class CounterWidget extends StatefulWidget {
  @override
  _CounterWidgetState createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> with SignalsAutoDisposeMixin {
  late final counter = createSignal(0);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Counter: $counter'),
            ElevatedButton(
              onPressed: () => counter.value++,
              child: Text('Increment'),
            ),
          ],
        ),
      ),
    );
  }
}

No Watch widget or extension is needed, the signal will automatically dispose itself when the widget is removed from the widget tree.

The SignalsAutoDisposeMixin is a mixin that automatically disposes all signals created in the state when the widget is removed from the widget tree.

Testing

Testing signals is possible by converting a signal to a stream and testing it like any other stream in Dart.

test('test as stream', () {
  final s = signal(0);
  final stream = s.toStream(); // create a stream of values

  s.value = 1;
  s.value = 2;
  s.value = 3;

  expect(stream, emitsInOrder([0, 1, 2, 3]));
});

emitsInOrder is a matcher that will check if the stream emits the values in the correct order which in this case is each value after a signal is updated.

You can also override the initial value of a signal when testing. This is is useful for mocking and testing specific value implementations.

test('test with override', () {
  final s = signal(0).overrideWith(-1);

  final stream = s.toStream();

  s.value = 1;
  s.value = 2;
  s.value = 3;

  expect(stream, emitsInOrder([-1, 1, 2, 3]));
});

overrideWith returns a new signal with the same global id sets the value as if it was created with it. This can be useful when using async signals or global signals used for dependency injection. @link https://dartsignals.dev/core/signal

Inheritance
Mixed-in types
Available extensions

Constructors

AsyncSignal(AsyncState<T> value, {String? debugLabel, bool autoDispose = false})
A Signal that stores value in AsyncState

Properties

autoDispose bool
Throws and error if read after dispose and can be disposed on last unsubscribe.
getter/setter pairinherited
debugLabel String?
Debug label for Debug Mode
finalinherited
disposed bool
Check if the effect is disposed
getter/setter pairinherited
equalityCheck bool Function(AsyncState<T> a, AsyncState<T> b)
Optional method to check if to values are the same
getter/setter pairinherited
future Future<T>
The future of the signal completer
no setter
globalId int
Global ID of the signal
finalinherited
hashCode int
The hash code for this object.
no setterinherited
internalValue AsyncState<T>
no setterinherited
isCompleted bool
Returns true if the signal is completed an error or data
no setter
isInitialized bool
Check if a signal value is set (does not subscribe)
no setterinherited
requireValue → T
Returns the value of the signal
no setter
runtimeType Type
A representation of the runtime type of the object.
no setterinherited
value AsyncState<T>
Compute the current value
getter/setter pairinherited-setteroverride-getter
version int
Version numbers should always be >= 0, because the special value -1 is used by Nodes to signify potentially unused but recyclable nodes.
getter/setter pairinherited

Methods

add(T event) → void
Adds a data event to the sink.
inherited
addError(Object error, [StackTrace? stackTrace]) → void
Adds an error to the sink.
inherited
afterCreate(AsyncState<T> val) → void
Internal hook for after a signal is created
inherited
beforeUpdate(AsyncState<T> val) → void
Internal hook for after a signal is updated
inherited
call() AsyncState<T>
Return the value when invoked
inherited
close() → void
Closes the sink.
inherited
dispose() → void
Dispose the signal
inherited
get() AsyncState<T>
Helper method to get the current value
inherited
init() → void
Initialize the signal
internalRefresh() bool
inherited
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
onDispose(void cleanup()) → void Function()
Add a cleanup function to be called when the signal is disposed
inherited
overrideWith(AsyncState<T> val) Signal<AsyncState<T>>
Override the current signal with a new value as if it was created with it
inherited
peek() AsyncState<T>
In the rare instance that you have an effect that should write to another signal based on the previous value, but you don't want the effect to be subscribed to that signal, you can read a signals's previous value via signal.peek().
inherited
readonly() ReadonlySignal<AsyncState<T>>
Returns a readonly signal
inherited
refresh() Future<void>
Refresh the future
reload() Future<void>
Reload the future
reset([AsyncState<T>? value]) → void
Reset the signal to the initial value
selectData<R>(R selector(T data)) Computed<AsyncState<R>>

Available on Signal<AsyncState<T>>, provided by the AsyncSignalState extension

Select from data when available, preserving async state
set(AsyncState<T> val, {bool force = false}) bool
Set the current value by a method
inherited
setError(Object error, [StackTrace? stackTrace]) → void
Set the error with optional stackTrace to AsyncError
setLoading([AsyncState<T>? state]) → void
Set the loading state to AsyncLoading
setValue(T value) → void
Set the value to AsyncData
subscribe(void fn(AsyncState<T> value)) → void Function()
Subscribe to value changes with a dispose function
inherited
subscribeToNode(Node node) → void
inherited
toJson() → dynamic
Convert value to JSON
inherited
toString() String
A string representation of this object.
inherited
unsubscribeFromNode(Node node) → void
inherited

Operators

operator ==(Object other) bool
The equality operator.
inherited