Essentials

Observing a Shard

Monitor and debug Shard state changes, errors, and lifecycle events.

Overview

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:

  • Debugging: Log all state transitions during development
  • Analytics: Track user interactions and state changes
  • Error Monitoring: Capture and report errors to crash reporting services
  • Testing: Verify state changes in tests

ShardObserver

ShardObserver is an abstract class that you can extend to receive notifications about Shard events.

Creating an Observer

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');
  }
}

Setting the Global Observer

Set the observer via Shard.observer:

void main() {
  // Set global observer (usually in main.dart)
  Shard.observer = LoggingObserver();
  
  runApp(MyApp());
}

Instance-Level Overrides

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);
}
Always call super.onChange() and super.onError() when overriding these methods to ensure the global observer receives notifications.

Error Reporting with addError

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));
  }
}

Next Steps

  • Learn about Async Values for handling async operations with FutureShard and StreamShard
  • Explore API Reference for complete API details