In the bloc package, what is a BlocObserver for, and how do you register one so it sees every Bloc and Cubit?
answer
- one place for every instance
- subclass and call super
- static Bloc.observer in main
- captured when each bloc is built
- MultiBlocObserver since 9.2
basics
~20 sA BlocObserver receives lifecycle callbacks - creation, events, changes, transitions, errors, handler completion and closing - from every Bloc and Cubit in one place. Subclass it, call super in each override, and assign it to Bloc.observer in main before any bloc exists.
solid answer
~40 s`BlocObserver` is the bloc package's global hook: its `onCreate`, `onEvent`, `onChange`, `onTransition`, `onError`, `onDone` and `onClose` callbacks fire for every `Bloc` and `Cubit` in the app, which makes it the natural place for debug logging or forwarding errors to a reporting service. You subclass it, override the callbacks you need (each is `@mustCallSuper`), and assign an instance to the static `Bloc.observer`, usually on the first lines of `main`. Timing matters: each bloc copies `Bloc.observer` into a field when it is constructed, so blocs created before the assignment keep reporting to the previous observer. To combine several observers, bloc 9.2 added `MultiBlocObserver(observers: [...])`; the zone-based `BlocOverrides` API was removed in bloc 9.0.
code
dart · 24 linesimport 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class AppBlocObserver extends BlocObserver {
const AppBlocObserver();
@override
void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) {
super.onChange(bloc, change);
debugPrint('${bloc.runtimeType} $change');
}
@override
void onError(BlocBase<dynamic> bloc, Object error, StackTrace stackTrace) {
debugPrint('${bloc.runtimeType} $error');
super.onError(bloc, error, stackTrace);
}
}
void main() {
Bloc.observer = const AppBlocObserver();
runApp(const ReaderApp());
}go deeper
Remember that BlocObserver is a single class that sees every bloc and cubit, and that you assign it to Bloc.observer at the start of main.
Explain that each instance captures the observer at construction, list which callbacks a Cubit never triggers, and show MultiBlocObserver for several concerns.
Keep observers cheap, split logging from error forwarding, and make sure tests install their observer before any bloc is built.
Decide what the global observer is allowed to do across teams - logging, error forwarding, lifecycle metrics - and what must stay out of it for performance and privacy.
## What a BlocObserver is In the **bloc** package (version 9 at the time of writing, re-exported by `flutter_bloc` and `hydrated_bloc`), every `Bloc` and every `Cubit` reports its lifecycle to one shared object: a **`BlocObserver`**. It is an abstract class with empty default implementations, so you override only what you need: | Callback | Fires when | Bloc | Cubit | |---|---|---|---| | `onCreate(bloc)` | an instance is constructed | yes | yes | | `onEvent(bloc, event)` | `add(event)` is called | yes | no | | `onChange(bloc, change)` | a new state is emitted | yes | yes | | `onTransition(bloc, transition)` | a handler emits in response to an event | yes | no | | `onError(bloc, error, stackTrace)` | an error is reported through `onError` | yes | yes | | `onDone(bloc, event, [error, stackTrace])` | an event handler finishes | yes | no | | `onClose(bloc)` | `close()` is called | yes | yes | Each callback is marked `@protected` and `@mustCallSuper`, so an override should call `super.onX(...)`. ## Registering it The registration point is a static field on `Bloc`: 1. Write a subclass - for example `AppBlocObserver extends BlocObserver`. 2. In `main`, before `runApp` and before anything creates a bloc, assign `Bloc.observer = const AppBlocObserver();`. 3. Every `Bloc` and `Cubit` constructed afterwards reports to it, including a `HydratedCubit` such as a reading-progress cubit. The detail interviewers probe is **when** the observer is read. In the bloc source, `BlocBase` stores `Bloc.observer` in a field as the instance is created, and all later callbacks go to that stored observer. So: - a bloc created before the assignment never reports to the new observer; - replacing `Bloc.observer` mid-session only affects blocs created afterwards; - tests that swap the observer temporarily must do so before building the bloc. ## More than one observer Before bloc 9.2 you had to write one observer that did everything. Since 9.2, **`MultiBlocObserver`** takes a list and forwards every callback to each observer in the order given: ```dart Bloc.observer = MultiBlocObserver( observers: [ const LoggingObserver(), const ErrorReportingObserver(), ], ); ``` This keeps concerns apart: a logging observer can be debug-only, while an error-forwarding one runs in every build. ## What it is good for - **Debug logging** of every change or transition without touching each bloc. - **Error forwarding** from `onError` to whatever crash-reporting setup the app uses. - **Lifecycle diagnostics**: `onCreate` shows when a lazily created cubit really appears, `onClose` shows whether it was disposed. Callbacks run synchronously on every change, so keep them cheap: format a log line, do not serialise large states or perform I/O inline. ## History worth knowing - bloc 8.0 replaced the static `Bloc.observer` with the zone-based `BlocOverrides.runZoned`; bloc 8.1 reintroduced `Bloc.observer` and deprecated `BlocOverrides`; bloc 9.0 removed `BlocOverrides` entirely. - bloc 9.1 added `onDone`. - bloc 9.2 added `MultiBlocObserver`. An answer that registers observers with `BlocOverrides` describes code that no longer compiles against bloc 9.
- Why might a bloc created in a test never call your test observer?Each bloc stores `Bloc.observer` in a field when it is constructed. If the test assigns its observer after building the bloc, that instance keeps reporting to the observer that was active at construction. Assign the observer first, then build the bloc.
- Should a BlocObserver's onChange serialise the whole state for logging?Usually not. Callbacks run synchronously on every emission, so heavy work there slows every state change in the app. Log a compact description in debug builds, and keep production observers to cheap work such as forwarding errors.
A BlocObserver is like the control room for a building's cameras: each camera is wired to whichever room was active on the day it was installed. Open a new control room later and the old cameras keep feeding the old one.
saying these in an interview costs you the question
- Registers observers with BlocOverrides.runZoned in a bloc 9 codebase.
- Sets Bloc.observer inside a widget after the blocs were created.
- Believes assigning Bloc.observer twice adds a second observer.
- Overrides observer callbacks without calling super.
- Does network I/O synchronously inside onChange for every state.