BlocObserver: Debug and Observe your Bloc Easily
Learn how to observe a bloc by simply overriding `onChange`, `onTransition`, `onEvent`, `onError` methods.

Search for a command to run...
Learn how to observe a bloc by simply overriding `onChange`, `onTransition`, `onEvent`, `onError` methods.

You should update the way you set up the global observer. Now runZoned is obsolate. Instead you should use Bloc.observer = MyGlobalObserver()
Lessons from Code, Content, and Consistency

Reliability basics: Outbox pattern + why it matters

Introducing Kafka/Redpanda + move to event-driven workflow

Connect Orders โ Inventory (first working service-to-service flow)

Inventory service + gRPC + proto contracts

BlocObserver is a abstract class for monitoring BloC instances behavior. This means we can track anytime an event is triggered or a state is emitted. And in the instance of Debugging the BloC, I believe this is incredible.
onChange()You must override the onChange method inside your bloc in order to see the change when a new state is emitted.
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
// Your events here
on<ExampleEvent>(....)
@override
void onChange(Change<ExampleState> change) {
super.onChange(change);
debugPrint(change.toString());
debugPrint(change.currentState.toString());
debugPrint(change.nextState.toString());
}
}
ChangeChange represents the change from one State to another. Change consists of the currentState and nextState.Change occurs, which is why onChange is a great spot to add logging/analytics for a specific cubit.One thing to remember here is you need to always call
super.onChangefirst before performing any operation.
onTransitionclass ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
// Your events here
on<ExampleEvent>(....)
@override
void onTransition(Transition<JokeEvent, JokeState> transition) {
super.onTransition(transition);
debugPrint(transition.toString());
}
}
super.onTransitionshould always be called first.
onErroronError function within your bloc.onError is called whenever an error occurs and notifies BlocObserver.onError.
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
// Your events here
on<ExampleEvent>(....)
@override
void onError(Object error, StackTrace stackTrace) {
super.onError(error, stackTrace);
debugPrint(error.toString());
}
}
super.onErrorshould always be called first.
onEventclass ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
// Your events here
on<ExampleEvent>(....)
@override
void onEvent(JokeEvent event) {
super.onEvent(event);
debugPrint(event.toString());
}
}
class JokeBloc extends Bloc<JokeEvent, JokeState> {
final JokeRepository _jokeRepository;
JokeBloc(this._jokeRepository) : super(JokeLoadingState()) {
on<LoadJokeEvent>(
(event, emit) async {
emit(JokeLoadingState());
try {
final joke = await _jokeRepository.getJoke();
emit(JokeLoadedState(joke));
} catch (e) {
addError(Exception(e.toString()), StackTrace.current);
emit(JokeErrorState(e.toString()));
}
},
);
on<ExtraLoadJokeEvent>(
(event, emit) async {
emit(JokeLoadingState());
try {
await Future.delayed(const Duration(seconds: 3));
final joke = await _jokeRepository.getJoke();
emit(JokeLoadedState(joke));
} catch (e) {
addError(Exception(e.toString()), StackTrace.current);
emit(JokeErrorState(e.toString()));
}
},
);
}
@override
void onTransition(Transition<JokeEvent, JokeState> transition) {
super.onTransition(transition);
debugPrint(transition.toString());
}
@override
void onChange(Change<JokeState> change) {
super.onChange(change);
debugPrint(change.toString());
debugPrint(change.currentState.toString());
debugPrint(change.nextState.toString());
}
@override
void onError(Object error, StackTrace stackTrace) {
super.onError(error, stackTrace);
debugPrint(error.toString());
}
@override
void onEvent(JokeEvent event) {
super.onEvent(event);
debugPrint(event.toString());
}
}

LoadJokeEvent(). onEvent method will be triggered. Which you can confirm in the log.onTransition gets called. As I've said earlier, the onTransition will get called before the onChange. Which contains the currentState, triggered event and nextState.onTransition, the onChange method is called. When the joke data is loading the JokeLoadingState passes, after the successful fetch of the data, as you can see in the log we got the JokeLoadedState with the instance JokeModel.So the flow is :
onEvent>onTransition>onChange>onError

BlocObserver.That's it, now you can override all the methods here as we did in the above example.
class MyGlobalObserver extends BlocObserver {
@override
void onEvent(Bloc bloc, Object? event) {
super.onEvent(bloc, event);
debugPrint('${bloc.runtimeType} $event');
}
@override
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
debugPrint('${bloc.runtimeType} $change');
}
@override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
debugPrint('${bloc.runtimeType} $transition');
}
@override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
debugPrint('${bloc.runtimeType} $error $stackTrace');
super.onError(bloc, error, stackTrace);
}
}
main.dart file and wrap your runApp function around BlocOverrides.runZoned(). And then pass the blocObserver parameter.void main() {
BlocOverrides.runZoned(
() {
runApp(const MyApp());
},
blocObserver: MyGlobalObserver(),
);
}