LogoSignals.dart
Copy Markdown
rodydavis/signals.dart 999999

AsyncSignal

A highly powerful Signal specifically designed for manual, imperative asynchronous state management.

A highly powerful Signal specifically designed for manual, imperative asynchronous state management.

Unlike declarative reactive signals like futureSignal or streamSignal (which automatically wrap and listen to an existing Future or Stream), AsyncSignal gives you full manual/imperative control over pushing async states (AsyncState.loading, AsyncState.data, and AsyncState.error) into the reactive graph.

This is the perfect state primitive for building custom repositories, handling manual user action triggers (e.g., submitting a registration form, calling an API on button click), or bridging low-level callback-based APIs into reactive states.

1. Imperative State Mutations#

You can update the state of the signal directly using specialized mutation helpers:

  • setLoading() puts the signal into a clean AsyncLoading state.
  • setValue(T data) pushes a new AsyncData state containing the data.
  • setError(Object error, [StackTrace? stackTrace]) transitions the signal to an AsyncError state.
final authState = asyncSignal<User>(AsyncState.loading());

Future<void> login(String email, String password) async {
  try {
    authState.setLoading(); // Set UI to loading state
    final user = await authApi.signIn(email, password);
    authState.setValue(user); // Push success data
  } catch (err, stack) {
    authState.setError(err, stack); // Push error state
  }
}

2. Awaiting Async Completion via .future#

An outstanding capability of AsyncSignal is its built-in .future getter. Any part of your code can await this future. It returns a standard Future that resolves when the signal next receives a data value, or throws if the signal next receives an error state.

final loginSignal = asyncSignal<User>(AsyncState.loading());

// Task A: Start background operation
Future.delayed(Duration(seconds: 2), () {
  loginSignal.setValue(User(name: 'Charlie'));
});

// Task B: Wait for the signal to resolve!
final user = await loginSignal.future; // Suspends execution until Task A completes!
print(user.name); // 'Charlie'

3. Rendering in Flutter using Watch and AsyncState Pattern matching#

In your Flutter widgets, you can seamlessly watch the signal and use Dart's native pattern matching on AsyncState to render different widgets corresponding to the current asynchronous lifecycle:

Widget build(BuildContext context) {
  final state = authState.watch(context);

  return state.map(
    data: (user) => HomeScreen(user: user),
    error: (error, stackTrace) => ErrorWidget(error),
    loading: () => const CircularProgressIndicator(),
  );
}

4. Bridging callback/event-driven systems via EventSink#

AsyncSignal implements Dart's standard EventSink interface. This allows it to act directly as an event sink for streams, websockets, or callback listeners:

final messageLog = asyncSignal<String>(AsyncState.loading());
final chatStream = webSocket.stream.map((event) => event.toString());

// Automatically push all incoming messages and errors from the stream into the signal:
chatStream.listen(
  (msg) => messageLog.add(msg),
  onError: (err) => messageLog.addError(err),
  onDone: () => messageLog.close(),
);
Favor AsyncSignal when you need manual, callback-driven, or button-press-triggered state mutations. For auto-triggering, declarative, or read-only asynchronous data dependencies (like pulling data when an ID changes), favor [futureSignal](/packages/signals/async/future) or [computedAsync](/packages/signals/async/computed) instead.

Constructors#

View Constructors
AsyncSignal(super.value, {super.options})

A Signal that stores value in AsyncState

Properties#

View Properties
Completer<T> completer

Internal Completer for values

Methods#

View Methods
Future<T> future

The future of the signal completer

bool isCompleted

Returns true if the signal is completed an error or data

void setError(Object error, [StackTrace? stackTrace])

Set the error with optional stackTrace to AsyncError

void setValue(T value)

Set the value to AsyncData

void setLoading([AsyncState<T>? state])

Set the loading state to AsyncLoading

void reset([AsyncState<T>? value])

Reset the signal to the initial value

void init()

Initialize the signal

Future<void> reload()

Reload the future

Future<void> refresh()

Refresh the future

AsyncState<T> value
T requireValue

Returns the value of the signal


asyncSignal#

Helper function to create an AsyncSignal initialized with an AsyncState.

Example#

// Create an AsyncSignal initialized to a loading state
final counter = asyncSignal<int>(AsyncState.loading());

// Create an AsyncSignal initialized with initial data
final status = asyncSignal<String>(AsyncState.data('Active'));

AsyncSignalOptions#

Configuration options for an AsyncSignal.

Constructors#

View Constructors
AsyncSignalOptions({this.initialValue, this.dependencies = const [], this.onDone, this.cancelOnError, this.lazy = true, super.name, super.autoDispose, super.watched, super.unwatched})

Creates a new AsyncSignalOptions instance.

Properties#

View Properties
T? initialValue

The initial value of the async signal.

List<ReadonlySignal<dynamic>> dependencies

The list of dependencies to watch/listen to.

void Function()? onDone

Optional function called when a stream completes.

bool? cancelOnError

Whether to cancel the stream subscription on error.

bool lazy

Whether the execution is lazy.

Methods#

View Methods
AsyncSignalOptions<T> copyWith({T? initialValue, List<ReadonlySignal<dynamic>>? dependencies, void Function()? onDone, bool? cancelOnError, bool? lazy, bool? autoDispose, String? name, void Function()? watched, void Function()? unwatched})

Creates a copy of this options with custom overrides.

bool ==(Object other)
int hashCode