With flutter_bloc, what is the difference between BlocProvider(create:) and BlocProvider.value, and who closes the bloc in each case?
answer
- create owns, value borrows
- create closes it on dispose
- lazy defaults to true
- .value for an existing instance
- never construct inside .value
basics
~20 sBlocProvider(create:) builds the bloc lazily and closes it when the provider leaves the tree. BlocProvider.value exposes an existing instance, for example to a dialog or pushed route, and never closes it; whoever created it does.
solid answer
~40 s`BlocProvider(create: (context) => LoginCubit(...))` owns its instance: `create` runs lazily on the first lookup (pass `lazy: false` to create eagerly), and when the provider is disposed it calls `close()`. `BlocProvider.value(value: cubit)` only exposes an instance that already exists — typically to hand a bloc to a pushed route or a dialog, whose subtree does not sit below the original provider — and does not close it. Mixing them up causes real bugs: constructing a bloc inside `.value` leaks it, because nothing ever closes it, and wrapping an existing bloc in `create` closes that shared bloc when the pushed route pops, so the original screen's next `add` or `emit` throws a `StateError`. `MultiBlocProvider(providers: [...], child: ...)` flattens nested providers; the `child` of each listed provider is ignored.
code
dart · 28 linesimport 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
// LoginCubit, AuthRepository, LoginView and ForgotPasswordDialog are declared elsewhere.
class LoginPage extends StatelessWidget {
const LoginPage({super.key});
@override
Widget build(BuildContext context) {
// Owns the cubit: created on first lookup, closed when LoginPage leaves the tree.
return BlocProvider(
create: (context) => LoginCubit(context.read<AuthRepository>()),
child: const LoginView(),
);
}
}
void showForgotPassword(BuildContext context) {
final cubit = context.read<LoginCubit>();
showDialog<void>(
context: context,
// Borrows the same cubit for the dialog route; not closed when the dialog pops.
builder: (_) => BlocProvider.value(
value: cubit,
child: const ForgotPasswordDialog(),
),
);
}go deeper
Recall the rule: create makes and closes the bloc, .value only exposes one that already exists.
Explain lazy creation, why dialogs and pushed routes need .value, and how MultiBlocProvider flattens providers.
Diagnose ownership bugs from symptoms: a StateError after a route pops, or state resetting because a bloc is built inside .value in a build method.
Define an ownership convention for blocs across routes and features so every instance has exactly one creator responsible for closing it.
## Two constructors, two ownership models `BlocProvider` in **flutter_bloc** 9.1 is a dependency-injection widget built on the provider package. It makes one instance of a `Bloc` or `Cubit` available to a subtree, and its two constructors differ in **who owns** that instance. | | `BlocProvider(create: ...)` | `BlocProvider.value(value: ...)` | |---|---|---| | Creates the instance | yes, via `create` | no, receives one | | When | on first lookup (`lazy: true` default) | already created | | Closes it on dispose | yes | no | | Typical use | a screen's own bloc | an existing bloc for a new route or dialog | The docs are explicit: new instances belong in `create`, and `.value` should only provide **existing** instances to new subtrees. ## Lazy creation - By default `create` runs the first time a descendant looks the bloc up — through `BlocProvider.of`, `context.read`, or a `BlocBuilder`/`BlocListener` that finds it. - `lazy: false` runs `create` immediately when the provider is built. Use it when the bloc should start work, such as loading saved credentials, before any widget asks for it. - `create` runs once per provider lifetime; rebuilding the provider widget reuses the same instance. ## When .value is right A dialog or a pushed route is inserted near the root of the navigator, **not** below the page's provider, so a lookup from inside it cannot find the page's bloc. Pass the existing instance along: ```dart void showForgotPassword(BuildContext context) { final cubit = context.read<LoginCubit>(); showDialog<void>( context: context, builder: (_) => BlocProvider.value( value: cubit, child: const ForgotPasswordDialog(), ), ); } ``` When the dialog closes, its `BlocProvider.value` is disposed, and the cubit stays open for the login page that still owns it. ## Two classic bugs 1. **Creating inside `.value`.** `BlocProvider.value(value: LoginCubit(), child: ...)` never closes that cubit, and if it sits in a `build` method every rebuild constructs a new instance, silently resetting state. 2. **Borrowing with `create`.** `BlocProvider(create: (_) => existingCubit, child: DetailsPage())` makes the new route the owner of a shared instance. When the route pops, the provider closes the cubit; back on the original screen, the next `add` or `emit` throws a `StateError` because the instance is closed. The mental rule: **the widget that creates the bloc is the one that closes it**, and `create` is how a widget declares that it is the creator. ## MultiBlocProvider Nesting several providers indents a tree quickly. `MultiBlocProvider` flattens it: ```dart MultiBlocProvider( providers: [ BlocProvider(create: (_) => LoginCubit(authRepository)), BlocProvider(create: (_) => PasswordVisibilityCubit()), ], child: const LoginView(), ) ``` - Each entry is a normal `BlocProvider` with the usual ownership rules. - Any `child` given to an entry is **ignored**; only the `MultiBlocProvider`'s own `child` is used. - Order matters only if one `create` reads another provider from the list: later entries can see earlier ones. ## Summary - `create`: owns, lazy by default, closes on dispose. - `.value`: borrows, never closes. - Never construct inside `.value`; never hand an existing shared instance to `create`.
- Why can a dialog not see the page's bloc without BlocProvider.value?showDialog pushes a route into the Navigator, so the dialog's widgets are built under the Navigator's overlay, not below the page's BlocProvider. A lookup walks up the tree from the dialog and never passes that provider. Re-providing the existing instance with BlocProvider.value inside the dialog's builder fixes it without transferring ownership.
- When would you set lazy: false on a BlocProvider?When the bloc must start working before any widget reads it — for example a cubit that begins restoring saved credentials or subscribes to connectivity as soon as the page appears. With the default lazy creation, a bloc that no visible widget has looked up yet has not even been constructed.
saying these in an interview costs you the question
- BlocProvider.value closes the bloc when the route pops
- create runs again every time the provider rebuilds
- Constructing a new bloc inside BlocProvider.value is fine
- BlocProvider creates its bloc eagerly by default
- A child passed to a provider inside MultiBlocProvider is rendered