一、為什么會(huì)有?Provider?将塑?
因?yàn)?Flutter 與 React 技術(shù)棧的相似性扒磁,所以在 Flutter 中涌現(xiàn)了諸如flutter_redux?尾菇、flutter_dva?登馒、?flutter_mobx?匙握、?fish_flutter等前端式的狀態(tài)管理,它們大多比較復(fù)雜陈轿,而且需要對(duì)框架概念有一定理解圈纺。
而作為 Flutter 官方推薦的狀態(tài)管理?scoped_model?,又因?yàn)槠湓O(shè)計(jì)較為簡(jiǎn)單麦射,有些時(shí)候不適用于復(fù)雜的場(chǎng)景赠堵。
所以在經(jīng)歷了一端坎坷之后,今年 Google I/O 大會(huì)之后法褥,?Provider?成了 Flutter 官方新推薦的狀態(tài)管理方式之一。
它的特點(diǎn)就是:?不復(fù)雜酬屉,好理解半等,代碼量不大的情況下,可以方便組合和控制刷新顆粒度?呐萨, 而原 Google 官方倉(cāng)庫(kù)的狀態(tài)管理?flutter-provide?已宣告GG 杀饵,?provider?成了它的替代品。
二谬擦、Provider知識(shí)點(diǎn)
1切距、?Provider?的內(nèi)部?DelegateWidget?是一個(gè)?StatefulWidget?,所以可以更新且具有生命周期惨远。
2谜悟、狀態(tài)共享是使用了?InheritedProvider?這個(gè)?InheritedWidget?實(shí)現(xiàn)的。
3北秽、巧妙利用?MultiProvider?和?Consumer?封裝葡幸,實(shí)現(xiàn)了組合與刷新顆粒度控制。
三贺氓、Provider的工作原理
1蔚叨、Delegate
既然是狀態(tài)管理,那么肯定有?StatefulWidget?和?setState?調(diào)用辙培。
在?Provider?中蔑水,一系列關(guān)于??StatefulWidget?的生命周期管理和更新,都是通過(guò)各種代理完成的扬蕊,如下圖所示搀别,上面代碼中我們用到的?ChangeNotifierProvider?大致經(jīng)歷了這樣的流程:
設(shè)置到?ChangeNotifierProvider?的?ChangeNotifer?會(huì)被執(zhí)行?addListener?添加監(jiān)聽(tīng)?listener。
listener?內(nèi)會(huì)調(diào)用?StateDelegate?的?StateSetter?方法厨相,從而調(diào)用到??StatefulWidget?的?setState领曼。
當(dāng)我們執(zhí)行?ChangeNotifer?的?notifyListeners?時(shí)鸥鹉,就會(huì)最終觸發(fā)?setState?更新。
2庶骄、InheritedProvider
狀態(tài)共享肯定需要?InheritedWidget?毁渗,InheritedProvider?就是InheritedWidget?的子類,所有的?Provider?實(shí)現(xiàn)都在?build?方法中使用?InheritedProvider?進(jìn)行嵌套单刁,實(shí)現(xiàn)?value?的共享灸异。
使用方式
ChangeNotifierProvider 方式
通過(guò)調(diào)用ChangeNotifier.notifyListeners對(duì)ChangeNotifier進(jìn)行監(jiān)聽(tīng),將其公開(kāi)給它的子Widget并重建依賴項(xiàng)羔飞;
1. 綁定數(shù)據(jù)
ChangeNotifierProvider綁定數(shù)據(jù)有兩種方式:
ChangeNotifierProvider({Key key, @required ValueBuilder<T> builder, Widget child })
通過(guò)構(gòu)造器創(chuàng)建一個(gè)ChangeNotifier肺樟,在ChangeNotifierProvider移除時(shí)自動(dòng)處理;
classMyAppextendsStatelessWidget{@overrideWidgetbuild(BuildContext context){returnChangeNotifierProvider<User>(builder:(_)=>User('Flutter',0),child:MaterialApp(title:'Flutter Demo',theme:ThemeData(primarySwatch:Colors.blue),home:MyHomePage(title:'Peovider Demo')));}}
ChangeNotifierProvider.value({Key key, @required T notifier, Widget child })
通過(guò)監(jiān)聽(tīng)通知給子Widget并重建依賴項(xiàng)逻淌;
classMyAppextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnChangeNotifierProvider<User>.value(notifier:User('Flutter',0),child:MaterialApp(title:'Flutter Demo',theme:ThemeData(primarySwatch:Colors.blue),home:MyHomePage(title:'Peovider Demo')));}}
2.ListenableProvider 方式
1. 數(shù)據(jù)綁定
ListenableProvider({Key key, @required ValueBuilder<T> builder, Disposer<T> dispose, Widget child })
通過(guò)構(gòu)造器綁定數(shù)據(jù)并進(jìn)行監(jiān)聽(tīng)么伯,當(dāng)從Widget Tree中刪除時(shí)dispose要銷毀;注意:構(gòu)造器builder不可為空卡儒;
classMyAppextendsStatelessWidget{@overrideWidgetbuild(BuildContext context){returnListenableProvider<User>(builder:(_)=>User('Flutter',0),child:MaterialApp(title:'Flutter Demo',theme:ThemeData(primarySwatch:Colors.blue),home:MyHomePage(title:'Peovider Demo')));}}
ListenableProvider.value({Key key, @required T listenable, Widget child })
通過(guò).value方式對(duì)數(shù)據(jù)進(jìn)行監(jiān)聽(tīng)listenable田柔;
classMyAppextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnListenableProvider<User>.value(listenable:User('Flutter',0),child:MaterialApp(title:'Flutter Demo',theme:ThemeData(primarySwatch:Colors.blue),home:MyHomePage(title:'Peovider Demo')));}}
2. 獲取數(shù)據(jù)
Provider.of(context)方式
classProviderTextextendsStatelessWidget{@overrideWidgetbuild(BuildContext context){finaluser=Provider.of<User>(context);returnText('${user.getName}');}}
Consumer Widget構(gòu)造器方式
classProviderTextextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnConsumer<User>(builder:(context,user,_){returnText(user.getName);});}}
小結(jié)
為方便理解,結(jié)合上一節(jié)的ChangeNotifierProvider骨望,發(fā)現(xiàn)與ListenableProvider和ValueListenableProvider的使用基本相同硬爆;
classChangeNotifierProvider<TextendsChangeNotifier>extendsListenableProvider<T>implementsSingleChildCloneableWidget{}classChangeNotifierimplementsListenable{}classValueListenableProvider<T>extendsAdaptiveBuilderWidget<ValueListenable<T>,ValueNotifier<T>>implementsSingleChildCloneableWidget{}classValueNotifier<T>extendsChangeNotifierimplementsValueListenable<T>{}
分析源碼:ChangeNotifierProvider繼承自ListenableProvider且對(duì)應(yīng)的ChangeNotifier繼承自listenable;算是ListenableProvider的子類擎鸠;ValueNotifier繼承自ChangeNotifier也與ChangeNotifierProvider相似缀磕;
使用ChangeNotifierProvider和ValueListenableProvider綁定實(shí)體類時(shí)需要注意分別繼承對(duì)應(yīng)的ChangeNotifier和ValueNotifier;
classUserwithChangeNotifier{}classPersonextendsValueNotifier<User>{}
無(wú)論使用那種.value方式劣光,均建議在dispose中進(jìn)行listener的關(guān)閉袜蚕;
@overridevoiddispose(){stream.dispose();super.dispose();}
Provider分類
你也可以在 main 方法中通過(guò)下面這行代碼來(lái)禁用此提示。?Provider.debugCheckInvalidValueType = null;
這是由于 Provider 只能提供恒定的數(shù)據(jù)绢涡,不能通知依賴它的子部件刷新廷没。提示也說(shuō)的很清楚了,假如你想使用一個(gè)會(huì)發(fā)生 change 的 Provider垂寥,請(qǐng)使用下面的 Provider颠黎。
ListenableProvider
ChangeNotifierProvider
ValueListenableProvider
StreamProvider
這幾個(gè) Provider 有什么異同。
先關(guān)注?ListenableProvider / ChangeNotifierProvider?這兩個(gè)類滞项。
ListenableProvider 提供(provide)的對(duì)象是繼承了 Listenable 抽象類的子類狭归。由于無(wú)法混入,所以通過(guò)繼承來(lái)獲得 Listenable 的能力文判,同時(shí)必須實(shí)現(xiàn)其?addListener / removeListener?方法过椎,手動(dòng)管理收聽(tīng)者。顯然戏仓,這樣太過(guò)復(fù)雜疚宇,我們通常都不需要這樣做亡鼠。
class ChangeNotifier implements Listenable
而混入了?ChangeNotifier?的類自動(dòng)幫我們實(shí)現(xiàn)了聽(tīng)眾管理,所以 ListenableProvider 同樣也可以接收混入了 ChangeNotifier 的類敷待。
ChangeNotifierProvider 則更為簡(jiǎn)單间涵,它能夠?qū)ψ庸?jié)點(diǎn)提供一個(gè)?繼承?/?混入?/?實(shí)現(xiàn)?了 ChangeNotifier 的類。通常我們只需要在 Model 中?with ChangeNotifier?榜揖,然后在需要刷新?tīng)顟B(tài)的時(shí)候調(diào)用?notifyListeners?即可勾哩。
那么?ChangeNotifierProvider?和?ListenableProvider?究竟區(qū)別在哪呢,ChangeNotifierProvider 會(huì)在你需要的時(shí)候举哟,自動(dòng)調(diào)用其 _disposer 方法思劳。
static void _disposer(BuildContext context, ChangeNotifier notifier) => notifier?.dispose();
我們可以在 Model 中重寫(xiě) ChangeNotifier 的 dispose 方法,來(lái)釋放其資源妨猩。
ValueListenableProvider潜叛。
ValueListenableProvider 用于提供實(shí)現(xiàn)了?繼承?/?混入?/?實(shí)現(xiàn)?了 ValueListenable 的 Model。它實(shí)際上是專門用于處理只有一個(gè)單一變化數(shù)據(jù)的 ChangeNotifier壶硅。
class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T>
通過(guò) ValueListenable 處理的類不再需要數(shù)據(jù)更新的時(shí)候調(diào)用?notifyListeners钠导。
StreamProvider
StreamProvider?專門用作提供(provide)一條 Single Stream。我在這里僅對(duì)其核心屬性進(jìn)行講解森瘪。
T initialData:你可以通過(guò)這個(gè)屬性聲明這條流的初始值。
ErrorBuilder<T> catchError:這個(gè)屬性用來(lái)捕獲流中的 error票堵。在這條流 addError 了之后扼睬,你會(huì)能夠通過(guò)?T Function(BuildContext context, Object error)?回調(diào)來(lái)處理這個(gè)異常數(shù)據(jù)。實(shí)際開(kāi)發(fā)中它非常有用悴势。
updateShouldNotify:和之前的回調(diào)一樣窗宇,這里不再贅述。
除了這三個(gè)構(gòu)造方法都有的屬性以外特纤,StreamProvider 還有三種不同的構(gòu)造方法军俊。
StreamProvider(...):默認(rèn)構(gòu)造方法用作創(chuàng)建一個(gè) Stream 并收聽(tīng)它。
StreamProvider.controller(...):通過(guò) builder 方式創(chuàng)建一個(gè)?StreamController<T>捧存。并且在 StreamProvider 被移除時(shí)粪躬,自動(dòng)釋放 StreamController。
StreamProvider.value(...):監(jiān)聽(tīng)一個(gè)已有的 Stream 并將其 value 提供給子孫節(jié)點(diǎn)昔穴。
注意事項(xiàng)
不要所有狀態(tài)都放在全局
開(kāi)發(fā)者為了圖方便省事镰官,經(jīng)常把所有東西都放在頂層 MaterialApp 之上。嚴(yán)格區(qū)分你的全局?jǐn)?shù)據(jù)與局部數(shù)據(jù)吗货,資源不用了就要釋放泳唠!否則將會(huì)嚴(yán)重影響你的應(yīng)用 performance。
在 Flutter 中笨腥,組合大于繼承的特性隨處可見(jiàn)拓哺。常見(jiàn)的 Widget 實(shí)際上都是由更小的 Widget 組合而成,直到基本組件為止脖母。為了使我們的應(yīng)用擁有更高的性能士鸥,控制 Widget 的刷新范圍便顯得至關(guān)重要。盡量使用 Consumer 來(lái)獲取祖先 Model镶奉,以維持最小刷新范圍础淤。
這個(gè)問(wèn)題實(shí)際上得分兩步。
實(shí)際上在祖先節(jié)點(diǎn)中共享數(shù)據(jù)這件事我們已經(jīng)在之前的文章中接觸過(guò)很多次了哨苛,都是通過(guò)系統(tǒng)的 InheritedWidget 進(jìn)行實(shí)現(xiàn)的鸽凶。Provider 也不例外,在所有 Provider 的 build 方法中建峭,返回了一個(gè) InheritedProvider玻侥。
class InheritedProvider<T> extends InheritedWidget
Flutter 通過(guò)在每個(gè) Element 上維護(hù)一個(gè)?InheritedWidget?哈希表來(lái)向下傳遞 Element 樹(shù)中的信息。通常情況下亿蒸,多個(gè) Element 引用相同的哈希表凑兰,并且該表僅在 Element 引入新的?InheritedWidget?時(shí)改變。時(shí)間復(fù)雜度為 O(1) 边锁。
通知刷新這一步實(shí)際上就是使用了 Listener 模式姑食。Model 中維護(hù)了一堆聽(tīng)眾,然后 notifiedListener 通知刷新茅坛。
全局狀態(tài)需要放在頂層 MaterialApp 之上音半,優(yōu)先初始化,以便在 Navigator 以及 BuildContex控制全局狀態(tài)
當(dāng)需要獲取全局頂層數(shù)據(jù)贡蓖,并需要做一些會(huì)產(chǎn)生額外結(jié)果的時(shí)候曹鸠,main 函數(shù)是一個(gè)很好的選擇。在 main 方法中創(chuàng)建 Model 并進(jìn)行初始化的工作斥铺,這樣就只會(huì)執(zhí)行一次彻桃。
如果我們的數(shù)據(jù)只是在這個(gè)頁(yè)面中需要使用,那么你有這兩種方式可以選擇晾蜘。
StatefulWidget
在InitState()中使用?Provider.of<T>(context)是錯(cuò)誤的邻眷。
/// If [listen] is `true` (default), later value changes will trigger a new
/// [State.build] to widgets, and [State.didChangeDependencies] for
/// [StatefulWidget].
源碼中的注釋解釋了,如果這個(gè)?Provider.of<T>(context)?listen 了的話剔交,那么當(dāng) notifyListeners 的時(shí)候耗溜,就會(huì)觸發(fā) context 所對(duì)應(yīng)的 State 的 [State.build] 和 [State.didChangeDependencies] 方法。也就是說(shuō)省容,如果你使用了非 Provider 提供的數(shù)據(jù)抖拴,例如 ChangeNotifierProvider 這樣會(huì)改變依賴的類,并且獲取數(shù)據(jù)時(shí)?Provider.of<T>(context, listen: true)?選擇 listen (默認(rèn)就為 listen)的話,數(shù)據(jù)刷新時(shí)會(huì)重新運(yùn)行 didChangeDependencies 和 build 兩個(gè)方法阿宅。這樣一來(lái)對(duì) didChangeDependencies 也會(huì)產(chǎn)生副作用候衍。假如在這里請(qǐng)求了數(shù)據(jù),當(dāng)數(shù)據(jù)到來(lái)的時(shí)候洒放,又回觸發(fā)下一次請(qǐng)求蛉鹿,最終無(wú)限請(qǐng)求下去。
這里除了副作用以外還有一點(diǎn)往湿,假如數(shù)據(jù)改變是一個(gè)同步行為妖异,例如這里的 counter.increment 這樣的方法,在 didChangeDependencies 中調(diào)用的話领追,就會(huì)造成下面這個(gè)錯(cuò)誤他膳。
源碼分析
Flutter 中的 Builder 模式
在 Provider 中,各種 Provider 的原始構(gòu)造方法都有一個(gè) builder 參數(shù)绒窑,這里一般就用?(_) => XXXModel()?就行了棕孙。感覺(jué)有點(diǎn)多次一舉,為什么不能像?.value()?構(gòu)造方法那樣簡(jiǎn)潔呢些膨。
實(shí)際上蟀俊,Provider 為了幫我們管理 Model,使用到了 delegation pattern订雾。
builder 聲明的 ValueBuilder 最終被傳入代理類?BuilderStateDelegate?/?SingleValueDelegate肢预。 然后通過(guò)代理類才實(shí)現(xiàn)的 Model 生命周期管理。