With the provider package, what is the difference between context.watch, context.read and context.select, and when do you use each?
answer
- three BuildContext extension methods
- one subscribes, one does not
- watch is Provider.of, listen true
- read is Provider.of, listen: false
- select compares a derived value
basics
~10 scontext.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 sAll 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 linesimport '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
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.
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.
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.
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