Async Signal
Async signals are used in the reactive system to handle async operations, including Futures and Streams. Jolt provides AsyncSignal to manage async operation states (loading, success, error, etc.) and automatically notify subscribers when states change. Suitable for data loading, real-time data streams, and similar scenarios.
import 'package:jolt/jolt.dart';
void main() {
final userSignal = AsyncSignal.fromFuture(fetchUser());
Effect(() {
final state = userSignal.value;
if (state.isLoading) {
print('Loading...');
} else if (state.isSuccess) {
print('User: ${state.data}');
} else if (state.isError) {
print('Error: ${state.error}');
}
});
}AsyncState
AsyncState is a sealed class representing different states of async operations:
AsyncLoading<T>: Loading stateAsyncSuccess<T>: Success state, contains dataAsyncError<T>: Error state, contains error information
State Checking
final state = asyncSignal.value;
if (state.isLoading) {
print('Loading...');
} else if (state.isSuccess) {
print('Success: ${state.data}');
} else if (state.isError) {
print('Error: ${state.error}');
}Data Access
final state = asyncSignal.value;
// Get data (may be null)
final data = state.data;
// Get error (may be null)
final error = state.error;
final stackTrace = state.stackTrace;map Method
Use the map method to return different values based on state:
final message = state.map(
loading: () => 'Loading...',
success: (data) => 'Success: $data',
error: (error, stackTrace) => 'Error: $error',
);Creating AsyncSignal
From Future
Use AsyncSignal.fromFuture to create from a Future:
Future<String> fetchUser() async {
await Future.delayed(Duration(seconds: 1));
return 'John Doe';
}
final userSignal = AsyncSignal.fromFuture(fetchUser());From Stream
Use AsyncSignal.fromStream to create from a Stream:
final stream = Stream.periodic(Duration(seconds: 1), (i) => i);
final dataSignal = AsyncSignal.fromStream(stream);Using AsyncSource
You can create using a custom AsyncSource:
final signal = AsyncSignal(
source: FutureSource(future),
initialValue: AsyncLoading(),
);Basic Usage
State Checking
AsyncSignal's value is AsyncState with three states:
final asyncSignal = AsyncSignal.fromFuture(fetchUser());
Effect(() {
final state = asyncSignal.value;
if (state.isLoading) {
print('Loading...');
} else if (state.isSuccess) {
print('Success: ${state.data}');
} else if (state.isError) {
print('Error: ${state.error}');
}
});Direct Data Access
AsyncSignal provides a data property for direct data access (may be null):
final signal = AsyncSignal.fromFuture(fetchUser());
// Direct access to data (may be null)
final user = signal.data;Reloading Data
To reload data, you can create a new AsyncSignal or use a new AsyncSource:
final signal = AsyncSignal.fromFuture(fetchUser());
// Method 1: Create new AsyncSignal
final newSignal = AsyncSignal.fromFuture(fetchUser());
// Method 2: Use new AsyncSource
final newSource = FutureSource(fetchUser());
final reloadedSignal = AsyncSignal(source: newSource);AsyncSource
AsyncSource is an abstract interface for defining async data sources. You can implement custom AsyncSource to create special async behaviors.
Implementing Custom AsyncSource
class MyAsyncSource<T> implements AsyncSource<T> {
@override
FutureOr<void> subscribe(void Function(AsyncState<T> state) emit) async {
emit(AsyncLoading());
try {
final data = await fetchData();
emit(AsyncSuccess(data));
} catch (e, st) {
emit(AsyncError(e, st));
}
}
@override
FutureOr<void> dispose() {
// Clean up resources
}
}
// Use custom source
final signal = AsyncSignal(source: MyAsyncSource());FutureSource
FutureSource is a wrapper for Futures, automatically managing Future state transitions:
final future = Future.delayed(Duration(seconds: 1), () => 'Hello');
final source = FutureSource(future);
final signal = AsyncSignal(source: source);StreamSource
StreamSource is a wrapper for Streams, automatically managing Stream state transitions:
final stream = Stream.periodic(Duration(seconds: 1), (i) => i);
final source = StreamSource(stream);
final signal = AsyncSignal(source: source);Use Cases
Data Loading
AsyncSignal is perfect for data loading scenarios:
class UserService {
Future<User> fetchUser(int id) async {
await Future.delayed(Duration(seconds: 1));
return User(id: id, name: 'User $id');
}
}
final userService = UserService();
final userSignal = AsyncSignal.fromFuture(
userService.fetchUser(1)
);
Effect(() {
final state = userSignal.value;
if (state.isLoading) {
showLoadingIndicator();
} else if (state.isSuccess) {
displayUser(state.data!);
} else if (state.isError) {
showError(state.error);
}
});Real-Time Data Streams
AsyncSignal can be used to handle real-time data streams:
final chatMessages = AsyncSignal.fromStream(
chatService.messageStream()
);
Effect(() {
final state = chatMessages.value;
if (state.isSuccess) {
displayMessage(state.data!);
}
});Error Handling
AsyncSignal provides complete error handling capabilities:
final dataSignal = AsyncSignal.fromFuture(fetchData());
Effect(() {
final state = dataSignal.value;
if (state.isError) {
print('Error: ${state.error}');
print('Stack trace: ${state.stackTrace}');
// Handle error
handleError(state.error, state.stackTrace);
}
});Reloading
Using the fetch method enables reload functionality:
final dataSignal = AsyncSignal.fromFuture(fetchData());
void reload() {
dataSignal.fetch(FutureSource(fetchData()));
}
// User clicks refresh button
refreshButton.onTap = reload;Lifecycle Management
AsyncSignal implements the Signal interface and has lifecycle management capabilities:
dispose(): Release resources, including canceling ongoing async operationsisDisposed: Check if disposed
final signal = AsyncSignal.fromFuture(fetchData());
// Use signal...
// Release when no longer needed
signal.dispose();Important Notes
State Transitions:
AsyncSignalautomatically manages state transitions fromAsyncLoadingtoAsyncSuccessorAsyncError.Data Access: The
dataproperty returnsnullinAsyncLoadingandAsyncErrorstates, only returning actual data inAsyncSuccessstate.Error Handling: Ensure proper handling of error states, including error information and stack traces.
Resource Cleanup:
AsyncSignalautomatically cleans upAsyncSourceresources, but if you implement customAsyncSource, ensure proper implementation of thedispose()method.Stream Subscriptions: For
StreamSource,AsyncSignalautomatically manages Stream subscription lifecycles.Reloading: When using the
fetchmethod to reload data, previous async operations are automatically canceled.
Related APIs
- Signal - Learn about basic signal usage
- Effect - Reactive side effects
- Extensions - Async signal extension methods