Series 4 — Part 1 of 1
Flutter BLoC: Building a Feature with Events, States, and Cubit
A standalone deep-dive on the BLoC pattern in Flutter
After GetX, my next exploration was Flutter BLoC — the pattern that treats UI as a function of state, and models every user action as an explicit event. BLoC is more verbose than either Riverpod or GetX, but it is also the most explicit about intent.
I spent two days reading the official BLoC documentation, watching walkthroughs, and building a basic implementation with MVVM architecture.
The BLoC mental model
BLoC stands for Business Logic Component. The pattern works like this: the UI emits Events, the BLoC processes them and emits new States, and the UI rebuilds based on the current State. Nothing is hidden. Every transition is traceable.
The flutter_bloc package gives you two primitives:
- Cubit: The simpler primitive — it holds state and exposes methods to update it, without an explicit event hierarchy.
-
Bloc: The full version — you define an event class hierarchy, and an
on<Event>handler that emits new states.
Implementing a feature with BLoC
I built a product listing feature using Bloc. The events were LoadProducts and RefreshProducts. The states were ProductsInitial, ProductsLoading, ProductsLoaded, and ProductsError.
class ProductBloc extends Bloc<ProductEvent, ProductState> {
final ProductRepository _repo;
ProductBloc(this._repo) : super(ProductsInitial()) {
on<LoadProducts>((event, emit) async {
emit(ProductsLoading());
try {
final products = await _repo.fetchProducts();
emit(ProductsLoaded(products));
} catch (e) {
emit(ProductsError(e.toString()));
}
});
}
}
The UI uses BlocBuilder to rebuild on state changes:
BlocBuilder<ProductBloc, ProductState>(
builder: (context, state) {
if (state is ProductsLoading) return const CircularProgressIndicator();
if (state is ProductsLoaded) return ProductList(items: state.products);
if (state is ProductsError) return Text('Error: ${state.message}');
return const SizedBox.shrink();
},
)
The advantage is total clarity — you can read the bloc class and understand every possible state transition without reading any UI code.
BLoC with MVVM
I structured the app so that BLoCs acted as ViewModels and repositories remained the data layer. BLoC maps cleanly to MVVM — the Bloc is the ViewModel, the state is what the View renders, and events are the actions the View sends.
Fixing bugs in the implementation
-
Provider Scope: I hit a routing issue where the
BlocProviderwas not available in the subtree that needed it. The fix was to move theBlocProviderhigher in the widget tree, above the route that consumed it. -
Rebuild detection with Equatable: I had a state management issue where emitting a new state inside an event handler was not triggering a rebuild — this was caused by using the same state instance without
Equatableoverriding equality correctly.
What I learned
- Explicit transitions: BLoC is the most explicit of the three patterns — every state and every transition is named.
- Cubit as an on-ramp: Cubit is a simpler starting point. Upgrade to full Bloc when you need event history or complex transitions.
-
Widget tree placement:
BlocProvidermust be placed above the widget that consumes the Bloc in the tree. -
Equatable is essential:
Equatableis required forBlocBuilderto detect state changes correctly. - Team collaboration: BLoC is excellent for teams — the event/state contracts are self-documenting.
Top comments (0)