Shard provides an observer pattern that allows you to monitor state changes, errors, and lifecycle events across all Shard instances in your application. This is useful for:
ShardObserver is an abstract class that you can extend to receive notifications about Shard events.
class LoggingObserver extends ShardObserver {
@override
void onChange<T>(Shard<T> shard, T previousState, T currentState) {
print('${shard.runtimeType}: $previousState → $currentState');
}
@override
void onError<T>(Shard<T> shard, Object error, StackTrace? stackTrace) {
print('${shard.runtimeType} error: $error');
}
}
Set the observer via Shard.observer:
void main() {
// Set global observer (usually in main.dart)
Shard.observer = LoggingObserver();
runApp(MyApp());
}
You can also override observer methods directly in your Shard subclass for instance-specific handling:
class CounterShard extends Shard<int> {
CounterShard() : super(0);
@override
void onChange(int previousState, int currentState) {
// Local handling
print('Counter changed: $previousState → $currentState');
// IMPORTANT: Call super to notify global observer
super.onChange(previousState, currentState);
}
@override
void onError(Object error, StackTrace? stackTrace) {
// Local error handling
print('Counter error: $error');
// IMPORTANT: Call super to notify global observer
super.onError(error, stackTrace);
}
@override
void dispose() {
// Cleanup resources if needed
print('Counter disposed');
super.dispose();
}
void increment() => emit(state + 1);
}
super.onChange() and super.onError() when overriding these methods to ensure the global observer receives notifications.The addError method allows you to report errors without blocking the execution flow. This is useful for non-fatal errors that should be logged but shouldn't stop the operation:
class DataShard extends Shard<DataState> {
DataShard() : super(DataState.initial());
Future<void> fetchData() async {
emit(state.copyWith(isLoading: true));
try {
final data = await api.fetchData();
emit(state.copyWith(isLoading: false, data: data));
} catch (e, st) {
// Report error to observer (non-blocking)
addError(e, st);
// Continue with fallback behavior
emit(state.copyWith(isLoading: false, error: e.toString()));
}
}
void riskyOperation() {
// Report error but continue execution
addError(Exception('Something went wrong'), StackTrace.current);
// This still executes
emit(state.copyWith(warningShown: true));
}
}