Flutter開(kāi)發(fā) Provider詳解

一、為什么會(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)與ListenableProviderValueListenableProvider的使用基本相同硬爆;

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相似缀磕;

使用ChangeNotifierProviderValueListenableProvider綁定實(shí)體類時(shí)需要注意分別繼承對(duì)應(yīng)的ChangeNotifierValueNotifier

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。

盡量在 Model 中使用私有變量“_”宙搬,減少耦合

控制你的刷新范圍

在 Flutter 中笨腥,組合大于繼承的特性隨處可見(jiàn)拓哺。常見(jiàn)的 Widget 實(shí)際上都是由更小的 Widget 組合而成,直到基本組件為止脖母。為了使我們的應(yīng)用擁有更高的性能士鸥,控制 Widget 的刷新范圍便顯得至關(guān)重要。盡量使用 Consumer 來(lái)獲取祖先 Model镶奉,以維持最小刷新范圍础淤。

Provider 是如何做到狀態(tài)共享的

這個(gè)問(wèn)題實(shí)際上得分兩步。

獲取頂層數(shù)據(jù)

實(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)

數(shù)據(jù)初始化

全局?jǐn)?shù)據(jù)

當(dāng)需要獲取全局頂層數(shù)據(jù)贡蓖,并需要做一些會(huì)產(chǎn)生額外結(jié)果的時(shí)候曹鸠,main 函數(shù)是一個(gè)很好的選擇。在 main 方法中創(chuàng)建 Model 并進(jìn)行初始化的工作斥铺,這樣就只會(huì)執(zhí)行一次彻桃。

單頁(yè)面

如果我們的數(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 生命周期管理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末洼哎,一起剝皮案震驚了整個(gè)濱河市烫映,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谱净,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件擅威,死亡現(xiàn)場(chǎng)離奇詭異壕探,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)郊丛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門李请,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人厉熟,你說(shuō)我怎么就攤上這事导盅。” “怎么了揍瑟?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵白翻,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)滤馍,這世上最難降的妖魔是什么岛琼? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮巢株,結(jié)果婚禮上槐瑞,老公的妹妹穿的比我還像新娘。我一直安慰自己阁苞,他們只是感情好困檩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著那槽,像睡著了一般悼沿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上倦炒,一...
    開(kāi)封第一講書(shū)人閱讀 51,688評(píng)論 1 305
  • 那天显沈,我揣著相機(jī)與錄音,去河邊找鬼逢唤。 笑死拉讯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鳖藕。 我是一名探鬼主播魔慷,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼著恩!你這毒婦竟也來(lái)了院尔?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤喉誊,失蹤者是張志新(化名)和其女友劉穎邀摆,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體伍茄,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡栋盹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了敷矫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片例获。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖曹仗,靈堂內(nèi)的尸體忽然破棺而出榨汤,到底是詐尸還是另有隱情,我是刑警寧澤怎茫,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布收壕,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏啼器。R本人自食惡果不足惜旬渠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望端壳。 院中可真熱鬧告丢,春花似錦、人聲如沸损谦。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)照捡。三九已至颅湘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間栗精,已是汗流浹背闯参。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悲立,地道東北人鹿寨。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像薪夕,于是被迫代替她去往敵國(guó)和親脚草。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355