文章目錄
狀態管理
Flutter
頁面是有widget
構成的,在widget
中就有有狀態 widget
和無狀態 widget
。這裏的狀態是State
,翻看源碼可以看到State
的描述是這樣的:(1)在構建widget
時可以同步讀取(2)在widget
的生存期內可能會更改。widget
實現者的職責是確保使用State.setState
在狀態改變時迅速得到通知。因此狀態管理
就是在state
變化時迅速通知到widget
的實現着,讓其根據通知做出相應的處理。
上篇文章(Flutter widget 間傳遞數據的方案 InheritedWidget、Notification、EventBus )我們瞭解了數據在widget
之間的傳遞,文中的方案對狀態的管理耦合度都比較高。
早就聽聞官方推薦的 Provider 庫和 阿里出品的 fish-redux 庫
通過本篇我們來學習大廠們是如何對state
進行管理的。
Provider
Provider 4.0.5:依賴項注入(DI)和狀態管理之間的混合,使用
widgets
構建。
通過使用widgets
進行狀態管理,provider
可以保證:
- 可維護性,通過強制的單向數據流實現
- 可測試性/可組合性,因爲始終可以模擬/覆蓋值
- 健壯性,因爲很難忘記處理模型/小部件的更新方案
使用文檔
暴露(創建)值
創建新的對象
- 推薦做法:在
create
內創建一個新對象
Provider(
create: (_) => new MyModel(),
child: ...
)
- 不推薦做法:使用
Provider.value
創建對象。
ChangeNotifierProvider.value(
value: new MyModel(),
child: ...
)
不要使用隨時間變化的變量來創建對象。原因是在這種情況下,變量更改時
將永遠不會更新
您的對象
int count;
Provider(
create: (_) => new MyModel(count),
child: ...
)
如果要將隨時間變化的變量傳遞給對象,請考慮使用ProxyProvider
:
int count;
ProxyProvider0(
update: (_, __) => new MyModel(count),
child: ...
)
使用已存在的對象
如果已經有對象實例並想要公開它,則應使用提供程序的
.value
構造函數。否則,可能會在對象仍在使用時調用其dispose
方法。
- 推薦做法:使用
ChangeNotifierProvider.value
提供一個現有的ChangeNotifier
。
MyChangeNotifier variable;
ChangeNotifierProvider.value(
value: variable,
child: ...
)
- 不推薦做法:使用默認構造函數重用現有的
ChangeNotifier
MyChangeNotifier variable;
ChangeNotifierProvider(
create: (_) => variable,
child: ...
)
讀取值
讀取值的最簡單方法是使用靜態方法
Provider.of <T>(BuildContext context)
。該方法從傳遞BuildContext
的widget
開始,在widget tree
中查找如果找到,它將返回找到的T
類型的最近變量(如果未找到,則拋出該異常)。
結合暴露一個值
的第一個示例,此widget
將讀取顯示的String
並呈現“ Hello World”
。
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
// String 類型
Provider.of<String>(context)
);
}
}
另外,也可以不使用Provider.of
,而可以使用Consumer
和Selector
。 這些對於性能優化或難以獲得provider
的BuildContext
後代很有用。有關更多信息,請參見FAQ或 Consumer 和 Selector 的文檔。
讀取多個值 MultiProvider
在大型應用程序中注入許多值時,Provider
可能會迅速嵌套,如下:
Provider<Something>(
create: (_) => Something(),
child: Provider<SomethingElse>(
create: (_) => SomethingElse(),
child: Provider<AnotherThing>(
create: (_) => AnotherThing(),
child: someWidget,
),
),
),
這種死亡嵌套,看着就讓人入魔,所以Provider
也提供了簡便的寫法:
MultiProvider(
providers: [
Provider<Something>(create: (_) => Something()),
Provider<SomethingElse>(create: (_) => SomethingElse()),
Provider<AnotherThing>(create: (_) => AnotherThing()),
],
child: someWidget,
)
ProxyProvider
從
3.0.0
開始,提供了一種新的provider
:ProxyProvider
。ProxyProvider
是一個provider
,它將來自其他provider
的多個值組合到一個新對象中,並將結果發送給Provider
。 然後,只要新provider
之一依賴更新,該新對象就會被更新。
下面的示例使用ProxyProvider
來基於來自另一個provider
的計數器來構建轉換。
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter()),
ProxyProvider<Counter, Translations>(
update: (_, counter, __) => Translations(counter.value),
),
],
child: Foo(),
);
}
class Translations {
const Translations(this._value);
final int _value;
String get title => 'You clicked $_value times';
}
它具有多種變體,例如:
ProxyProvider vs ProxyProvider2 vs ProxyProvider3
,… 類名後面的數字是ProxyProvider
依賴的其他providers
的數量。ProxyProvider vs ChangeNotifierProxyProvider vs ListenableProxyProvider
,… 它們的工作方式都相似,但是ChangeNotifierProxyProvider
不會將結果發送到Provider
,而是將其值發送到ChangeNotifierProvider
。
使用文檔總結
首先Provider
是依賴注入和狀態管理和混合物,也就是它能實現這兩種功能。依賴注入這裏先暫時不用管,狀態管理纔是文章的重點。
這裏的狀態管理主要是指數據、UI的管理。
看了以上文檔,如果還是懵逼,那麼直接看這裏的使用步驟吧。
Provider
簡單的使用可以按如下步驟來:
- 創建繼承自
ChangeNotifier
的數據類 - 創建值,創建新對象通過
create
的方式,複用對象通過ChangeNotifierProvider.value
的方式 - 獲取值,可通過
Provider.of(context)
的方式或者Consumer
和Selector
的方式
簡單示例使用
官方 simple,實現的效果是:點擊 + 號按鈕,標題文本和內容文本都會更新顯示次數。 爲了更好的理解
Provider
的使用,可看下面的代碼
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ProviderTestPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 使用 ChangeNotifierProvider.value 提供一個現有的ChangeNotifier 創建 Provider
return ChangeNotifierProvider.value(
value: Counter(),
child: MaterialApp(
home: const MyHomePage(),
)
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Title()),
body: const Center(child: CounterLabel()),
floatingActionButton: const IncrementCounterButton(),
);
}
}
/// + 號按鈕 widget
class IncrementCounterButton extends StatelessWidget {
const IncrementCounterButton({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: () {
// listen: false 必須設置
Provider.of<Counter>(context,listen: false).increment();
},
tooltip: 'Increment',
child: const Icon(Icons.add),
);
}
}
/// 文本內容 widget
class CounterLabel extends StatelessWidget {
const CounterLabel({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
final counter = Provider.of<Counter>(context);
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'${counter.count}',
// ignore: deprecated_member_use
style: Theme.of(context).textTheme.display1,
),
],
);
}
}
/// 標題 widget
class Title extends StatelessWidget {
const Title({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
final counter = Provider.of<Counter>(context);
return Text('Tapped ${counter.count} times');
}
}
/// 1. 數據類
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
使用 Provider 實現主題切換示例
上篇文章寫過
EventBus
實現主題切換 https://blog.csdn.net/ITxiaodong/article/details/105083216,這次使用Provider
也實現主題切換的功能。
完成下面幾個步驟即可實現:
- 定義類
extends
或者with ChangeNotifier
,如:點擊查看 theme.dart 的實現 - 封裝
Store
類,用於所有provider
數據的初始化。如:點擊查看 store.dart 的實現 - 在
main.dart
文件中聲明爲全局的對象,並通過Consumer
去調用。如:點擊查看 main.dart 的實現。 - 在
我的頁面
點擊修改主題後出現彈窗並選擇顏色。如:點擊查看 mine_page.dart 的實現
爲了閱讀體驗,不粘貼代碼了。
具體源碼請跳轉項目查看:https://github.com/YGragon/Flutter-WanAndroid
參考
- https://pub.dev/packages/provider#-readme-tab-
- https://juejin.im/post/5d00a84fe51d455a2f22023f
- https://juejin.im/post/5d0634c7f265da1b91639232
上車
佛系原創號主