在Flutter状态管理1-ChangeNotifierProvider的使用中,我们介绍了provider
包里的ChangeNotifierProvider
使用,本文我们介绍另一种ChangeNotifierProxyProvider的使用
官方GitHub sample地址:provider_shopper,项目比较简单,一个商品列表页Catalog 和一个购物车页面Cart。
ChangeNotifierProxyProvider的使用
1. 先看主类
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Using MultiProvider is convenient when providing multiple objects.
return MultiProvider(
providers: [
// In this sample app, CatalogModel never changes, so a simple Provider
// is sufficient.
Provider(builder: (context) => CatalogModel()),
// CartModel is implemented as a ChangeNotifier, which calls for the use
// of ChangeNotifierProvider. Moreover, CartModel depends
// on CatalogModel, so a ProxyProvider is needed.
ChangeNotifierProxyProvider<CatalogModel, CartModel>(
builder: (context, catalog, previousCart) =>
CartModel(catalog, previousCart)),
],
child: MaterialApp(
title: 'Provider Demo',
theme: appTheme,
initialRoute: '/',
routes: {
'/': (context) => MyCatalog(),
'/cart': (context) => MyCart(),
},
),
);
}
}
这里使用了MultiProvider
,可以同时为Widgets树提供多个Model对象。
这里的CartModel实例对象由ChangeNotifierProxyProvider提供,ChangeNotifierProxyProvider
组合了两种功能:
(1)它会自动订阅CartModel中的更改(如果您只想使用此功能,只需使用ChangeNotifierProvider
)
(2)它采用先前提供的对象的值(在本例中为CatalogModel,如上所述),并使用它来构建CartModel的值(如果您只需要此功能,只需使用ProxyProvider
)
2. 在Model中定义方法
CartModel
继承自ChangeNotifier,提供了add和items的get方法,但是我很好奇,为什么add方法只存储一个id,为什么不直接存储Item?然后还要再持有一个CatalogModel
对象来获取Item?感觉这里是为了讲ChangeNotifierProxyProvider
才故意这样操作的吧( 即CartModel依赖于CatalogModel)
⚠️注意:CartModel实现为一个ChangeNotifier,它要求使用ChangeNotifierProvider。 而且,CartModel依赖于CatalogModel,因此需要ProxyProvider。
class CartModel extends ChangeNotifier {
/// The current catalog. Used to construct items from numeric ids.
final CatalogModel _catalog;
/// Internal, private state of the cart. Stores the ids of each item.
final List<int> _itemIds;
/// Construct a CartModel instance that is backed by a [CatalogModel] and
/// an optional previous state of the cart.
///
/// If [previous] is not `null`, it's items are copied to the newly
/// constructed instance.
CartModel(this._catalog, CartModel previous)
: assert(_catalog != null),
_itemIds = previous?._itemIds ?? [];
/// List of items in the cart.
List<Item> get items => _itemIds.map((id) => _catalog.getById(id)).toList();
/// The current total price of all items.
int get totalPrice =>
items.fold(0, (total, current) => total + current.price);
/// Adds [item] to cart. This is the only way to modify the cart from outside.
void add(Item item) {
_itemIds.add(item.id);
// This line tells [Model] that it should rebuild the widgets that
// depend on it.
notifyListeners();
}
}
3. 在商品列表页点击ADD按钮的处理
在点击按钮时,通过var cart = Provider.of<CartModel>(context);
来获取当前的Model,然后调用其中定义的方法add(),以及items的get方法来进行逻辑操作
class _AddButton extends StatelessWidget {
final Item item;
const _AddButton({Key key, @required this.item}) : super(key: key);
@override
Widget build(BuildContext context) {
var cart = Provider.of<CartModel>(context);
return FlatButton(
onPressed: cart.items.contains(item) ? null : () => cart.add(item),
splashColor: Theme.of(context).primaryColor,
child: cart.items.contains(item)
? Icon(Icons.check, semanticLabel: 'ADDED')
: Text('ADD'),
);
}
}
更多的代码,大家可以直接去该项目的GitHub仓库查看。
总结:
- 如果要在一个Widget树中,有多个Model数据需要共享,那么可以用
MultiProvider
,并在providers参数中提供多个Provider - 如果某个数据model的改变依赖于另一个model类,那么就需要使用
ProxyProvider
- 如果需要同时监听一个数据model的改变来自动更新UI,并且存在上述场景2,那么应该使用
ChangeNotifierProxyProvider