Skip to content

handle_bloc_event_subclasses

v1.0.0 Warning Bloc / Riverpod

This rule flags a Bloc whose sealed event hierarchy has a subclass with no on<E> handler. Adding an unregistered event does nothing at all at runtime.

on<E> registration is a runtime lookup keyed by type. If no handler is registered for an event, add(MyEvent()) returns normally, emits no state, throws nothing and logs nothing. The feature simply does not work.

The gap almost always appears later: a new event class joins the hierarchy and the matching on<E> is forgotten. Because the compiler cannot see the omission, only a test that exercises that exact event will catch it — and the missing event is usually the one no test covers yet.

See also: bloc: Bloc.on

sealed class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<Increment>((event, emit) => emit(state + 1));
// `Decrement` is never handled — adding it does nothing
}
}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<Increment>((event, emit) => emit(state + 1));
on<Decrement>((event, emit) => emit(state - 1));
}
}

A handler for the base type covers every subclass:

on<CounterEvent>((event, emit) => emit(state));

Only sealed event hierarchies are checked. A sealed type may only be extended within its own library, so its subtypes are knowable. For an open base class a subtype may live in any library, so “every subtype” cannot be computed and a report would be guesswork — make the event base sealed to get this check, which is good practice regardless.

Handlers are found by scanning the class for on<E>(...) calls, so a registration made outside the Bloc, or through a helper, is not detected.

This rule is in the recommended preset, so it is on with preset: recommended or preset: opinionated. Add it to preset: core with handle_bloc_event_subclasses: true.

To turn it off:

many_lints.yaml
rules:
handle_bloc_event_subclasses: false

To keep the rule on but skip certain paths, use per-rule exclude.