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
.valueinside 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");
});
- Inheritance
-
- Object
- Signal<
AsyncState< T> > - AsyncSignal
- Mixed-in types
- Available extensions
Constructors
-
AsyncSignal(AsyncState<
T> value, {SignalOptions<AsyncState< ? options})T> > - 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
eventto the sink.inherited -
addError(
Object error, [StackTrace? stackTrace]) → void -
Adds an
errorto 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<
Select from data when available, preserving async stateAsyncState< , provided by the AsyncSignalState extensionT> > -
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