Once you've defined your blocs and provided them using BlocProvider, you need to connect them to your UI. The flutter_bloc package offers three main widgets for this: BlocBuilder, BlocListener, and BlocConsumer. This guide explains each one, when to use them, and how to get the most out of them.
BlocBuilder: Rebuilding on State Changes
BlocBuilder is a widget that rebuilds whenever the bloc's state changes. It takes a builder function that receives the current state and returns a widget. It's the go‑to widget for displaying state‑dependent content.
You can omit the bloc type if it can be inferred from the context:
BlocBuilder with buildWhen
Sometimes you want to rebuild only on certain state changes. The buildWhen parameter lets you control exactly when the builder should be called. It receives the previous and current state and returns a boolean. This is a powerful performance optimisation.
BlocListener: Side Effects Without Rebuilding
BlocListener is used for side effects like navigation, showing snackbars, or logging. It does not rebuild the UI. It runs a listener function whenever the state changes, but the child widget remains untouched. This prevents accidental rebuilds and infinite loops.
Like BlocBuilder, BlocListener also supports a listenWhen parameter to trigger the listener only on specific state changes.
BlocConsumer: Combining Builder and Listener
BlocConsumer is a convenience widget that combines BlocBuilder and BlocListener in one. It takes both a builder and a listener. Use it when you need both rebuild and side effect handling for the same bloc.
Both listenWhen and buildWhen can be used with BlocConsumer to fine‑tune when each part triggers.
When to Use Each Widget
| Widget | Use Case | Rebuilds | Side Effects |
|---|---|---|---|
| BlocBuilder | Displaying state-dependent UI | Yes | No |
| BlocListener | Navigation, snackbars, logging | No | Yes |
| BlocConsumer | Both UI and side effects from same bloc | Yes | Yes |
Performance Tips
- Use
buildWhento filter unnecessary rebuilds – Especially for complex UIs, this is critical. - Place
BlocBuilderas low as possible – Rebuild only the part that actually changes, not the whole screen. - Use
BlocListenerfor side effects – Avoid putting navigation code insideBlocBuilder, as it could cause multiple calls. - Use
context.readfor event dispatching – No need to useBlocBuilderjust to access the bloc. - Consider
BlocSelectorfor very granular rebuilds – It selects a piece of state and rebuilds only when that piece changes.
Common Mistakes
- ❌ Placing
BlocBuilderat the top of the screen – Rebuilds the entire screen on every state change, hurting performance. ✅ PlaceBlocBuilderas close to the UI that depends on the state as possible. - ❌ Calling
BlocListenerwithout a child – Thechildis required (except when using it as a top‑level wrapper). ✅ Always provide achild, even if it'sconst SizedBox.shrink(). - ❌ Using
BlocConsumerwhen only one of builder/listener is needed – Adds unnecessary complexity. ✅ Use separateBlocBuilderandBlocListenerwhen only one is needed. - ❌ Forgetting to handle loading/error states – UI may break or show stale data. ✅ Always define and handle all possible states.
BlocSelector: Even More Granular Rebuilds
For advanced performance, BlocSelector allows you to rebuild only when a specific part of the state changes. It selects a value from the state and rebuilds only when that value changes.
This is even more efficient than BlocBuilder with buildWhen because it doesn't require comparing the entire state.
Putting It All Together: Example
Conclusion
BlocBuilder, BlocListener, and BlocConsumer are the essential widgets for connecting blocs to your Flutter UI. By understanding their roles and using them wisely, you can build reactive, efficient, and maintainable applications. Remember to place builders low, filter rebuilds with buildWhen, and use listeners for side effects.