arch-atlas

Bloc observer

Presentation (Integration)

03:39

Summary

This lesson demonstrates how to set up a BlocObserver to monitor and log state changes across all Bloc and Cubit instances in the application. While the broader goal of this section is to integrate the presentation layer with the user interface to display fetched data, the instructor prioritizes establishing visibility into state transitions first. The implementation involves creating a custom observer class, overriding the onChange method to log state updates, and registering this observer globally in the main application file.

Key Ideas

  • BlocObserver acts as a global listener for all Bloc and Cubit lifecycle events and state transitions.
  • Overriding the onChange method allows developers to log every state change globally, significantly simplifying debugging.
  • Methods like onCreate and onClose can also be overridden to track the lifecycle of a Cubit and identify potential memory leaks from unclosed instances.
  • The log function from dart:developer is preferred over standard print for outputting state changes to the console.
  • The global observer is registered by assigning the custom observer instance directly to Bloc.observer.

Code Snippets

Creating a simple BlocObserver to log state changes (simple_bloc_observer.dart)dart
import 'dart:developer';
import 'package:flutter_bloc/flutter_bloc.dart';

class SimpleBlocObserver extends BlocObserver {
  @override
  void onChange(BlocBase bloc, Change change) {
    super.onChange(bloc, change);
    log(change.toString());
  }
}
Registering the custom BlocObserver globally (main.dart)dart
void main() async {
  await Hive.initFlutter();
  Hive.registerAdapter(BookEntityAdapter());
  setupServiceLocator();
  await Hive.openBox<BookEntity>(kFeaturedBox);
  await Hive.openBox<BookEntity>(kNewestBox);
  Bloc.observer = SimpleBlocObserver();
  runApp(const Bookly());
}

Architecture Insight

Architecture insight

BlocObserver is the observability hook for the state layer — every state transition runs through it. That's the right place for analytics, structured logging, or crash-reporter breadcrumbs.

It's tempting to put business logic here ("when X transitions to Y, also do Z") but resist — the observer is for *observing*, not deciding. Cross-Cubit coordination belongs in a use case, not an observer hook.

My Notes

Setting up a BlocObserver early in the UI integration phase is an excellent practice. It instantly makes debugging easier by providing a clear, chronological log of all state emissions. The instructor correctly highlights that while onChange is the most common override for pure state logging, using onCreate and onClose is invaluable for tracing memory issues and ensuring transient Cubit instances are properly disposed of.

  • dart:developer vs dart:math: Be careful when auto-importing log(). The IDE might accidentally import dart:math instead of dart:developer, which will cause a type error since the math version expects a num.
  • Deprecation Warning: Older tutorials might use BlocOverrides.runZoned(). As shown in this lesson, simply setting Bloc.observer = MyObserver(); before runApp() is the modern approach.