In Riverpod 3, how does provider.select narrow a widget's rebuilds, and what does it not save you from?
answer
- selector runs on every notification
- compared with ==
- new List each time defeats it
- upstream still recomputes
- selectAsync for futures
basics
~20 sprovider.select(fn) makes ref.watch or ref.listen react only when fn's result changes by ==. The upstream provider still recomputes and the selector still runs on every change; returning a freshly built list or object each time defeats it.
solid answer
~40 s`ref.watch(portfolioProvider.select((p) => p.total))` subscribes to a derived value: on each notification from `portfolioProvider` Riverpod runs the selector and notifies the widget only if the new result is not `==` to the previous one. It is the documented way to cut rebuilds instead of `ref.read`. It does not stop the upstream provider from recomputing, and the selector runs on every notification, so it should be cheap. The comparison is `==`, so a selector that builds a new `List` with `toList()` rebuilds every time unless the result type has value equality. Since Riverpod 3.0 all providers themselves also filter updates with `==`. For async providers, `selectAsync` gives a `Future` of the selected value, typically awaited inside another provider.
code
dart · 22 linesimport 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class TotalLabel extends ConsumerWidget {
const TotalLabel({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final total = ref.watch(portfolioProvider.select((p) => p.total));
final currency = ref.watch(portfolioProvider.select((p) => p.currency));
ref.listen(portfolioProvider.select((p) => p.currency), (previous, next) {
if (previous != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Converted from $previous to $next')),
);
}
});
return Text('${total.toStringAsFixed(2)} $currency');
}
}go deeper
Remember that select lets a widget watch one field of a provider's state instead of the whole object.
Walk through the mechanics: selector runs per notification, result compared with ==, rebuild only on difference. Explain why a new List per call defeats it.
Decide between select, a derived provider and a plain watch from profiling evidence, and connect Riverpod 3's == filtering to value-equality state classes.
Guide the team away from premature select sprinkling; set equality conventions for state classes so filtering works predictably across the codebase.
## The problem select solves In a stock-portfolio app, a `portfolioProvider` may expose one state object with a `total`, a `currency` and a list of `positions`. A header that only shows the total would, with a plain `ref.watch(portfolioProvider)`, rebuild whenever any position ticks. **`select`** narrows the subscription to the part the widget uses. ```dart final total = ref.watch( portfolioProvider.select((p) => p.total), ); ``` `select` is an extension on `ProviderListenable`, so it works with `ref.watch`, `ref.listen`, `ref.read` and in both widgets and providers. It returns another `ProviderListenable` of the selected type. ## What happens on each change 1. `portfolioProvider` produces a new state and, if the new state is not `==` to the old one, notifies its listeners. 2. For the selected listener, Riverpod runs the selector on the new state. 3. It compares the result with the last selected result using `==`. 4. Only if they differ is the widget marked to rebuild (or the `listen` callback called). So the header rebuilds when the total changes, and not when a position's price moves in a way that leaves the total identical. ## What select does not do | Assumption | Reality | |---|---| | The upstream provider stops recomputing | It recomputes exactly as before; only the downstream notification is filtered | | The selector runs only when the field changes | It runs on every upstream notification, so keep it cheap | | Any derived value is filtered | Only values with meaningful `==`; a new `List` per call compares by identity | | It replaces splitting providers | For expensive derivations, a separate `Provider` that watches the source caches the result once for all readers | The `==` trap is the one interviewers probe. This selector rebuilds on every change: ```dart ref.watch(portfolioProvider.select( (p) => p.positions.where((x) => x.quantity > 0).toList(), )); ``` `toList()` returns a new `List` every time, and Dart's `List` uses identity equality, so the comparison always fails. Fixes: select a primitive (a count, a total), give the result a value-equality type, or move the derivation into its own `Provider` that watches the source. ## Equality in Riverpod 3 Riverpod 3.0 made **all providers** filter their own notifications with `==`; before, some used `identical`. For state classes with value equality (hand-written `==`, freezed or equatable), an update that produces an equal object no longer notifies at all. Notifiers can override `updateShouldNotify` if a different rule is needed. `select` then applies the same `==` test to the selected slice. ## Async providers: selectAsync For a `FutureProvider` or `AsyncNotifierProvider`, plain `select` sees `AsyncValue` objects. `selectAsync` on the provider returns a `ProviderListenable<Future<Out>>`, which fits inside another provider: ```dart final totalInBaseProvider = FutureProvider((ref) async { final total = await ref.watch( quotesProvider.selectAsync((q) => q.total), ); return total; }); ``` The dependent provider re-runs only when the selected value changes. ## When to reach for it - A widget reads one field of a large or frequently changing state. - A `ref.listen` should fire only on a specific transition, such as the currency field changing. - Not as a reflex: the Riverpod docs warn against over-optimising, and a plain `watch` is fine for most widgets.
- Is select better than creating a separate derived Provider?They solve different costs. `select` filters rebuilds of one listener but runs its selector per listener on every change. A derived `Provider` that watches the source computes once, caches the result for every reader, and notifies only when its result changes by `==`. For a cheap field access, `select`; for an expensive filter or sort shared by several widgets, a derived provider.
- What changed about update filtering in Riverpod 3.0?All providers now use `==` to decide whether to notify; before, some used `identical`. A stream or notifier that emits an equal value no longer notifies listeners. A `Notifier` can override `updateShouldNotify` to change this, for example to `identical` for a large class with an expensive `==`.
saying these in an interview costs you the question
- select stops the upstream provider from recomputing
- The selector only runs when the chosen field changes
- Returning a new toList() from select is fine because contents are compared
- ref.read in build is the better way to avoid rebuilds
- select only works inside widgets, not inside providers