轉載請註明出處:https://blog.csdn.net/llew2011/article/details/105450640
Flutter開發過程中一個常見的問題就是狀態管理,所謂狀態管理就是管理Flutter的Widget狀態,對於Flutter的狀態管理,社區上已有多種成熟的方案:Provider、Redux、MobX、BLoC等。在這些方案裏Google建議我們使用Provider,接下來我們就學習下Provider,看它是如何做到的狀態管理,在瞭解其原理之前,我們先看下它的使用。
Provider的安裝
首先創建Flutter項目providerDemo,創建完畢後在根目錄下找到pubspec.yaml
文件,在dependencies下追加provider依賴,如下所示:
name: flutter_provider
description: A new Flutter application.
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
provider: 3.2.0 // 添加privider的依賴,注意格式
dev_dependencies:
flutter_test:
sdk: flutter
pubspec.yaml
是flutter的配置文件,它與Android項目中的build.gradle
功能類似,添加完依賴後狀態欄會彈出4個能選項:Package get , Package upgrade, Flutter upgrade以及Flutter doctor,直接點擊Packages get就會自動加載依賴,截圖如下:
Provider的使用
假設我們有三個頁面,他們分別是A頁面B頁面和C頁面,其中A頁面是主頁面(Flutter項目啓動後最先展示的頁面),它展示了一個用戶的姓名和年齡,然後從A頁面跳轉到B頁面,在B頁面可以修改用戶的姓名,之後又從B頁面跳轉到C頁面並在C頁面又修改了用戶的年齡,修改完成之後直接返回到A頁面,返回到A頁面後應該展示的是修改後的值。這個小功能在Android中能很容易實現,接下來我們看看在Flutter中該如何做。
創建UserModel
A頁面展示的是用戶姓名和年齡,我們可以定義一個UserModel
(有用戶名和年齡屬性),然後在B頁面和C頁面分別修改UserModel
的姓名和年齡(提供修改姓名和年齡的方法),修改後可以觸發頁面更新從而使A頁面展示更新後的值。UserModel
定義如下:
class UserModel with ChangeNotifier {
String _name;
int _age;
UserModel(this._name, this._age);
void updateName(String name) {
this._name = name;
notifyListeners();
}
void updateAge(int age) {
this._age = age;
notifyListeners();
}
get name => _name;
get age => _age;
}
UserModel
定義了_name 和 _age私有屬性並對外提供了updateName()和updateAge()方法,在這些updateXXX()方法內調用了notifyListeners()方法,notifyListeners()方法是ChangeNotifier
類中提供的,ChangeNotifier
是Flutter提供的具有觀察者功能的類,UserModel
使用with關鍵字表示它具有ChangeNotifier
的所有功能,我們稍微看下ChangeNotifier
的源碼,如下所示:
class ChangeNotifier implements Listenable {
// 監聽器容器
ObserverList<VoidCallback> _listeners = ObserverList<VoidCallback>();
@override
void addListener(VoidCallback listener) {
// 添加監聽器
}
@override
void removeListener(VoidCallback listener) {
// 移除監聽器
}
void notifyListeners() {
// 通知所有監聽器,觸發回調
}
}
ChangeNotifier
實現了Listenable
接口,它對外提供了注入監聽器,移除監聽器以及觸發監聽器回調等功能,因爲UserModel
使用了with關鍵字聚合了ChangeNotifier
的這些功能,所以UserModel
本質上也是一個具有觀察者功能的類。
注入UserModel
創建完UserModel
後,使用Provider庫提供的ChangeNotifierProvider
類給當前應用注入一個UserModel
實例,注入之後就可以在其它頁面使用該實例。在mian()方法做如下修改:
void main() {
// runApp(MyApp()); // 註釋掉runApp()方法,修改如下
var newWidget = ChangeNotifierProvider.value(
value: UserModel("張三", 22),
child: MyApp(),
);
runApp(newWidget);
}
main()方法是flutter的入口,該方法內僅調用了runApp()方法,runApp()方法接收一個Widget
類型的參數,我們使用ChangeNotifierProvider
的命名構造方法value()
創建一個newWidget實例並把newWidget實例傳給了runApp()方法,在構造newWidget實例時給newWidget傳遞了一個UserModel
實例和MyApp
實例。修改完main()方法後我們開始創建A、B、C三個頁面。
使用UserModel
首先創建主頁面A,在A頁面中展示UserModel
的name和age,如下所示:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'A頁面'),
navigatorObservers: <NavigatorObserver>[
GlobalNavigatorObserver(),
],
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends BaseState<MyHomePage> {
@override
Widget buildContent(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("用戶名:${Provider.of<UserModel>(context).name}"),// 使用UserModel的name值
Text("年 齡: ${Provider.of<UserModel>(context).age}"),// 使用UserModel的age值
RaisedButton(
child: Text("打開設置用戶名頁面"),
// 跳轉到B頁面
onPressed: () => NavigatorHelper.push(SettingNameWidget()),
),
],
),
),
);
}
}
A頁面用兩個文本框和一個按鈕(文本框用來展示用戶名和年齡,按鈕用來做頁面跳轉),展示姓名和年齡時是通過Provider提供的of()靜態方法獲取到了剛剛注入的UserModel
實例然後分別獲取姓名和年齡值。當點擊了按鈕後頁面會跳轉打B頁面(NavigatorHelper
是封裝的一個不依賴BuildContext就可以進行頁面跳轉的輔助類)。B頁面佈局如下:
class SettingNameWidget extends StatefulWidget {
@override
State createState() {
return _SettingNameState();
}
}
class _SettingNameState extends State<SettingNameWidget> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("設置用戶名頁面"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("設置用戶名"),
TextField(
showCursor: true,
onChanged: (value) {
// 更新name
Provider.of<UserModel>(context).updateName(value);
},
),
RaisedButton(
child: Text("打開設置年齡頁面"),
onPressed: () {
// 跳轉到C頁面
NavigatorHelper.push(SettingAgeWidget());
},
),
],
),
),
);
}
}
B頁面包含了一個輸入框和按鈕,在輸入數據後會調用Provider的of()方法獲取UserModel
實例並設置UserModel
的name值,點擊按鈕後跳轉打C頁面,C頁面佈局如下:
class SettingAgeWidget extends StatefulWidget {
@override
State createState() {
return _SettingAgeState();
}
}
class _SettingAgeState extends State<SettingAgeWidget> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("設置年齡頁面"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("設置年齡"),
TextField(
showCursor: true,
keyboardType: TextInputType.number,
onChanged: (value) {
// 更新年齡
Provider.of<UserModel>(context).updateAge(int.parse(value));
},
),
RaisedButton(
child: Text("返回首頁"),
onPressed: () => NavigatorHelper.popUntil(ModalRoute.withName("/")),
)
],
),
),
);
}
}
C頁面添加了一個輸入框和一個按鈕,當輸入框中的數據改變後會修改UserModel
的age值,最後點擊按鈕返回到A頁面,此時A頁面就顯示的是修改後的名字和年齡,演示如下:
以上就是Provider的簡單使用,通過這個示例向小夥伴們展示瞭如何使用Provider,接下來我給小夥伴們介紹一下Provider的工作原理,看看Provider是如何做到跨頁面共享狀態的。
Provider的原理
根據剛纔的示例我們知道main.dart的main()方法調用了runApp()方法,而runApp()方法接收一個Widget
類型的參數,通過使用ChangeNotifierProvider
的命名構造方法value()生成了一個ChangeNotifierProvider
實例newWidget然後把newWidget傳遞給了runApp()方法,那也就是說ChangeNotifierProvider
一定是Widget
的子類。我們就看下ChangeNotifierProvider
的源碼,如下所示:
class ChangeNotifierProvider<T extends ChangeNotifier>
extends ListenableProvider<T> implements SingleChildCloneableWidget {
// 省略相關代碼
ChangeNotifierProvider.value({
Key key,
@required T value,
Widget child,
}) : super.value(key: key, value: value, child: child);
}
ChangeNotifierProvider
繼承了ListenableProvider
,它除了定義了兩個構造方法外,什麼都沒有做,繼續看下它的父類ListenableProvider
,源碼如下:
class ListenableProvider<T extends Listenable> extends ValueDelegateWidget<T>
implements SingleChildCloneableWidget {
// 包含一個Widget實例,該實例是剛剛傳遞進來的MyApp
final Widget child;
// 省略相關代碼
ListenableProvider.value({
Key key,
@required T value,
Widget child,
}) : this._(
key: key,
delegate: _ValueListenableDelegate(value), // 創建一個Delegate
child: child,
);
// 重寫了build()方法,返回InheritedProvider實例
@override
Widget build(BuildContext context) {
final delegate = this.delegate as _ListenableDelegateMixin<T>;
return InheritedProvider<T>(
value: delegate.value,
updateShouldNotify: delegate.updateShouldNotify,
child: child, // 把child傳遞給InheritedProvider
);
}
}
ListenableProvider
繼承了ValueDelegateWidget
也提供了一個value()命名構造方法並重寫了build()方法,它又定義了一個Widget
類型的child屬性,因爲我們在創建ChangeNotifierProvider
的時候傳遞的child是MyApp實例,所以當前的child就是MyApp實例。ListenableProvider
的build()方法返了一個InheritedProvider
實例,在創建InheritedProvider
實例的時候把child傳遞給了InheritedProvider
。我們繼續看InheritedProvider
的源碼,如下所示:
class InheritedProvider<T> extends InheritedWidget {
const InheritedProvider({
Key key,
@required T value,
UpdateShouldNotify<T> updateShouldNotify,
Widget child,
}) : _value = value,
_updateShouldNotify = updateShouldNotify,
super(key: key, child: child);// 把child傳遞給了InheritedWidget,也就是把MyApp傳遞給了InheritedWidget
final T _value; // _value就是UserModel實例
final UpdateShouldNotify<T> _updateShouldNotify;
@override
bool updateShouldNotify(InheritedProvider<T> oldWidget) {
if (_updateShouldNotify != null) {
return _updateShouldNotify(oldWidget._value, _value);
}
return oldWidget._value != _value;
}
}
通過InheritedProvider
的源碼我們發現它繼承自InheritedWidget
,InheritedWidget
是Flutter中比較重要的一個功能型Widget
,它提供了一種數據可以在Widget
樹中從上到下傳遞、共享的方式,比如我們在應用的根Widget
中通過InheritedWidget
共享了一個數據,那麼我們便可以在任意的子Widget
中來獲取該共享數據,Provider的原理恰好是通過該特性實現了狀態的跨組件共享(#^.^#)。這也是Provider要求我們在main()方法做修改的目的,就是把根Widget(MyApp)替換成InheritedWidget
,然後把MyApp作爲InheritedWidget
的子Widget
,操作過程如下所示:
到這裏我們已經知道了Provider是通過InheritedWidget
實現了狀態跨Widget
共享,那麼它是如何觸發頁面更新的呢?繼續看觸發頁面更新的時機,當調用了UserModel的setXXX()方法後會觸發notifyListeners()方法,notifyListeners()方法是在ChangeNotifier
中定義的,源碼如下:
void notifyListeners() {
if (_listeners != null) {
final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners);
for (VoidCallback listener in localListeners) {
try {
// 循環遍歷監聽器,並執行監聽器的回調函數listener
if (_listeners.contains(listener))
listener();
} catch (exception, stack) {
}
}
}
}
notifyListeners()方法會循環遍歷注入的監聽器並執行其回調,那麼這些監聽器是什麼時候注入的呢?還記得ListenableProvider
的value()命名構造方法嗎?它內部在調用私有構造方法的時候傳遞了一個_ValueListenableDelete
實例,代碼如下:
ListenableProvider.value({
Key key,
@required T value,
Widget child,
}) : this._(
key: key,
delegate: _ValueListenableDelegate(value), // 傳遞了一個_ValueListenableDelegate
child: child,// MyApp實例
);
// _ValueListenableDelegate源碼如下:
class _ValueListenableDelegate<T extends Listenable>
extends SingleValueDelegate<T> with _ListenableDelegateMixin<T> {
_ValueListenableDelegate(T value, [this.disposer]) : super(value);
@override
void didUpdateDelegate(_ValueListenableDelegate<T> oldDelegate) {
super.didUpdateDelegate(oldDelegate);
if (oldDelegate.value != value) {
_removeListener?.call();
oldDelegate.disposer?.call(context, oldDelegate.value);
// 調用startListening()方法
if (value != null) startListening(value, rebuild: true);
}
}
@override
void startListening(T listenable, {bool rebuild = false}) {
// 調用父類的startListening()方法
super.startListening(listenable, rebuild: rebuild);
}
}
// _ValueListenableDelegate的父類startListening方法如下:
void startListening(T listenable, {bool rebuild = false}) {
var buildCount = 0;
final setState = this.setState;
// listener是一個函數,當執行listener的時候會執行setState()方法
final listener = () => setState(() => buildCount++);
var capturedBuildCount = buildCount;
if (rebuild) capturedBuildCount--;
updateShouldNotify = (_, __) {
final res = buildCount != capturedBuildCount;
capturedBuildCount = buildCount;
return res;
};
// 添加監聽器,此時參數listenable就是UserModel
// 調用UserModel的addListener()方法把listener函數注入
listenable.addListener(listener);
_removeListener = () {
listenable.removeListener(listener);
_removeListener = null;
updateShouldNotify = null;
};
}
通過對_ValueListenableDelete
的執行流程分析,我們知道在_ValueListenableDelegate
的didUpdateDelegate()方法內部startListening()方法,而startListening()方法最終會把setState()函數封裝成參數添加進UserModel
的監聽器容器中,當調用UserModel
的notifyListeners()方法時會循環遍歷監聽器容器並間接執行到setState()方法,而setState()方法是Flutter Framework層提供的刷新頁面的函數,所以只要UserModel
的狀態發生改變都會觸發setState()函數從而刷新頁面。
以上就是Provider的狀態管理,它的原理就是合理利用了InheritedWidget
特性。由於篇幅原因,Provider的of()靜態函數是如何獲取到注入的UserModel
實例的,這個很簡單就不再進行源碼分析了。感興趣的小夥伴可以自行分析(#^.^#)
或許有的小夥伴有疑問,應用開發中我們不僅僅是用戶信息需要全局共享,假如其它信息也需要狀態共享,那該怎麼辦呢?Provider庫的開發者早已考慮到了多狀態共享的情況,遇見多狀態共享只需要簡單的使用MultiProvider就行了,具體用法可參考Provider的WiKi。
另外畫了一下Provider的流程圖: