flutter
/

BlocProvider: Dependency Injection for Flutter Bloc

Last Sync: Today

On this page

11
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

BlocProvider: Dependency Injection for Flutter Bloc

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.

DARTRead-only
1
BlocProvider(
  create: (context) => CounterBloc(),
  child: CounterPage(),
)

Inside the child widgets, access the bloc using context.read<CounterBloc>() or context.watch<CounterBloc>().

DARTRead-only
1
class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text(
          'Value: ${context.watch<CounterBloc>().state.value}',
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<CounterBloc>().add(IncrementPressed()),
      ),
    );
  }
}

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).
DARTRead-only
1
// Use create when you want a new instance
BlocProvider(
  create: (_) => CounterBloc(),
  child: MyWidget(),
)

// Use value when you have an existing instance
BlocProvider.value(
  value: existingBloc,
  child: MyWidget(),
)

MultiBlocProvider

When you need to provide multiple blocs, use MultiBlocProvider to avoid nesting. It takes a list of providers and a single child.

DARTRead-only
1
MultiBlocProvider(
  providers: [
    BlocProvider<AuthBloc>(create: (context) => AuthBloc()),
    BlocProvider<ProfileBloc>(create: (context) => ProfileBloc()),
    BlocProvider<SettingsBloc>(create: (context) => SettingsBloc()),
  ],
  child: MyApp(),
)

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.

DARTRead-only
1
// Global scope
MaterialApp(
  home: BlocProvider(
    create: (_) => AuthBloc(),
    child: HomePage(),
  ),
)

// Feature-specific scope
class FeaturePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => FeatureBloc(),
      child: FeatureContent(),
    );
  }
}

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.

DARTRead-only
1
// Rebuilds only when the counter value changes
final count = context.select((CounterBloc bloc) => bloc.state.value);

// Rebuilds only when the user name changes
final name = context.select((ProfileBloc bloc) => bloc.state.user.name);

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.

DARTRead-only
1
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => MyBloc(), // Auto-closed when MyWidget is removed
      child: ...
    );
  }
}

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.

DARTRead-only
1
testWidgets('displays counter value', (tester) async {
  final mockBloc = MockCounterBloc();
  whenListen(mockBloc, Stream.value(CounterState(5)));

  await tester.pumpWidget(
    MaterialApp(
      home: BlocProvider.value(
        value: mockBloc,
        child: CounterPage(),
      ),
    ),
  );

  expect(find.text('Value: 5'), findsOneWidget);
});

Best Practices

  • Provide blocs at the highest level needed – Not globally unless necessary.
  • Use MultiBlocProvider for multiple blocs – Avoids deep nesting.
  • Prefer create over value – Let BlocProvider handle lifecycle automatically.
  • Use context.read for events, context.watch for state – Clear separation.
  • Use context.select for 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 – Use BlocProvider or BlocProvider.value to share instances.
  • ❌ Using BlocProvider.value for new instances – Use create instead to get auto‑close.
  • ❌ Providing a bloc too low – The bloc is not available to widgets that need it.
  • ❌ Accessing bloc with read inside build – Won't rebuild on state changes; use watch or select.
  • ❌ 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.

Test Your Knowledge

Q1
of 3

Which widget should you use to provide multiple blocs without nesting?

A
BlocBuilder
B
BlocListener
C
MultiBlocProvider
D
BlocConsumer
Q2
of 3

What happens to a bloc created with BlocProvider(create: ...) when the provider is removed?

A
It remains in memory
B
It is automatically closed
C
It throws an exception
D
It continues to listen to events
Q3
of 3

Which method should you use to listen to state changes and rebuild only when a specific field changes?

A
context.read
B
context.watch
C
context.select
D
BlocBuilder

Frequently Asked Questions

What is the difference between BlocProvider and BlocProvider.value?

BlocProvider(create:) creates a new bloc instance and automatically closes it when the provider is removed. BlocProvider.value: uses an existing bloc and does not close it automatically. Use create for new instances; use value for sharing existing blocs (e.g., from a parent).

Can I use BlocProvider without MultiBlocProvider for multiple blocs?

Yes, you can nest BlocProviders. However, MultiBlocProvider is more readable and avoids deep nesting. It's recommended when providing three or more blocs.

How do I access a bloc from a nested route?

Ensure the BlocProvider is placed above the Navigator. For example, wrap MaterialApp with the provider, or wrap the route's builder with a provider if you need a fresh instance per route.

Why do I get ProviderNotFoundException?

It means you tried to access a bloc that hasn't been provided in the current context. Check that you have a BlocProvider for that bloc above the widget that is trying to access it.

Previous

bloc equatable

Next

bloc multiblocprovider

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.