BlocProvider is a Flutter widget that provides a bloc to its descendants. It handles the lifecycle of the bloc, automatically closing it when no longer needed. This guide explains how to use BlocProvider effectively, from basic setup to advanced scoping strategies.
What is BlocProvider?
BlocProvider is a dependency injection (DI) widget that makes a bloc instance available to all widgets in its subtree. It uses Flutter's InheritedWidget mechanism under the hood, ensuring that you can access the same bloc instance from anywhere below it.
Basic Usage
Wrap a part of your widget tree with BlocProvider and provide a bloc via the create parameter. The bloc will be created once when the provider is first inserted.
Inside the child widgets, access the bloc using context.read<CounterBloc>() or context.watch<CounterBloc>().
BlocProvider vs BlocProvider.value
There are two ways to create a BlocProvider:
BlocProvider(create: ...)– creates a new bloc instance and automatically closes it when the provider is removed.BlocProvider.value(...)– provides an existing bloc instance. Use this when the bloc is already created elsewhere (e.g., in a parent provider).
MultiBlocProvider
When you need to provide multiple blocs, use MultiBlocProvider to avoid nesting. It takes a list of providers and a single child.
Scoping Blocs
The placement of BlocProvider determines the scope of the bloc. Blocs are available only to descendants of the provider. This allows you to create isolated blocs for specific parts of your app.
When a widget needs a bloc that is not provided in its context, you'll get a ProviderNotFoundException. Ensure the bloc is provided at or above the widget that needs it.
Accessing Blocs
Gets the bloc without listening to state changes. Use this for adding events or calling methods.
Gets the bloc and listens to state changes. The widget will rebuild whenever the state changes. Use this in build methods to display state.
Listens only to a specific part of the state. The widget rebuilds only when the selected value changes. This is more efficient than watch for complex states.
Lifecycle Management
Blocs created with BlocProvider(create: ...) are automatically closed when the provider is removed from the tree. This prevents memory leaks. You should never manually close blocs provided this way.
If you need to keep a bloc alive across route changes, provide it at a higher level (e.g., above MaterialApp).
Testing with BlocProvider
In widget tests, you can use BlocProvider to inject mock blocs.
Best Practices
- Provide blocs at the highest level needed – Not globally unless necessary.
- Use
MultiBlocProviderfor multiple blocs – Avoids deep nesting. - Prefer
createovervalue– Let BlocProvider handle lifecycle automatically. - Use
context.readfor events,context.watchfor state – Clear separation. - Use
context.selectfor performance – Avoid unnecessary rebuilds in large widgets. - Keep blocs scoped to features – Prevents namespace pollution and accidental misuse.
Common Mistakes
- ❌ Creating a bloc inside
build– UseBlocProviderorBlocProvider.valueto share instances. - ❌ Using
BlocProvider.valuefor new instances – Usecreateinstead to get auto‑close. - ❌ Providing a bloc too low – The bloc is not available to widgets that need it.
- ❌ Accessing bloc with
readinsidebuild– Won't rebuild on state changes; usewatchorselect. - ❌ Not providing a bloc before using it – Leads to
ProviderNotFoundException.
What's Next?
Now that you know how to inject blocs, learn how to emit states effectively and test your blocs.
Next, explore Bloc emit patterns and Bloc testing.