AsyncSignal<T> class

A Signal is a reactive container for a value that changes over time. It forms the bedrock of the reactive framework, allowing fine-grained, glitch-free propagation of state updates to dependent computeds and effects.

You can read a signal's current state, mutate it to dispatch updates, or subscribe to changes by accessing its .value property inside any active reactive context.

Core Example

import 'package:signals/signals.dart';

// Create a reactive signal holding an integer
final counter = signal(0);

// Read the value: prints 0
print(counter.value);

// Write to a signal: dispatches updates to all downstreams synchronously
counter.value = 1;

Key API Capabilities

1. Reading & Writing via .value

The .value property is the default way to interact with a signal.

  • Inside a Reactive Context: Accessing .value inside a computed block or effect callback automatically registers the signal as a dependency, establishing an active subscription.
  • Outside a Reactive Context: Acts as a standard getter and setter, allowing you to fetch or update the underlying state.

2. Non-reactive Reads via .peek()

If you need to read a signal's current value without subscribing to its updates inside a reactive context, use the .peek() method. This is invaluable when writing to another signal inside an effect based on the previous state, preventing infinite update loops (cycles).

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

effect(() {
  // Subscribes to changes of `counter`
  final current = counter.value;
  print('Counter updated: $current');

  // Read current count non-reactively and increment.
  // The effect will NOT subscribe to `effectTriggerCount`.
  effectTriggerCount.value = effectTriggerCount.peek() + 1;
});

3. Accessing the Previous State via .previousValue

Signals automatically cache their immediately preceding value. Accessing .previousValue lets you perform diffing or historic analysis. Like .peek(), reading .previousValue does not establish a reactive dependency.

final username = signal("initial_user");

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

username.value = "new_user";
// Prints:
// Current Username: new_user
// Previous Username: initial_user

4. Force Updates via .set()

When dealing with mutable data types (e.g., custom class instances, collections), mutating properties directly does not change the instance reference. You can force an update using .set(..., force: true) to skip standard equality checks and notify all downstreams.

final numbers = signal([1, 2, 3]);

// Modify the list in-place and force notify
numbers.value.add(4);
numbers.set(numbers.value, force: true);

Lifecycle & Resource Management

Auto-Disposal

If a signal is constructed with autoDispose: true, it will automatically destroy itself when it no longer has active reactive listeners (subscriptions). This prevents memory leaks by freeing resources as soon as they are out of scope.

final s = signal(0, options: SignalOptions(autoDispose: true));

s.onDispose(() => print('Signal has been disposed!'));

// Create active subscriber
final dispose = s.subscribe((_) {});

// Cancel subscription: s has no listeners, so it self-disposes
dispose();
// Prints: "Signal has been disposed!"

You can manually verify the lifecycle state using .disposed, or register custom clean-up routines via .onDispose(callback).


Flutter Integration

In Flutter applications, manage state and reactivity seamlessly by using SignalWidget (for stateless widgets) or SignalStatefulWidget (for stateful widgets). These widgets establish an implicit reactive context directly at the element layer. Any signal accessed via .value inside the build method is automatically tracked, and the widget automatically rebuilds when they mutate.

Stateless Example with SignalWidget

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

final counter = signal(0);

class CounterDisplay extends SignalWidget {
  const CounterDisplay({super.key});

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

Stateful Example with SignalStatefulWidget

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

class CounterDisplay extends SignalStatefulWidget {
  const CounterDisplay({super.key});

  @override
  State<CounterDisplay> createState() => _CounterDisplayState();
}

class _CounterDisplayState extends State<CounterDisplay> {
  // Local signal scoped to this widget state:
  final counter = signal(0);

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

Testing Strategies

1. Converting to Streams

You can convert any reactive signal into a standard Dart Stream by calling .toStream(). This is highly beneficial for testing signal value sequences in order using test matchers.

test('emits sequential count updates in order', () async {
  final counter = signal(0);
  final stream = counter.toStream();

  counter.value = 1;
  counter.value = 2;

  await expectLater(stream, emitsInOrder([0, 1, 2]));
});

2. Dependency Injection & Mock Overrides

Global or lazy signals used across your application can be mocked or overridden during testing via .overrideWith(value). This returns a new signal sharing the same global identifier, helping you mock complex state dependencies seamlessly.

test('mocking global signals', () {
  final apiToken = signal("production_token");

  // Override with test mock token
  apiToken.overrideWith("mock_test_token");

  expect(apiToken.value, "mock_test_token");
});

@link https://dartsignals.dev/core/signal

Inheritance
Mixed-in types
Available extensions

Constructors

AsyncSignal(AsyncState<T> value, {SignalOptions<AsyncState<T>>? options})
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 Debug label for Debug Mode
no setterinherited
disposed bool
Check if the effect is disposed
getter/setter pairinherited
equalityCheck → SignalEquality<AsyncState<T>>
Optional method to check if to values are the same
no setterinherited
future Future<T>
The future of the signal completer
no setter
globalId int
finalinherited
hashCode int
The hash code for this object.
no setterinherited
internalValue AsyncState<T>
@internal Internal getter for the raw value without subscription tracking.
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
name String?
finalinherited
requireValue → T
Returns the value of the signal
no setter
runtimeType Type
A representation of the runtime type of the object.
no setterinherited
unwatched → void Function()?
finalinherited
value AsyncState<T>
Gets the current value of the signal.
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
watched → void Function()?
finalinherited

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
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
@internal Refreshes the signal's value internally.
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
Updates the signal's value by method call.
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
@internal Subscribes this signal to notifications from a given dependency node.
inherited
toJson() → dynamic
Convert value to JSON
inherited
toString() String
A string representation of this object.
inherited
unsubscribeFromNode(Node node) → void
@internal Unsubscribes this signal from notifications from a given dependency node.
inherited

Operators

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