EventTransformer and observability (BlocObserver)
The official docs call Bloc's second advantage over Cubit advanced event transformations: the ability to use reactive operators such as buffer, debounce and throttle.
The mechanism: you pass a transformer parameter to on<Event>. An EventTransformer<T> changes how the incoming event stream is processed by the bloc.
Ready-made transformers live in package:bloc_concurrency:
- `concurrent` — processes events concurrently.
- `sequential` — processes events one after another.
- `droppable` — ignores events added while another event is being processed.
- `restartable` — processes only the latest event and cancels previous handlers.
| Scenario | Transformer | Reason |
|---|---|---|
| Typing in a search field | `restartable` (plus debounce) | Only the latest query's result matters; older requests are cancelled |
| Double-tapping a "Pay" button | `droppable` | The second request is dropped — no duplicate transaction |
| Pagination: "load more" | `droppable` | Prevents duplicate requests while scrolling |
| Sending analytics events | `sequential` | Order matters and no event may be lost |
| Independent loads | `concurrent` | Running in parallel is fastest |
import 'package:bloc_concurrency/bloc_concurrency.dart';
import 'package:stream_transform/stream_transform.dart';
EventTransformer<E> debounceRestartable<E>(Duration duration) {
return (events, mapper) =>
restartable<E>().call(events.debounce(duration), mapper);
}
class SearchBloc extends Bloc<SearchEvent, SearchState> {
SearchBloc(this._repository) : super(const SearchState()) {
on<SearchQueryChanged>(
_onQueryChanged,
// Sürətli yazılışda yalnız son sorğu işlənir, əvvəlkilər ləğv olunur.
transformer: debounceRestartable(const Duration(milliseconds: 300)),
);
}
final SearchRepository _repository;
Future<void> _onQueryChanged(
SearchQueryChanged event,
Emitter<SearchState> emit,
) async {
if (event.query.isEmpty) {
emit(const SearchState());
return;
}
emit(state.copyWith(status: SearchStatus.loading));
final results = await _repository.search(event.query);
emit(state.copyWith(status: SearchStatus.success, results: results));
}
}Search: debounce plus restartable.
Observability: BlocObserver. To watch the lifecycle of every bloc and cubit from one place, you override BlocObserver:
onCreate— a bloc was createdonEvent— an event was added to a bloconChange— the state changed (cubits too)onTransition— the state → event → state transition; per the docs aTransitionconsists of the current state, the event and the next state, andonTransitionis invoked beforeonChangeonError— an error occurredonClose— the bloc was closed
Registration: Bloc.observer = MyObserver();, or Bloc.observer = MultiBlocObserver(observers: [...]); to combine several.
This is the concrete proof of BLoC's traceability advantage in an interview: the log shows which event caused which state transition — information a Cubit cannot provide, because there is no event object.
class AppBlocObserver extends BlocObserver {
const AppBlocObserver();
@override
void onEvent(Bloc bloc, Object? event) {
super.onEvent(bloc, event);
// Audit tələb olunan axınlarda event jurnalı buradan yazılır.
log('${bloc.runtimeType} ← $event');
}
@override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
// Transition: cari state + event + növbəti state.
log('${bloc.runtimeType} ${transition.currentState.runtimeType} '
'--${transition.event.runtimeType}--> ${transition.nextState.runtimeType}');
}
@override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
// Crash reporting servisinə göndərmək üçün mərkəzi nöqtə.
reportError(error, stackTrace, context: bloc.runtimeType.toString());
super.onError(bloc, error, stackTrace);
}
}
void main() {
Bloc.observer = const AppBlocObserver();
runApp(const MyApp());
}An observer: logging and crash reporting in one place.
Do not leave the transformer choice to assumption: in critical flows (payments, order creation, pagination) write the transformer parameter explicitly and put it on the code-review checklist. Bugs of the "tapped twice → two orders" kind come precisely from behaviour that was silently assumed.
📚 Sources and documentation
- package:bloc_concurrencyofficialpub.dev
The official description of the concurrent, sequential, droppable and restartable transformers.
- Bloc concepts: event transformations and the observerofficialbloclibrary.dev
EventTransformer, Transition and the onTransition/onChange ordering are explained here.
- package:blocofficialpub.dev
The BlocObserver callbacks and MultiBlocObserver registration.
- package:hydrated_blocofficialpub.dev
When state must be persisted to disk and restored automatically.