現在大家已經了解了 狀態管理中的聲明式編程思維 和 短時 (ephemeral) 和應用 (app) 狀態的區別 之間的區別,現在可以學習如何管理簡單的全局應用狀態。
在這裡,我們打算使用 provider package。如果你是 Flutter 的初學者,而且也沒有很重要的理由必須選擇別的方式來實現(Redux、Rx、hooks 等等),那麼這就是你應該入門使用的。provider 非常好理解而且不需要寫很多代碼。它也會用到一些在其它實現方式中用到的通用概念。
即便如此,如果你已經從其它響應式框架上積累了豐富的狀態管理經驗的話,那麼可以在 狀態 (State) 管理參考 中找到相關的 package 和教程。
連結:https://flutter.cn/docs/development/data-and-backend/state-mgmt/options
1. 示例
為了演示效果,我們實現下面這個簡單應用。

程序有三個獨立的頁面:一個登陸提示,一個類別頁面,一個購物車頁面(分別用 MyLoginScreen, MyCatalog,MyCart widget 來展示)。雖然看上去是一個購物應用程式,但是你也可以和社交網絡應用類比(把類別頁面替換成朋友圈,把購物車替換成關注的人)。
類別頁面包含一個自定義的 app bar (MyAppBar) 以及一個包含元素列表的可滑動的視圖 (MyListItems)。
這是應用程式對應的可視化的 widget 樹。

所以我們有至少 6 個 Widget 的子類。他們中有很多需要訪問一些全局的狀態。比如,MyListItem 會被添加到購物車中。但是它可能需要檢查和自己相同的元素是否已經被添加到購物車中。
這裡我們出現了第一個問題:我們把當前購物車的狀態放在哪合適呢?
2. 提高狀態的層級
在 Flutter 中,有必要將存儲狀態的對象置於 widget 樹中對應 widget 的上層。
為什麼呢?在類似 Flutter 的聲明式框架中,如果你想要修改 UI,那麼你需要重構它。並沒有類似 MyCart.updateWith(somethingNew) 的簡單調用方法。換言之,你很難通過外部調用方法修改一個 widget。即便你自己實現了這樣的模式,那也是和整個框架不相兼容。
void myTapHandler() {
var cartWidget = somehowGetMyCartWidget();
cartWidget.updateWith(item);
}
即使你實現了上面的代碼,也得處理 MyCart widget 中的代碼:
Widget build(BuildContext context) {
return SomeWidget(
);
}
void updateWith(Item item) {
}
你可能需要考慮當前 UI 的狀態,然後把最新的數據添加進去。但是這樣的方式很難避免出現 bug。
在 Flutter 中,每次當 widget 內容發生改變的時候,你就需要構造一個新的。你會調用 MyCart(contents)(構造函數),而不是 MyCart.updateWith(somethingNew)(調用方法)。因為你只能通過父類的 build 方法來構建新 widget,如果你想修改 contents,就需要調用 MyCart 的父類甚至更高一級的類。
void myTapHandler(BuildContext context) {
var cartModel = somehowGetMyCartModel(context);
cartModel.add(item);
}
Widget build(BuildContext context) {
var cartModel = somehowGetMyCartModel(context);
return SomeWidget(
);
}
@override
Widget build(BuildContext context) {
return SomeWidget(
MyListItem(myTapCallback),
);
}
void myTapCallback(Item item) {
print('user tapped on $item');
}
class CartModel extends ChangeNotifier {
final List<Item> _items = [];
UnmodifiableListView<Item> get items => UnmodifiableListView(_items);
int get totalPrice => _items.length * 42;
void add(Item item) {
_items.add(item);
notifyListeners();
}
}
test('adding item increases total cost', () {
final cart = CartModel();
final startingPrice = cart.totalPrice;
cart.addListener(() {
expect(cart.totalPrice, greaterThan(startingPrice));
});
cart.add(Item('Dash');
});
void main() {
runApp(
ChangeNotifierProvider(
builder: (context) => CartModel(),
child: MyApp(),
),
);
}
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(builder: (context) => CartModel()),
Provider(builder: (context) => SomeOtherClass()),
],
child: MyApp(),
),
);
}
return Consumer<CartModel>(
builder: (context, cart, child) {
return Text("Total price: ${cart.totalPrice}");
},
);
return Consumer<CartModel>(
builder: (context, cart, child) => Stack(
children: [
child,
Text("Total price: ${cart.totalPrice}"),
],
),
child: SomeExpensiveWidget(),
);
return Consumer<CartModel>(
builder: (context, cart, child) {
return HumongousWidget(
child: AnotherMonstrousWidget(
child: Text('Total price: ${cart.totalPrice}'),
),
);
},
);
return HumongousWidget(
child: AnotherMonstrousWidget(
child: Consumer<CartModel>(
builder: (context, cart, child) {
return Text('Total price: ${cart.totalPrice}');
},
),
),
);
Provider.of<CartModel>(context, listen: false).add(item);
在 build 方法中使用上面的代碼,當 notifyListeners 被調用的時候,並不會使 widget 被重構。連結:https://github.com/flutter/samples/tree/master/provider_shopper如果你想參考稍微簡單一點的示例,可以看看 Counter 應用程式是如何 基於 provider 實現的。連結:https://github.com/flutter/samples/tree/master/provider_counter如果你已經學會了並且準備使用 provider 的時候,別忘了先在 pubspec.yaml 中添加相應的依賴。連結:https://github.com/flutter/samples/tree/master/provider_countername: my_name
description: Blah blah blah.
dependencies:
flutter:
sdk: flutter
provider: ^3.0.0
dev_dependencies: