Flutter State Management with Provider
To install Provider, depend on provider
in pubspec.yaml
:
dependencies:
provider:
Concept | Purpose |
---|---|
ChangeNotifier |
Contains state, notifies listeners |
ChangeNotifierProvider |
Provides state |
Consumer |
Read/updates state, rebuilds |
ChangeNotifier
Create a class model that contains state.
import 'package:flutter/foundation.dart' // contains ChangeNotifier
class CartModel extends ChangeNotifier { // enables notifyListeners()
final List<Item> _items = [];
int get totalPrice => _items.length * 42;
void add(Item item) {
_items.push(item);
notifyListeners();
// causes listeners to rebuild on state change
}
}
ChangeNotifierProvider
Place the model in the tree, to provide state to widgets below.
Note: The "top widget" is the common ancestor in a section of the widget tree whose widgets need state.
To place the model, locate the top widget and wrap it in ChangeNotifierProvider<T>
. That T
is your class model. This provides state data to anywhere in the widget tree, from the wrapped widget down.
In wrapping the top widget, ChangeNotifierProvider<T>
takes two arguments: builder
, an invoked function instantiating the state model class, and child
, the top widget being wrapped.
ChangeNotifierProvider<CartModel>(
builder: (context) => CartModel(),
child: MyApp(),
),
Provider.of
Read/update the class model from any widget below in the tree.
Down the widget tree, some widgets ("consumers" or "listeners") need to access state in order to read it and rebuild to reflect state changes, and some widgets ("producers") need to access state to update it.
Provider.of<T>
evaluates to the closed ancestor widget whose type is the state model class, i.e. Provider.of<T>
evaluates to the instance of the state model class with all its properties and methods.
FlatButton(
onPressed: () {
final model = Provider.of<CartModel>(context);
print(model.totalPrice); // read state
model.add(item); // update state
},
);
By default, Provider.of<T>
triggers a rebuild of the widget, so it is used for a consumer that needs to show updated state (think a counter in a cart), i.e. the widget is auto-registered as a listener with an implicit default of listen: true
. The rebuild affects also all its descendants.
FlatButton(
onPressed: () {
final cartModel = Provider.of<CartModel>(context);
cartModel.add(item); // triggers rebuild
},
);
To override the default and prevent it from rebuilding and thus to simply update the value, set listen: false
. This is useful if the widget is a producer that only updates state without showing the update (think a button to remove items from a cart), to prevent wasteful rebuilds.
FlatButton(
onPressed: () {
final cartModel = Provider.of<CartModel>(context, listen: false);
cartModel.clearAll(); // does NOT trigger rebuild
}
);
Consumer<T>
Read/update the class model from any widget below in the tree.
Consumer
is an alternative to Provider.of<T>
. Consumer
is useful if you need access to the BuildContext
.
Down the widget tree, some widgets ("consumers" or "listeners") need to access state to rebuild to reflect state changes, and some widgets ("producers") need to access state to update it. To give them access to state, wrap them with Consumer<T>
. That T
is the state class model. The consumer will rebuild whenever the class model calls notifyListeners()
.
In wrapping the consumer, Consumer<T>
takes a builder
argument, an anonymous function with three arguments, the second argument being an instance of the class model. Disregard context
and child
.
Consumer<CartModel>(
builder: (context, cartModel, child) {
return RaisedButton(
onPressed: () {
print(cartModel.totalPrice); // read state
cartModel.add(item); // update state
}
)
}
);