Skip to content

avoid_duplicate_bloc_event_handlers

v0.8.0WarningConfigurableBloc & Riverpod

This rule flags a bloc constructor that registers the same event type with on<E> more than once.

Bloc.on<E> asserts that each event type has exactly one handler. A second registration for the same type throws a StateError:

on<IncrementEvent> was called multiple times.

Because the registration happens in the constructor, the throw fires the first time the bloc is instantiated — often deep in a provider or a route builder, far from the duplicated line. Catching it at analysis time turns a runtime crash into a squiggle on the exact call.

The usual cause is a copy-pasted on<...> line where the type argument was not updated.

See also: bloc: Bloc.on

The usual cause: the line was duplicated to add the opposite behaviour and the type argument was never updated. It compiles; it throws the first time the bloc is constructed.

// Don't
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<IncrementEvent>((event, emit) => emit(state + 1));
on<IncrementEvent>((event, emit) => emit(state - 1)); // LINT
}
}
// Do
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<IncrementEvent>((event, emit) => emit(state + 1));
on<DecrementEvent>((event, emit) => emit(state - 1));
}
}

If two behaviours genuinely belong to one event, merge them into a single handler rather than registering twice.

If your blocs register through a helper that forwards to on, name it so the same crash is caught there:

many_lints.yaml
rules:
avoid_duplicate_bloc_event_handlers:
additional_methods: [onEvent]
// Don't
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
onEvent<IncrementEvent>((event, emit) => emit(state + 1));
onEvent<IncrementEvent>((event, emit) => emit(state - 1)); // LINT
}
void onEvent<E extends CounterEvent>(EventHandler<E, int> handler) =>
on<E>(handler);
}

Registrations are tracked per constructor. A bloc with two constructors that each register the same event is not flagged, because only one of them runs for any given instance.

Only an unqualified on<E>(...) or this.on<E>(...) with exactly one type argument is counted. A registration made outside the constructor body, or through a variable holding the handler, is not seen.

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

To turn it off:

many_lints.yaml
rules:
avoid_duplicate_bloc_event_handlers: false

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

analysis_options.yaml
many_lints:
rules:
avoid_duplicate_bloc_event_handlers:
additional_methods: [onEvent]
Option Type Default Description
additional_methods list of strings [] Extra registration methods treated like Bloc’s on, for a project wrapper that forwards to it