skip to content

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