avoid_bloc_public_methods
v0.4.0 Warning Bloc / Riverpod
This rule flags public methods, getters, and setters declared on a Bloc. Cubits are exempt — a Cubit’s public methods are its API.
Not reported: private members, static members, and anything marked @override.
Why use this rule
Section titled “Why use this rule”A Bloc’s whole point is that every state change enters through add(event), where it can be logged, replayed and observed by BlocObserver. A public method is a second, untracked entrance: it changes state without producing an event, so the transition never appears in the observer log and cannot be reproduced from a recorded event sequence.
If the class genuinely wants a method-shaped API, it wants to be a Cubit — that is exactly the difference between the two.
See also: Bloc best practices | When to use Cubit vs Bloc
Examples
Section titled “Examples”A convenience method that bypasses the event log
Section titled “A convenience method that bypasses the event log”The method works, but BlocObserver.onEvent never fires for it, so the transition is invisible to logging and to replay:
// Don'timport 'package:bloc/bloc.dart';
sealed class CounterEvent {}
class Increment extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> { CounterBloc() : super(0) { on<Increment>((event, emit) => emit(state + 1)); }
// LINT — changes state without an event void increment() => emit(state + 1);}Add the event instead. The caller writes one more character and gets the whole observer pipeline:
// Doimport 'package:bloc/bloc.dart';
sealed class CounterEvent {}
class Increment extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> { CounterBloc() : super(0) { on<Increment>((event, emit) => emit(state + 1)); }}
// At the call site: counterBloc.add(Increment());A getter that duplicates the state
Section titled “A getter that duplicates the state”isEmpty reads fine, but a widget calling it is not subscribed to anything, so it never rebuilds when the answer changes:
// Don'timport 'package:bloc/bloc.dart';
sealed class CartEvent {}
class CartBloc extends Bloc<CartEvent, List<String>> { CartBloc() : super(const []);
bool get isEmpty => state.isEmpty; // LINT}// Do — derive it where the state is already being watchedimport 'package:bloc/bloc.dart';
sealed class CartEvent {}
class CartBloc extends Bloc<CartEvent, List<String>> { CartBloc() : super(const []);}
// In the widget:// final isEmpty = context.watch<CartBloc>().state.isEmpty;Cubits are not reported
Section titled “Cubits are not reported”The same members on a Cubit are exactly right — that is what a Cubit is for:
// Not reportedimport 'package:bloc/bloc.dart';
class CounterCubit extends Cubit<int> { CounterCubit() : super(0);
void increment() => emit(state + 1);
void reset() => emit(0);}Overrides, privates and statics are allowed
Section titled “Overrides, privates and statics are allowed”import 'package:bloc/bloc.dart';
sealed class CounterEvent {}
class Increment extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> { CounterBloc() : super(0) { on<Increment>(_onIncrement); }
// Private: fine void _onIncrement(Increment event, Emitter<int> emit) => emit(state + 1);
// Override: fine @override void onChange(Change<int> change) => super.onChange(change);
// Static: fine static CounterEvent increment() => Increment();}Configuration
Section titled “Configuration”This rule is in the opinionated preset, so it is on with
preset: opinionated, or by name:
rules: avoid_bloc_public_methods: trueTo turn it off again:
rules: avoid_bloc_public_methods: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Related rules
Section titled “Related rules”avoid_passing_bloc_to_bloc— Prevent Bloc/Cubit classes from depending on other Bloc/Cubit instances.prefer_bloc_extensions— Use context.read/watch instead of BlocProvider.of or RepositoryProvider.of.avoid_duplicate_bloc_event_handlers— Register each bloc event type exactly once.avoid_public_notifier_properties— Prevent public fields, getters, and setters on Notifier classes.