skip to content

With flutter_bloc, what is the difference between BlocProvider(create:) and BlocProvider.value, and who closes the bloc in each case?

level: middleimportance: must knowfreq 62%

answer

  1. create owns, value borrows
  2. create closes it on dispose
  3. lazy defaults to true
  4. .value for an existing instance
  5. never construct inside .value

basics

~20 s

BlocProvider(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 lines
dart
import '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

for a junior

Recall the rule: create makes and closes the bloc, .value only exposes one that already exists.

for a middle

Explain lazy creation, why dialogs and pushed routes need .value, and how MultiBlocProvider flattens providers.

for a senior

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.

for a principal

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