Intermediate · State management · topic 28 of 50 · 35 min
Provider and ChangeNotifier
Explanation
Why Provider?
Provider wraps InheritedWidget in an easy API. It is used in many production apps, has been recommended in the Flutter documentation's simple state management guide, and is a good first state management tool to learn. Add it with flutter pub add provider.
The idea has three parts. A model class holds the shared data and the methods that change it. A provider widget makes one instance of that model available to everything below it in the tree. Widgets that need the data read it from context, and widgets that display it are rebuilt automatically when the model announces a change.
ChangeNotifier is a small class from Flutter's foundation library. It keeps a list of listeners; calling notifyListeners() tells all of them "something changed". Provider registers the widgets that watch the model as listeners, so you never call setState yourself for shared data.
Step 1: a ChangeNotifier model
import 'package:flutter/foundation.dart';
class CartItem {
const CartItem(this.name, this.price);
final String name;
final int price;
}
class CartModel extends ChangeNotifier {
final List<CartItem> _items = [];
List<CartItem> get items => List.unmodifiable(_items); // read-only view
int get count => _items.length;
int get total => _items.fold(0, (sum, i) => sum + i.price);
void add(CartItem item) {
_items.add(item);
notifyListeners(); // tell listeners to rebuild
}
void clear() {
_items.clear();
notifyListeners();
}
}Step 2: provide it above the widgets
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CartModel(),
child: const ShopApp(),
),
);
}
// Several models:
// MultiProvider(providers: [
// ChangeNotifierProvider(create: (_) => CartModel()),
// ChangeNotifierProvider(create: (_) => AuthModel()),
// ], child: const ShopApp())Step 3: read it
class CartBadge extends StatelessWidget {
const CartBadge({super.key});
@override
Widget build(BuildContext context) {
final count = context.watch<CartModel>().count; // rebuilds when the cart changes
return Badge(label: Text('$count'), child: const Icon(Icons.shopping_cart));
}
}
class AddButton extends StatelessWidget {
const AddButton({super.key, required this.item});
final CartItem item;
@override
Widget build(BuildContext context) {
return FilledButton(
onPressed: () => context.read<CartModel>().add(item), // read in callbacks: no rebuild needed
child: const Text('Add to cart'),
);
}
}| API | Use in | Rebuilds on change? |
|---|---|---|
| context.watch<T>() | build | Yes |
| context.read<T>() | Callbacks like onPressed | No |
| context.select<T, R>((t) => t.part) | build | Only when that part changes |
| Consumer<T>(builder: ...) | Inside a larger build | Only the Consumer's subtree |
Consumer and select limit rebuilds: if only the cart badge depends on the count, wrap just the badge, not the whole screen. This matters for performance in big screens.
Keep the business logic, like calculating totals and checking stock, inside the model, not in widgets. Then the same rules apply on every screen, and you can unit test the model without any UI. ChangeNotifierProvider also disposes the model automatically when the provider is removed from the tree.
Common mistakes
- Forgetting notifyListeners(): data changes but the UI does not.
- Using context.watch inside onPressed: an error or warning; use context.read there.
- ProviderNotFoundException: the provider is not above the widget that reads it (for example, provided inside a different route). Move it higher.
- Exposing the mutable list publicly so screens change it without notifying.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Provider and ChangeNotifier · 1/6
Provider is a simple, popular package for sharing state, built on InheritedWidget.
Prefer a video by a teacher? Find free videos on “flutter provider changenotifier tutorial watch read consumer” on YouTube ↗
In real life: A shared cart across screens
A sweet shop's ordering app shows the cart count badge on every screen's AppBar, an "Add" button on each product, and a checkout screen with the total. One CartModel provided at the top of the app powers all of them. Adding a laddu on the product screen instantly updates the badge everywhere, and the checkout reads the same total.
class CheckoutTotal extends StatelessWidget {
const CheckoutTotal({super.key});
@override
Widget build(BuildContext context) {
final total = context.select<CartModel, int>((c) => c.total); // rebuild only when total changes
return Text('To pay: Rs $total', style: Theme.of(context).textTheme.headlineSmall);
}
}Practice (0/3 done)
You change data in a ChangeNotifier, but the screen does not update. What is likely missing?
Which should an onPressed callback use to add an item to the cart?
Build a two-screen shop with Provider: a product list where each item has an Add button (context.read), an AppBar badge with the count (context.watch or select) on both screens, and a cart screen listing items with a total and a Clear button. Make sure the cart is shared between screens.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Riverpod overviewState management · same module
- Bloc, Cubit and choosing an approachState management · same module
- Interview questions: FlutterFlutter careers and interviews
- PerformanceAnimations and performance
- Widget testsResponsive design and testing
- The shared responsibility modelCyber Security: from zero to SOC analyst · Cloud security basics
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Which class do Provider models usually extend?
Answer: ChangeNotifier
ChangeNotifier has addListener and notifyListeners.
What causes ProviderNotFoundException?
Answer: Reading a provider that is not above the widget in the tree
Move the provider higher.
Which API rebuilds a widget only when one value of the model changes?
Answer: context.select
select listens to a part.
Where should you place a provider?
Answer: Above all widgets that need it, but no higher than needed
Scope it sensibly.
Which property holds a list of widgets in a Column?Related
Which object holds a StatefulWidget's State between rebuilds?Related
What is app state?Related
Answer: State shared by many parts of the app, like a cart
It usually survives navigation.
Which method runs once when a State is created?Related
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 2 more topics in “State management” before its exam: Riverpod overview, Bloc, Cubit and choosing an approach.
- Module 7 exam: State management · 12 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Networking, storage and Firebase. 50 topics left in the course before the final exam.