In the provider package, what problems does the Consumer widget solve, and what does its child parameter optimise?
answer
- a new BuildContext below the provider
- Provider.of in its own build
- builder(context, value, child)
- child built once, passed through
- Consumer2 to Consumer6
basics
~20 sConsumer<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.
solid answer
~40 s`Consumer<T>` takes a required `builder(context, value, child)` and an optional `child`. Its `build` just calls `Provider.of<T>(context)` with its own `BuildContext` and passes the result to `builder`. That solves two problems. First, when a widget creates a provider and wants to read it in the same `build`, its own context sits above the provider and the lookup throws `ProviderNotFoundException`; the Consumer's context is below it. Second, it narrows rebuilds: only the builder re-runs on a notification, not the whole enclosing `build`. The `child` parameter goes further: a subtree that does not depend on `T` is built once outside `builder` and passed back in, so Flutter sees the same widget instance and skips rebuilding it. `Consumer2` to `Consumer6` read several types at once.
code
dart · 34 linesimport 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// Basket is a ChangeNotifier exposing int count.
class PromoBanner extends StatelessWidget {
const PromoBanner({super.key, required this.code});
final String code;
@override
Widget build(BuildContext context) => Text('Use $code at checkout');
}
class BasketBar extends StatelessWidget {
const BasketBar({super.key, required this.promoCode});
final String promoCode;
@override
Widget build(BuildContext context) {
return Consumer<Basket>(
builder: (context, basket, child) {
return Row(
children: [
child!, // same PromoBanner instance on every rebuild
Text('${basket.count} items'),
],
);
},
child: PromoBanner(code: promoCode),
);
}
}go deeper
Recall the builder signature (context, value, child) and that Consumer gives you a context below a provider you just created.
Explain how the child argument is built once and passed through, and why Flutter skips rebuilding an identical widget instance.
Show when Consumer is the wrong tool: it scopes rebuilds but never filters them, so value-level filtering needs Selector or select.
Set a guideline for when a team prefers inline Consumers versus extracted widget classes, weighing readability, const-ness and review clarity.
## What Consumer is `Consumer<T>` is a small widget from the provider package. Its constructor takes an optional `key`, a required `builder` and an optional `child`; the builder's signature is `Widget Function(BuildContext context, T value, Widget? child)`. Its build does one thing: it calls `Provider.of<T>(context)` with **its own** `BuildContext` and hands the result to `builder`. The package documentation says plainly that it 'doesn't do any fancy work'. Variants `Consumer2` to `Consumer6` read two to six provider types and pass each value as its own builder argument, with `child` always last. Because `Provider.of<T>(context)` listens by default, a Consumer behaves like a `context.watch<T>()` whose rebuild is **scoped to the Consumer's builder**. That one fact gives it both of its purposes. ## Purpose 1: a context below the provider A `BuildContext` only sees providers **above** its widget. A common mistake is to create a provider and read it in the same `build` method: ```dart @override Widget build(BuildContext context) { return ChangeNotifierProvider( create: (_) => Basket(), child: Text('${Provider.of<Basket>(context).count}'), ); } ``` This `context` belongs to the widget whose `build` is running, an **ancestor** of the `ChangeNotifierProvider`, so the lookup finds nothing and throws `ProviderNotFoundException`. Wrapping the reader in `Consumer<Basket>` fixes it, because the Consumer's own context sits **below** the provider. A `Builder` or a separate widget class works for the same reason. ## Purpose 2: narrower rebuilds When a widget calls `context.watch<T>()` in its `build`, the **whole** build method re-runs on every notification and every widget it constructs is recreated. If only a small part of that tree depends on `T`, wrapping just that part in `Consumer<T>` moves the dependency down: the parent's `build` no longer subscribes, and only the builder re-runs. ## The child parameter Sometimes the widget that depends on `T` **wraps** a subtree that does not. Rebuilding the wrapper through `builder` would reconstruct the inner subtree too. The `child` parameter avoids that: 1. The independent subtree is built **once**, where the Consumer itself is constructed. 2. On each rebuild, the Consumer passes that same instance to `builder` as its third argument. 3. `builder` places it in the new tree. When Flutter updates the element at that position and finds the **identical** widget instance, it skips rebuilding that subtree. So when `Basket` notifies in the code example, the builder's own widgets (a `Row` and a `Text`) are recreated while the `PromoBanner` instance is reused. `child` is typed `Widget?` in the builder because the parameter is optional, hence the `!` when you know you passed one. A `const` child already gets this for free, because a `const` expression is canonicalised to one instance. The `child` parameter matters most for **non-const** subtrees, such as a widget built from runtime arguments. ## Consumer inside MultiProvider Consumer extends `SingleChildStatelessWidget` from the `nested` package, the same base the providers use, so it can appear in `MultiProvider(providers: [...])`. The documentation's rule there: it **must** return the `child` passed to `builder` somewhere in the tree it creates, because that child carries the rest of the nesting. ## Consumer next to the other readers | Tool | Subscribes to | What re-runs on a notification | Filters by value | |---|---|---|---| | `context.watch<T>()` in `build` | whole `T` | the calling widget's `build` | no | | `Consumer<T>` | whole `T` | the Consumer's `builder` | no | | `context.select` in `build` | selector result | the calling widget's `build`, if the result changed | yes | | `Selector<T, S>` | selector result | the Selector's `builder`, if the result changed | yes | Key takeaways: - A Consumer narrows **where** a rebuild happens, not **whether** it happens: every notification from `T` still runs `builder`. - To filter by value, use `Selector` or `context.select`. - Extracting a small widget class that calls `context.watch` gives the same scoping as a Consumer; the Consumer is simply the inline form. - The `child` optimisation composes with all of this: `Selector` takes a `child` with the same meaning. ## Mistakes reviewers catch - Building the would-be `child` inside `builder` anyway, which throws the optimisation away. - Passing `child` but never placing it in the returned tree, so the subtree silently disappears. - Wrapping an entire screen in one `Consumer`, which is no narrower than a screen-level `watch`. - Expecting a Consumer to skip rebuilds when the fields its builder reads are unchanged; it cannot see which fields the builder uses.
- Does wrapping a widget in Consumer reduce how often the builder runs?No. A Consumer listens to the whole value, so every notification from the provider runs its builder. It reduces how much is rebuilt, by moving the dependency from a large build method to a small builder. To skip rebuilds when the relevant value is unchanged, use `Selector` or `context.select`.
- When is a Consumer preferable to extracting a small widget class that calls context.watch?They scope rebuilds the same way. A Consumer is handy inline, especially right under a provider created in the same build, or when a non-const subtree should pass through `child`. A named widget class reads better when the dependent part is reused or large, and it can take a `const` constructor.
Consumer's child is like a prefabricated wall panel delivered to a renovation: each time the room is redone around it, the builders slot the same panel back in instead of building a new one.
saying these in an interview costs you the question
- Consumer only rebuilds when the fields its builder uses change
- The child passed to Consumer is rebuilt on every notification anyway
- Provider.of in the same build that creates the provider works because create runs first
- Consumer needs listen: false to avoid rebuilding its parent
- A Consumer inside MultiProvider may ignore the child argument