skip to content

Provider

The provider package wraps InheritedWidget so objects can be created, exposed and disposed down the tree, then read with watch, read and select. Interviewers use it as the rebuild-scope baseline.

on this pageshow

explore

questions

15

With the provider package, what is the difference between context.watch, context.read and context.select, and when do you use each?

level: juniorimportance: must knowfreq 72%

answer

  1. three BuildContext extension methods
  2. one subscribes, one does not
  3. watch is Provider.of, listen true
  4. read is Provider.of, listen: false
  5. select compares a derived value

basics

~10 s

context.watch<T>() returns the nearest provided T and rebuilds the widget when it notifies; context.read<T>() returns it without subscribing, for callbacks; context.select rebuilds only when the value its selector derives changes.

solid answer

~40 s

All three are `BuildContext` extensions from the provider package that return the nearest ancestor provider's value of type `T`. `context.watch<T>()` is `Provider.of<T>(context)`: it subscribes, so the widget rebuilds every time the provider notifies. `context.read<T>()` is `Provider.of<T>(context, listen: false)`: it returns the same live instance but registers no dependency, so it belongs in event handlers like `onPressed`, in `initState` and in a provider's `create`. `context.select((T v) => ...)` subscribes to a derived value: on each notification the selector re-runs and the widget rebuilds only if the result differs from the last one. Rule of thumb: `watch` or `select` for what `build` displays, `read` for what a callback triggers.

code

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

class Basket extends ChangeNotifier {
  final List<String> _skus = [];
  List<String> get skus => List.unmodifiable(_skus);
  int get count => _skus.length;

  void add(String sku) {
    _skus.add(sku);
    notifyListeners();
  }
}

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

  @override
  Widget build(BuildContext context) {
    // watch: rebuilds on every Basket notification
    final skus = context.watch<Basket>().skus;
    return Column(children: [for (final sku in skus) Text(sku)]);
  }
}

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

  @override
  Widget build(BuildContext context) {
    // select: rebuilds only when count changes
    final count = context.select((Basket b) => b.count);
    return Text('$count items');
  }
}

class AddButton extends StatelessWidget {
  const AddButton({super.key, required this.sku});

  final String sku;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      // read: no subscription, looked up when tapped
      onPressed: () => context.read<Basket>().add(sku),
      child: const Text('Add'),
    );
  }
}

go deeper

for a junior

Recall the mapping: watch subscribes and rebuilds, read does not subscribe, select subscribes to one derived value. Say which one goes in build and which in onPressed.

for a middle

Explain that watch and read are Provider.of with listen true and false, and how select re-runs its selector on notification and compares results before marking the widget dirty.

for a senior

Show judgement about rebuild cost: pick select or a narrow Consumer over a screen-level watch, and explain why read-in-build is a latent stale-UI bug rather than an optimisation.

for a principal

Frame it as a team convention: subscribe in build, read in callbacks, and make select the default for large models so reviews catch over-broad subscriptions early.

## Three ways to read one provider The provider package adds three **extension methods** to `BuildContext`: `watch` (from the `WatchContext` extension), `read` (from `ReadContext`) and `select` (from `SelectContext`). Each finds the **nearest ancestor provider** exposing type `T` and returns its value. They differ in one thing only: whether, and on what, the calling widget **subscribes**. | Method | Equivalent | Subscribes? | Widget rebuilds when | Typical place | |---|---|---|---|---| | `context.watch<T>()` | `Provider.of<T>(context)` | yes, to the whole value | the provider notifies | `build` | | `context.read<T>()` | `Provider.of<T>(context, listen: false)` | no | never, because of this call | event handlers, `initState`, `create` | | `context.select<T, R>(fn)` | the `Selector<T, R>` widget | yes, to `fn`'s result | `fn` returns a different result | `build` | ## watch: subscribe to everything `context.watch<Basket>()` is literally `Provider.of<Basket>(this)`, and `Provider.of`'s `listen` parameter **defaults to `true`**. With `listen: true` the call registers the widget's element as a **dependent** of the provider. From then on, every time the provider signals a change (for a `ChangeNotifierProvider`, every `notifyListeners()` call), the widget is marked dirty and its `build` runs again on the next frame. That is right for a widget that displays the value. It is also coarse: a widget that watches a `Basket` rebuilds when **anything** in the basket notifies, even if it shows a single field. `watch` does not track which fields `build` actually touched. ## read: access without a subscription `context.read<Basket>()` is `Provider.of<Basket>(this, listen: false)`. It returns the **same instance** the provider holds, not a copy, but registers no dependency, so later changes do not rebuild the caller. That makes it the tool for code that runs **once, in response to something**: - a button's `onPressed`: `context.read<Basket>().add(sku)`; - `initState`, to start one-off work; - a provider's `create` callback that needs another provider; - handing `context.read` itself, typed as the package's `Locator` typedef, to a plain Dart object so it can look providers up without holding a `BuildContext`. The package documentation says **not to call `read` inside `build`** as a way to avoid rebuilds. In provider 6.0.3 this is not blocked (`read` is a one-line call to `Provider.of` with `listen: false`), but it is brittle: the day someone starts displaying a field that changes, the widget silently shows stale data, with no error to point at the cause. ## select: subscribe to a slice `context.select((Basket b) => b.count)` runs the selector once during `build` and returns its result. It registers a dependency with an **aspect**: when the provider notifies, the package re-runs the selector synchronously and compares the new result with the one returned at the last build, using `DeepCollectionEquality` from package:collection. Lists, maps, sets and iterables are compared **by content**; anything else by `==`. Only when the results differ is the widget marked for rebuild. This is the package's own answer to 'my widget rebuilds too often': depend on a derived value, not the whole object. Calling `select` several times in one `build` is fine; the widget rebuilds if any of its selectors reports a change. ## Choosing, in one rule 1. The value is **shown** by `build` and all of it matters: `watch`. 2. The value is shown but only **part** of it matters: `select`. 3. The value is **used** by a callback or a one-off setup step: `read`. `Provider.of<T>(context)` and `Provider.of<T>(context, listen: false)` are the older spellings; they remain valid and are exactly what `watch` and `read` call. The widget forms are `Consumer<T>`, a `watch` scoped to its own subtree, and `Selector<T, R>`, a `select` scoped to its own subtree. ## Common confusions - `watch` rebuilds on **every** notification, not only when a field used in `build` changes. - `read` does not snapshot the value; it returns the live object, so a callback that reads a field later sees the current state. - `select` does not stop the provider notifying other dependents; it only decides whether **this** widget rebuilds. - None of the three copies the model: all share the single instance the provider owns. - All three look **upward** from the widget that owns the `context`, so the context must sit below the provider in the tree. In an interview, the crisp version is: `watch` subscribes to the whole value, `select` subscribes to a slice of it, `read` does not subscribe at all; subscribe in `build`, read in callbacks.

  • Why not call context.read in build for a value you believe never changes, to save rebuilds?
    It works in provider 6 but the package docs call it an anti-pattern. The optimisation depends on an assumption about the model; when someone later displays a field that does change, the widget shows stale data with no error. `context.select` gives the same filtering safely: it subscribes to exactly the value used, so the UI cannot silently fall out of date.
  • How do the Consumer and Selector widgets relate to these three methods?
    `Consumer<T>` calls `Provider.of<T>(context)` with its own context, so it is a `watch` whose rebuild is limited to its builder. `Selector<T, S>` is the widget form of `select`: it re-runs a selector and calls its builder only when the selected value compares unequal. Both are useful when you want the narrower scope without extracting a new widget class.
  • Can one build method call context.select more than once?
    Yes. Each call registers its own selector on the dependency, and the package documents that calling `select` multiple times is fine. When the provider notifies, the selectors run in turn and the widget is marked dirty as soon as one reports a changed result; if all compare equal, it is left alone.

saying these in an interview costs you the question

  • context.read is just a faster watch, so use it in build to avoid rebuilds
  • context.watch only rebuilds when a field used in build changes
  • context.read returns a snapshot copy that will not reflect later changes
  • Provider.of defaults to listen: false
  • context.select stops the provider notifying its other listeners
open as a page

With the provider package, which widgets can read a provider, and how do you decide where to place ChangeNotifierProvider in the tree?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Only descendants of a provider widget can read it, and the nearest provider of the requested type wins. Place ChangeNotifierProvider at the lowest common ancestor of every widget and route that reads it, no higher than its state should live.

open as a page

In Flutter's provider package, which provider class fits a plain object, a ChangeNotifier, a Future and a Stream, and why?

level: juniorimportance: must knowfreq 55%

basics

~10 s

Provider exposes a plain value without listening; ChangeNotifierProvider listens to a ChangeNotifier and disposes it; ListenableProvider and ValueListenableProvider cover other listenables; FutureProvider and StreamProvider expose an async result, starting from a required initialData.

open as a page

With the provider package, why does context.watch fail inside onPressed or initState, and what should those call sites use instead?

level: middleimportance: must knowfreq 58%

basics

~20 s

context.watch subscribes the widget, which is only meaningful during build: in onPressed provider's debug assertion rejects listening, and in initState Flutter rejects the dependency. Use context.read, or Provider.of with listen: false, at both call sites.

open as a page

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%

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.

open as a page

In a Flutter survey app using the provider package, SurveyPage provides SurveyAnswers but the ReviewPage it pushes throws ProviderNotFoundException; why, and how do you fix it?

level: seniorimportance: must knowfreq 55%

basics

~20 s

A pushed route is a child of the Navigator, not of SurveyPage, so lookups from ReviewPage never pass SurveyPage's provider. Lift the provider above the Navigator or around the flow, or re-provide the same instance on the new route with ChangeNotifierProvider.value.

open as a page

With the provider package, how do you rebuild only a basket badge when the item count changes, not on every Basket notification?

level: middleimportance: should knowfreq 44%

basics

~20 s

Put the badge in its own widget that calls context.select((Basket b) => b.count), or wrap it in Selector<Basket, int>. Either re-runs the selector on each notification and rebuilds only the badge when the count differs.

open as a page

In the provider package, what problems does the Consumer widget solve, and what does its child parameter optimise?

level: middleimportance: should knowfreq 48%

basics

~20 s

Consumer<T> calls Provider.of<T> with its own context, giving a reader below a provider created in the same build and limiting rebuilds to its builder. Its child is built once and handed back unchanged, so that subtree is reused.

open as a page

With the provider package, when does a provider dispose its value, and why is an instance passed to a .value constructor never disposed?

level: middleimportance: should knowfreq 42%

basics

~20 s

A provider disposes only what its create callback built, when the provider widget is unmounted: ChangeNotifierProvider calls dispose() itself, Provider calls its dispose callback. A .value constructor exposes an instance someone else owns, so it never disposes it.

open as a page

Why can Riverpod not throw the provider package's ProviderNotFoundException, and how does it replace provider's placement-based disposal?

level: middleimportance: should knowfreq 38%

basics

~20 s

provider's providers are widgets found by type at runtime, so a misplaced one fails the lookup. Riverpod's are global final declarations read by reference from one root ProviderScope, and state is freed by autoDispose rather than by unmounting a widget.

open as a page

In provider 6, why do FutureProvider and StreamProvider require initialData, and how do they handle errors from the Future or Stream?

level: middleimportance: should knowfreq 32%

basics

~20 s

Descendants read a plain T, so FutureProvider and StreamProvider need initialData to expose until the first result arrives; it became required with null safety in provider 5.0. Errors go to catchError, which builds a fallback value, or are reported through FlutterError.reportError.

open as a page

With the provider package, a Selector or context.select either never rebuilds or rebuilds on every notification; what are the usual causes and fixes?

level: seniorimportance: should knowfreq 28%

basics

~20 s

A selector returning a list mutated in place compares equal to itself, so nothing rebuilds; one returning a new object without == is never equal, so everything rebuilds. Return values or records; a parent rebuild also reruns Selector's builder.

open as a page

With Flutter's provider package, how do ProxyProvider and ChangeNotifierProxyProvider build a value from other providers, and what goes wrong if update creates a new notifier?

level: seniorimportance: should knowfreq 36%

basics

~20 s

ProxyProvider builds a value from providers above it in update, which runs on first read and again whenever a dependency changes. ChangeNotifierProxyProvider creates a notifier once and updates it; returning a new notifier from update loses its state and disposes the previous one.

open as a page

In provider 6, what does context.watch<SurveyAnswers?>() return when no matching provider is found, and when is a nullable lookup appropriate?

level: middleimportance: nice to knowfreq 18%

basics

~20 s

Since provider 6.0.0, a nullable type argument such as watch<SurveyAnswers?>() or read<SurveyAnswers?>() returns null when no provider is found, instead of throwing ProviderNotFoundException. Use it for reusable widgets that genuinely work with or without the provider.

open as a page

In Flutter's provider package, when does a provider's create callback actually run, and what does passing lazy: false change?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

A provider's create runs lazily, the first time a descendant reads the value, not when the provider is inserted. lazy: false makes the provider compute its value when it builds, so creation side effects start even if nothing has read it yet.

open as a page