1. Exposing State with ChangeNotifierProvider
flutter pub add provider
ChangeNotifierProvider(
create: (context) => CartModel(),
child: MaterialApp.router(routerConfig: router),
)
This is functionally the same CartModel from Week 6 — Provider isn't a
different state model, it's a cleaner way of doing exactly what last week's hand-rolled
InheritedWidget did, with the boilerplate (the static
.of(context) accessor, updateShouldNotify) handled for you.
2. Consumer vs. context.watch/read
Consumer<CartModel>(
builder: (context, cart, child) => Badge(label: Text('${cart.items.length}')),
)
@override
Widget build(BuildContext context) {
final cart = context.watch<CartModel>(); // rebuilds this whole build() on change
return Badge(label: Text('${cart.items.length}'));
}
ElevatedButton(
onPressed: () => context.read<CartModel>().add('Widget'), // inside a callback — correct
child: const Text('Add to Cart'),
)
The rule that trips up nearly everyone at first: context.watch inside
a callback (like onPressed) throws, because callbacks don't run during
build and have no meaningful "rebuild me" semantics — use context.read
there instead, reserving watch/Consumer for the actual
build method.
3. MultiProvider
A real app has more than one piece of shared state — auth, cart, user preferences —
and nesting ChangeNotifierProviders manually gets unwieldy fast.
MultiProvider flattens that.
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CartModel()),
ChangeNotifierProvider(create: (_) => AuthModel()),
ChangeNotifierProvider(create: (_) => PreferencesModel()),
],
child: MaterialApp.router(routerConfig: router),
)
Each provider is still independent — a widget watching CartModel only
rebuilds when CartModel changes, unaffected by AuthModel
or PreferencesModel updating. MultiProvider is purely
organizational, not a change to how any individual provider behaves.
4. Hands-on Exercise
Rebuild Week 6's cart with Provider, plus a second provider
Swap the hand-rolled InheritedWidget for Provider, and add a second, independent piece of app-wide state.
Requirements:
- Replace Week 6's custom
InheritedWidgetwithChangeNotifierProvider, keeping the sameCartModelclass largely unchanged. - Use
Consumerin at least one place andcontext.watchin another, for the same kind of read, to see both styles. - Add a second, independent provider — an
AuthModelwith anisSignedInflag is a good fit, since it plugs directly into Week 5's redirect logic. - Organize both with
MultiProviderand confirm a change to one does not cause widgets watching only the other to rebuild (verify with a print statement or the Flutter DevTools rebuild counter). - Use
context.readcorrectly inside at least one button'sonPressedcallback, and confirm usingcontext.watchthere instead throws the error this week's section described.
If you're not sure whether an unrelated provider's change is triggering extra rebuilds, the Flutter DevTools "Track widget rebuilds" feature (or a simple print in each widget's build method) will show you directly — don't just assume Provider's scoping is working correctly without actually checking.
5. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
Is Provider a different state management model from what Week 6 built by hand, or the same model with a nicer API?
Is Provider a different state management model from what Week 6 built by hand, or the same model with a nicer API?
The same underlying model — a ChangeNotifier made reachable from the widget tree via an InheritedWidget-based mechanism. Provider doesn't introduce new state semantics; it removes the boilerplate (writing your own static accessor, your own updateShouldNotify) around exactly the pattern built manually last week.
Q2
Why does calling context.watch inside an onPressed callback throw an error?
Why does calling context.watch inside an onPressed callback throw an error?
watch subscribes the current build to future rebuilds when the watched value changes — that only makes sense during a widget's build method, which is the one place "rebuild this" is meaningful. A callback like onPressed runs in response to a user action, entirely outside of any build phase, so there's no build context for watch to meaningfully attach a subscription to.
Q3
Why doesn't a change to AuthModel cause a widget that only watches CartModel to rebuild, even though both are registered under the same MultiProvider?
Why doesn't a change to AuthModel cause a widget that only watches CartModel to rebuild, even though both are registered under the same MultiProvider?
Each provider tracks its own listeners independently — watching CartModel only subscribes to CartModel's own notifyListeners() calls, not to every provider nested anywhere above it. MultiProvider is purely a way to flatten the nesting syntax; it doesn't merge or couple the providers' change notifications together in any way.
Q4
What is context.read for, and why is it the correct choice inside a button callback where context.watch is not?
What is context.read for, and why is it the correct choice inside a button callback where context.watch is not?
context.read retrieves the current value once, without subscribing to future changes — exactly what a one-off action like adding an item to the cart needs, since the callback doesn't need to "stay in sync" with the model, it just needs to call a method on it once, at the moment the button is pressed.