skip to content

With Flutter's provider package, when do you use a provider's create constructor versus its .value constructor, and what breaks when they are swapped?

level: middleimportance: must knowfreq 50%

answer

  1. who owns the object
  2. create runs once, lazily
  3. never build a new object in .value
  4. create with an existing instance disposes it
  5. changing inputs need a proxy

basics

~20 s

Use create when the provider builds and owns the object, .value when it exposes an instance owned elsewhere. A new object inside .value is recreated on every rebuild; an existing instance passed to create gets disposed while still in use.

solid answer

~50 s

The choice is about ownership. The default constructor takes `create`, which the provider calls once — lazily, on first read — and whose result it owns: `ChangeNotifierProvider` disposes that notifier when it leaves the tree. `.value` exposes an object someone else owns and never disposes it. Swapping them breaks in two directions. `ChangeNotifierProvider.value(value: ThemeNotifier())` inside `build` makes a fresh notifier on each rebuild, so state such as the chosen theme resets and none of those notifiers is ever disposed. `ChangeNotifierProvider(create: (_) => existingCart)` lets the provider dispose a cart that the rest of the app still uses. A third mistake is feeding `create` a variable that changes: `create` runs once, so the object never sees the new value — that is what `ProxyProvider` exists for. `.value` is right for existing instances, such as each `CartItem` in a list.

code

dart · 64 lines
dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class CartItem extends ChangeNotifier {
  CartItem(this.name);
  final String name;
  int quantity = 1;

  void increment() {
    quantity++;
    notifyListeners();
  }
}

class Cart extends ChangeNotifier {
  final List<CartItem> items = [CartItem('Baguette'), CartItem('Eclair')];

  @override
  void dispose() {
    for (final item in items) {
      item.dispose();
    }
    super.dispose();
  }
}

class CartScreen extends StatelessWidget {
  const CartScreen({super.key});

  @override
  Widget build(BuildContext context) {
    // create: the provider builds the Cart once and disposes it later.
    return ChangeNotifierProvider<Cart>(
      create: (_) => Cart(),
      builder: (context, _) {
        final cart = context.watch<Cart>();
        return ListView(
          children: [
            // .value: each CartItem is owned by the Cart, not by the row.
            for (final item in cart.items)
              ChangeNotifierProvider<CartItem>.value(
                value: item,
                child: const CartLine(),
              ),
          ],
        );
      },
    );
  }
}

class CartLine extends StatelessWidget {
  const CartLine({super.key});

  @override
  Widget build(BuildContext context) {
    final item = context.watch<CartItem>();
    return ListTile(
      title: Text(item.name),
      trailing: Text('x${item.quantity}'),
      onTap: item.increment,
    );
  }
}

go deeper

for a junior

Remember: create when the provider should make the object, .value when the object already exists and belongs to someone else.

for a middle

Explain ownership: create runs once and ChangeNotifierProvider disposes its result, .value never disposes, and what happens when either is used in the wrong place.

for a senior

Spot the leaks and use-after-dispose bugs these mistakes cause in review, and move objects with changing inputs to proxy providers.

for a principal

Make ownership explicit in the codebase's conventions so every provided object has one owner and a predictable lifetime across screens and tests.

## Two constructors, two ownership models Nearly every class in the provider package has two constructors: - the **default constructor**, which takes a `create` callback, for example `ChangeNotifierProvider(create: (_) => ThemeNotifier())`; - the **`.value` constructor**, which takes an existing object, for example `ChangeNotifierProvider.value(value: item)`. The difference is **who owns the object**. With `create`, the provider builds the object itself and manages its lifecycle. With `.value`, the object belongs to someone else — a parent widget, a list, a service — and the provider only exposes it. | | `create` | `.value` | |---|---|---| | Who builds the object | the provider, once | the caller, beforehand | | When it is built | lazily, on first read by default | already exists | | Disposal by `ChangeNotifierProvider` | yes, when the provider leaves the tree | never | | Rebuilding the provider widget | keeps the same object | exposes whatever value is passed now | | Typical use | app or screen state | items of a list, objects owned elsewhere | ## What `create` guarantees The package's documentation compares `Provider` to a `State.initState` plus `State.dispose` pair: `create` is called **only once**, the first time the value is read, and the result is kept across rebuilds of the provider widget. That is why a `ThemeNotifier` created this way keeps the user's chosen theme no matter how often `BakeryApp` rebuilds. When the provider is removed from the tree, `ChangeNotifierProvider` calls `dispose()` on the notifier it created; plain `Provider` does the same through its optional `dispose` callback. ## What `.value` guarantees `.value` exposes an object without taking ownership. It never disposes it, and if the provider widget rebuilds with a different object, dependents are notified — `Provider.value` compares old and new with `!=` by default, and the listenable variants resubscribe to the new object. That makes `.value` the right tool for list items: a `Cart` owns its `CartItem` notifiers, and each row is wrapped in `ChangeNotifierProvider.value(value: item, child: const CartLine())`, so the row can listen to its item without disposing it when the row scrolls away. ## The three classic mistakes 1. **Creating inside `.value`.** `ChangeNotifierProvider.value(value: ThemeNotifier(), child: ...)` inside a `build` method constructs a new notifier on every rebuild. State resets, listeners flip between instances, and because `.value` never disposes, each discarded notifier leaks. The package's documentation explicitly says not to use `.value` to create values. 2. **Passing an existing instance to `create`.** `ChangeNotifierProvider(create: (_) => cart)` makes the provider believe it owns `cart`, so it disposes it when it leaves the tree, even though other screens still use it. Any later `notifyListeners` on a disposed `ChangeNotifier` fails. 3. **Building from values that change.** `ChangeNotifierProvider(create: (_) => OrderNotifier(bakeryId))` captures `bakeryId` once; when the parent passes a new id, the notifier never learns about it. The package points to `ProxyProvider` or `ChangeNotifierProxyProvider` for objects that depend on changing inputs. ## A quick decision rule - Is the provider the natural owner, and should the object live exactly as long as the provider? Use `create`. - Does something else own the object, or does it already exist when the widget builds? Use `.value`. - Does the object depend on values that can change later? Use a proxy provider. ## Where the object is created matters too `create` receives a `BuildContext`, and the object it returns can read other providers through it at creation time, for example `create: (context) => Cart(context.read<PriceList>())`. That read happens once. It is fine for dependencies that never change, such as a service object; it is the third mistake above when the dependency can change. The same applies to widget constructor arguments captured in the closure: they are read once, not tracked. The `.value` constructor, by contrast, has no callback at all. Whatever expression you pass is evaluated on every build of the enclosing widget, which is harmless for a variable and harmful for a constructor call. ## Tests follow the same rule In widget tests the fake is owned by the test, so it is exposed with `.value`: `Provider<BakeryApi>.value(value: fakeApi, child: const BakeryApp())`. The package's own documentation recommends giving the type argument explicitly there, so the fake is registered under the interface type the app reads rather than under its own class.

  • Why is .value correct for list items even though the list rebuilds often?
    The items already exist and are owned by the list's model, so the row must not dispose them when it scrolls off screen. `.value` exposes the current item without taking ownership, and if a rebuilt row receives a different item, dependents are notified and the listener moves to the new object. Using `create` would tie each item's lifetime to a row widget.
  • How should a notifier that depends on a changing id be provided instead of create?
    With `ChangeNotifierProxyProvider` or `ProxyProvider`, whose `update` runs again when a provider it depends on changes. The notifier is created once in `create` and receives the new id in `update`, for example through a method such as `setBakery(id)`, so state is kept while the input stays current.

create is like a bakery baking its own bread: it makes the loaf and clears it away at closing. .value is like displaying a supplier's cake on consignment: the bakery shows it but must never throw it out, because it is not theirs.

saying these in an interview costs you the question

  • .value and create behave the same; .value is just shorter.
  • ChangeNotifierProvider.value disposes the notifier when the provider is removed.
  • create runs again whenever the provider widget rebuilds.
  • Passing an existing notifier to create is safe because create only reads it.
  • A variable captured in create stays in sync with later changes.