Computed<T> class
Data is often derived from other pieces of existing data. The computed
function lets you combine the values of multiple signals into a new signal that can be reacted to, or even used by additional computeds. When the signals accessed from within a computed callback change, the computed callback is re-executed and its new return value becomes the computed signal's value.
Computed
class extends theSignal
class, so you can use it anywhere you would use a signal.
import 'package:signals/signals.dart';
final name = signal("Jane");
final surname = signal("Doe");
final fullName = computed(() => name.value + " " + surname.value);
// Logs: "Jane Doe"
print(fullName.value);
// Updates flow through computed, but only if someone
// subscribes to it. More on that later.
name.value = "John";
// Logs: "John Doe"
print(fullName.value);
Any signal that is accessed inside the computed
's callback function will be automatically subscribed to and tracked as a dependency of the computed signal.
Computed signals are both lazily evaluated and memoized
Force Re-evaluation
You can force a computed signal to re-evaluate by calling its .recompute
method. This will re-run the computed callback and update the computed signal's value.
final name = signal("Jane");
final surname = signal("Doe");
final fullName = computed(() => name.value + " " + surname.value);
fullName.recompute(); // Re-runs the computed callback
Disposing
Auto Dispose
If a computed signal is created with autoDispose set to true, it will automatically dispose itself when there are no more listeners.
final s = computed(() => 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.
This means that it will no longer react to changes in its dependencies.
final s = computed(() => 0);
s.dispose();
final value = s.value; // 0
final b = computed(() => s.value); // 0
// b will not react to changes in s
You can check if a signal is disposed by calling the .disposed
method.
final s = computed(() => 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 = computed(() => 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 createComputed
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(context, 0);
late final isEven = createComputed(context, () => counter.value.isEven);
late final isOdd = createComputed(context, () => counter.value.isOdd);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Counter: even=$isEven, odd=$isOdd'),
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 computed signals is possible by converting a computed to a stream and testing it like any other stream in Dart.
test('test as stream', () {
final a = signal(0);
final s = computed(() => a());
final stream = s.toStream();
a.value = 1;
a.value = 2;
a.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 computed signal when testing. This is is useful for mocking and testing specific value implementations.
test('test with override', () {
final a = signal(0);
final s = computed(() => a()).overrideWith(-1);
final stream = s.toStream();
a.value = 1;
a.value = 2;
a.value = 2; // check if skipped
a.value = 3;
expect(stream, emitsInOrder([-1, 1, 2, 3]));
});
overrideWith
returns a new computed signal with the same global id sets the value as if the computed callback returned it.
@link https://dartsignals.dev/core/computed
- Inheritance
-
- Object
- ReadonlySignal<
T> - Computed
- Implemented types
- Implementers
- Available extensions
Constructors
-
Computed(ComputedCallback<
T> fn, {String? debugLabel, bool autoDispose = false}) -
Data is often derived from other pieces of existing data. The
computed
function lets you combine the values of multiple signals into a new signal that can be reacted to, or even used by additional computeds. When the signals accessed from within a computed callback change, the computed callback is re-executed and its new return value becomes the computed signal's value.
Properties
- autoDispose → bool
-
Throws and error if read after dispose and can be
disposed on last unsubscribe.
finalinherited
- debugLabel → String?
-
Debug label for Debug Mode
finalinherited
- disposed ↔ bool
-
Returns true if dispose has been called and will throw and
error on value read
getter/setter pairinherited
- globalId → int
-
Global ID of the signal
finalinherited
- hashCode → int
-
The hash code for this object.
no setterinherited
- hasSources → bool
-
Check if there are any targets attached
no setter
- hasTargets → bool
-
Check if there are any targets attached
no setterinherited
- initialValue → T
-
Value that the signal was created with
no setteroverride
- isInitialized → bool
-
Check if the signal is lazy and has not had a value set
no setteroverride
- previousValue → T?
-
Previous value that was set before the current
no setterinherited
- runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
-
sources
→ Iterable<
ReadonlySignal> -
@internal for testing getter to track all the signals currently
subscribed in the signal
no setter
-
targets
→ Iterable<
SignalListenable> -
@internal for testing getter to track all the effects currently
effected in the signal
no setterinherited
- value → T
-
Compute the current value
no setteroverride
- version → int
-
Version number is used to track changes and will increment for every set
no setterinherited
Methods
-
call(
) → T -
Return the value when invoked
inherited
-
dispose(
) → void -
Dispose the signal
override
-
get(
) → T -
Get the current value
inherited
-
listen(
BuildContext context, void callback(), {String? debugLabel}) → void -
Available on ReadonlySignal<
Used to listen for updates on a signal but not rebuild the nearest elementT> , provided by the FlutterReadonlySignalUtils extension -
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
onDispose(
void cleanup()) → EffectCleanup -
Add a cleanup function to be called when the signal is disposed
inherited
-
overrideWith(
T val) → Computed< T> - Override the current signal with a new value as if it was created with it
-
peek(
) → 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< T> - Returns a readonly signal
-
recompute(
) → void - Call the computed function and update the value
-
select<
R> (R selector(ReadonlySignal< T> ), {bool autoDispose = false, String? debugLabel}) → Computed<R> -
Available on ReadonlySignal<
Select a value and return a computed signal to listen for changesT> , provided by the ReadonlySignalUtils extension -
subscribe(
void fn(T value)) → EffectCleanup -
Subscribe to value changes
inherited
-
toJson(
) → dynamic -
Convert value to JSON
inherited
-
toStream(
) → Stream< T> -
Available on ReadonlySignal<
Convert a signal to a Stream to be consumed as a read only stream.T> , provided by the ReadonlySignalUtils extension -
toString(
) → String -
A string representation of this object.
inherited
-
toValueListenable(
) → SignalValueListenable< T, ValueListenable< T> , ReadonlySignal<T> > -
Available on ReadonlySignal<
Convert a signal to ValueListenable to be used in builders and other existing widgets like ValueListenableBuilderT> , provided by the FlutterReadonlySignalUtils extension -
unlisten(
BuildContext context, void callback()) → void -
Available on ReadonlySignal<
Stop subscriptions to updates on a signal for listenersT> , provided by the FlutterReadonlySignalUtils extension -
unwatch(
BuildContext context) → void -
Available on ReadonlySignal<
Stop subscriptions to updates on a signal for watchersT> , provided by the FlutterReadonlySignalUtils extension -
watch(
BuildContext context, {String? debugLabel}) → T -
Available on ReadonlySignal<
Rebuild the Element that the current signal is inside ofT> , provided by the FlutterReadonlySignalUtils extension
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited