前言
如果對Provider的使用已經很熟練,可以跳過這部分內容,直接看過程分析部分 Flutter狀態管理Provider(二)過程分析
簡介
官網介紹
[Provider](https://github.com/rrousselGit/provider)
簡單翻譯來說,Provider:依賴注入與狀態管理的結合,並且對外提供Widget使用。使用widget代替純Dart物件,比如說Stream。因為widget簡單,強大且可擴充套件。使用Provider,可以保證:可維護性(強制的單向資料流),可測試性,健壯性等等。
A mixture between dependency injection (DI) and state management, built with widgets for widgets.It purposefully uses widgets for DI/state management instead of dart-only classes like Stream.
The reason is, widgets are very simple yet robust and scalable.
By using widgets for state management, provider can guarantee:
複製程式碼
- maintainability, through a forced uni-directional data-flow
- testability/composability, since it is always possible to mock/override a value
- robustness, as it is harder to forget to handle the update scenario of a model/widget
什麼是狀態?什麼是狀態管理?
如果說頁面是靜態,拿到資料,渲染完就完事了.壓根就不需要狀態管理。 不幸的是,頁面不可能全是靜態。頁面需要響應資料變化(網路資料?使用者操作產生的資料?),更新UI。同時,資料變化的影響,不僅僅是元件內,還有可能是頁面內其他元件,甚至於應用內其他頁面
資料即為狀態。從資料變化到通知介面更新的過程,我們稱之為狀態管理 狀態管理要儘可能的把這個過程獨立出來,讓動態介面如同靜態頁面一般簡單。
有哪些狀態管理的方式/庫
- setState
- FutureBuilder/StreamBuilder/BLoc
- Provider/ScopedModel
- redux/Fish-redux
複製程式碼
簡易版Provider實現
有了狀態管理的介紹,我們可以參考Provider,通過手上現有的元件,實現一個簡易版的Provider。要用到的系統元件:
- setState:StatefulWidget現成的,重新整理UI的辦法
- InheritedWidget:一個基礎widget,讓widget樹具備從上而下傳遞資料的能力。同時資料變化可以引起依賴它的widget重新構建。
- ChangeNotifier:觀察者模式的程式碼模型。
setState
狀態管理最基礎的一個實現
class _SetStateDemoWidgetState extends State<SetStateDemoWidget> {
int count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(setStateDemoTitle),),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("計數:$count"),
RaisedButton(
child: Text("increment"),
onPressed: () => setState(() => count++),
)
],
),
),
);
}
}
複製程式碼
InheritedWidget
用CountProvider繼承InheritedWidget來儲存資料。 通過context.getElementForInheritedWidgetOfExactType拿到CountProvider中的資料。 這個context一定要注意,只能用CountProvider子widget的BuildContext。CountProvider的查詢是通過context往上的。 注意這個地方,我們只是單純的拿資料,還沒有用到InheritedWidget可以控制子widget重新構建的功能。
///首先我們要有個地方存放我們的資料
class CountProvider extends InheritedWidget {
final int count;
CountProvider({Key key, this.count, Widget child})
: super(key: key, child: child);
@override
bool updateShouldNotify(CountProvider old) {
return true;
}
}
class ProviderDemoWidget1 extends StatefulWidget {
ProviderDemoWidget1({Key key}) : super(key: key);
@override
_ProviderDemoWidget1State createState() => _ProviderDemoWidget1State();
}
class _ProviderDemoWidget1State extends State<ProviderDemoWidget1> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(providerDemoTitle1),
),
body: CountProvider(
count: _count,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Builder(
builder: (context2) {
CountProvider provider = context2
.getElementForInheritedWidgetOfExactType<CountProvider>()
.widget;
return Text("計數:${provider.count}");
},
),
/// 讀取和顯示計數
RaisedButton(
child: Text("increment"),
onPressed: () => setState(() => _count++),
),
Text(providerDemoIntroduction1),
],
),
),
),
);
}
}
複製程式碼
Provider(InheritedWidget + ChangeNotifier)
我們先看效果圖,我們看到有三種場景。只有依賴的元件更新了UI。
- CountModel封裝示例1中的count,繼承ChangeNotifier,具備notify能力。
class CountModel extends ChangeNotifier {
int count;
CountModel(this.count);
void increment() {
count++;
notifyListeners();
}
}
複製程式碼
- Provider繼承InheritedWidget,封裝兩種訪問: context.dependOnInheritedWidgetOfExactType和context.getElementForInheritedWidgetOfExactType. 當InheritedWidget重新構建是,前者widget會重新構建,後者則不會。
class Provider<T extends ChangeNotifier> extends InheritedWidget {
final T model;
Provider({Key key, this.model, Widget child}) : super(key: key, child: child);
static T of<T extends ChangeNotifier>(BuildContext context, bool depend) {
if (depend) {
return context.dependOnInheritedWidgetOfExactType<Provider>().model;
} else {
Provider provider =
context.getElementForInheritedWidgetOfExactType<Provider>().widget;
return provider.model;
}
}
@override
bool updateShouldNotify(Provider old) {
return true;
}
}
複製程式碼
- ChangeNotifierProvider通過監聽ChangeNotifier,將setState封裝在其內部。總之,把狀態管理(資料修改和Widget重新整理)封裝在自定義的物件內。外部控制元件不需要再關心狀態管理的細節了。
class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget {
final Widget child;
final T model;
ChangeNotifierProvider({this.child, this.model});
@override
_ChangeNotifierProviderState createState() => _ChangeNotifierProviderState();
}
class _ChangeNotifierProviderState extends State<ChangeNotifierProvider> {
_ChangeNotifierProviderState();
_update() {
setState(() => {});
}
@override
void initState() {
super.initState();
widget.model.addListener(_update);
}
@override
void dispose() {
super.dispose();
widget.model.removeListener(_update);
}
@override
Widget build(BuildContext context) {
return Provider(
model: widget.model,
child: widget.child,
);
}
}
複製程式碼
- 有了 CountModel,Provider,ChangeNotifierProvider,簡易版狀態管理Provider庫也就寫好了,下面用起來:
class ProviderDemoWidget3 extends StatefulWidget {
ProviderDemoWidget3({Key key}) : super(key: key);
@override
_ProviderDemoWidget3State createState() => _ProviderDemoWidget3State();
}
class _ProviderDemoWidget3State extends State<ProviderDemoWidget3> {
CountModel _countModel = CountModel(0);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(providerDemoTitle3), ),
body: ChangeNotifierProvider<CountModel>(
model: _countModel,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Builder(builder: (context1) {
return Text(
"計數:${Provider.of<CountModel>(context1, true).count}(有依賴情況)");
}),
Builder(builder: (context2) {
return Text(
"計數:${Provider.of<CountModel>(context2, false).count}(無依賴情況)");
}),
RaisedButton(
child: Text("increment"),
onPressed: () => _countModel.increment()),
Text(providerDemoIntroduction3),
],
),
),
),
);
}
}
複製程式碼
基於上個版本的通用泛型封裝
上一個示例中CountModel不具有通用性,所以我們寫一個泛型版本
class Provider<T extends ChangeNotifier> extends InheritedWidget {
final T model;
Provider({Key key, this.model, Widget child}) : super(key: key, child: child);
static T of<T extends ChangeNotifier>(BuildContext context, bool depend) {
if (depend) {
return context.dependOnInheritedWidgetOfExactType<Provider>().model;
} else {
Provider provider =
context.getElementForInheritedWidgetOfExactType<Provider>().widget;
return provider.model;
}
}
@override
bool updateShouldNotify(Provider old) {
return true;
}
}
複製程式碼
class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget {
final Widget child;
final T model;
ChangeNotifierProvider({this.child, this.model});
@override
_ChangeNotifierProviderState createState() => _ChangeNotifierProviderState();
}
class _ChangeNotifierProviderState extends State<ChangeNotifierProvider> {
_ChangeNotifierProviderState();
_update() {
setState(() => {});
}
@override
void initState() {
super.initState();
widget.model.addListener(_update);
}
@override
void dispose() {
super.dispose();
widget.model.removeListener(_update);
}
@override
Widget build(BuildContext context) {
return Provider(
model: widget.model,
child: widget.child,
);
}
}
複製程式碼
class CountModel extends ChangeNotifier {
int count;
CountModel(this.count);
void increment() {
count++;
notifyListeners();
}
}
class ProviderDemoWidget3 extends StatefulWidget {
ProviderDemoWidget3({Key key}) : super(key: key);
@override
_ProviderDemoWidget3State createState() => _ProviderDemoWidget3State();
}
class _ProviderDemoWidget3State extends State<ProviderDemoWidget3> {
CountModel _countModel = CountModel(0);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(providerDemoTitle3),
),
body: ChangeNotifierProvider<CountModel>(
model: _countModel,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Builder(builder: (context1) {
return Text(
"計數:${Provider.of<CountModel>(context1, true).count}(有依賴情況)");
}),
Builder(builder: (context2) {
return Text(
"計數:${Provider.of<CountModel>(context2, false).count}(無依賴情況)");
}),
RaisedButton(
child: Text("increment"),
onPressed: () => _countModel.increment()),
Text(providerDemoIntroduction3),
],
),
),
),
);
}
}
複製程式碼
真正Provider的用法
我們發現與上一個示例的簡易版Provider上的使用方法是一致的。 正如介紹所描述的非常簡單。當然了Provider庫,可維護性,可測試性,可擴充套件性遠比我們所寫的強大。
class ProviderDemoWidget4 extends StatefulWidget {
ProviderDemoWidget4({Key key}) : super(key: key);
@override
_ProviderDemoWidget4State createState() => _ProviderDemoWidget4State();
}
class _ProviderDemoWidget4State extends State<ProviderDemoWidget4> {
CountModel _countModel = CountModel(0);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(providerDemoTitle4),
),
body: ChangeNotifierProvider.value(
value: _countModel,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Consumer<CountModel>(
builder: (contextC, model, child) {
return Text("計數:${model.count}(有依賴情況)");
},
),
Builder(builder: (context2) {
return Text(
"計數:${Provider.of<CountModel>(context2, listen: false).count}(無依賴情況)");
}),
RaisedButton(
child: Text("increment"),
onPressed: () => _countModel.increment()),
Text(providerDemoIntroduction4),
],
),
),
),
);
}
}
class CountModel extends ChangeNotifier {
int count;
CountModel(this.count);
void increment() {
count++;
notifyListeners();
}
}
複製程式碼
到此為止,我們也就瞭解了Provider的基本原理和基本使用。 可我們的征程才開始。知其然,更要知其所以然。接下來,我們從setState開始一步步分析Provider狀態管理的過程。
Demo地址
入口:main_provider.dart