Flutter App狀態(tài)管理--Provider v3.1

老生常談的幾個問題

????一.為什么需要狀態(tài)管理?

? ? 二.什么是Provider?

? ? 三.Provider如何使用(最關(guān)心的問題)?

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

? ? 五.為什么選擇 Provider唉工?? ?

? ? 回答第一個問題法严,先看兩張圖?

? ??

圖一

這個圖相信大家也很常見寄月,就是創(chuàng)建flutter工程的時的默認demo,頁面也很簡單視圖中間有個text文本手负,點擊按鈕時文本數(shù)字自增,單個頁面狀態(tài)很可控,沒有setState無法實現(xiàn)的功能茬祷。


圖二

如果不是單個頁面呢清焕,是多個頁面,其中一些頁面之間相互關(guān)聯(lián)祭犯,正常開發(fā)中不可能遇到每個頁面獨立互不影響的秸妥,如果有效的管理這些多頁面共用的狀態(tài)成為一個頭痛的問題,也解釋了為什么要多界面狀態(tài)管理沃粗。

現(xiàn)在回答第二個問題粥惧,Provider 從名字上就很容易理解,它就是用于提供數(shù)據(jù)最盅,無論是在單個頁面還是在整個?app 都有它自己的解決方案突雪,我們可以很方便的管理狀態(tài)。

如何使用Provider

? ??第一步:添加依賴

????在pubspec.yaml中添加Provider的依賴(provider: ^3.1.0)

? ??第二步:創(chuàng)建數(shù)據(jù) Model

? ??這里的 Model 實際上就是我們的狀態(tài)涡贱,它不僅儲存了我們的數(shù)據(jù)模型咏删,而且還包含了更改數(shù)據(jù)的方法,并暴露出它想要暴露出的數(shù)據(jù)问词,如圖三

? ??

圖三

這個類意圖非常清晰督函,我們的數(shù)據(jù)就是一個 int 類型的?_count,下劃線代表私有。通過?get value?把?_count?值暴露出來辰狡。并提供?increment?方法用于更改數(shù)據(jù)锋叨。

這里使用了 mixin 混入了?ChangeNotifier,這個類能夠幫駐我們自動管理所有聽眾搓译。當(dāng)調(diào)用?notifyListeners()?時悲柱,它會通知所有聽眾進行刷新。

第三步:創(chuàng)建頂層共享數(shù)據(jù)

? ??我們在 main 方法中初始化全局數(shù)據(jù)些己,如圖4,這個是在我已有項目中建的所以有一些別的引用類.

標(biāo)紅的代碼意思嘿般,runApp是優(yōu)化的后的代碼段标,通過?Provider<T>.value?能夠管理一個恒定的數(shù)據(jù),并提供給子孫節(jié)點使用炉奴。我們只需要將數(shù)據(jù)在其 value 屬性中聲明即可逼庞。在這里我們將?textSize?傳入。

而?ChangeNotifierProvider<T>.value?不僅能夠提供數(shù)據(jù)供子孫節(jié)點使用瞻赶,還可以在數(shù)據(jù)改變的時候通知所有聽眾刷新赛糟。(通過之前我們說過的?notifyListeners)

此處的?<T>?范型可省略。但是我建議大家還是進行聲明砸逊,這會使你的應(yīng)用更加健壯璧南。

除了上述幾個屬性之外?Provider<T>.value?還提供了?UpdateShouldNotify?Function,用于控制刷新時機师逸。

typedef UpdateShouldNotify<T> = bool Function(T previous, T current);

我們可以在這里傳入一個方法?(T previous, T current){...}?司倚,并獲得前后兩個 Model 的實例,然后通過比較兩個 Model 以自定義刷新規(guī)則篓像,返回 bool 表示是否需要刷新动知。默認為 previous != current 則刷新。

圖4

第四步:在子頁面中獲取狀態(tài)

獲取頂層數(shù)據(jù)最簡單的方法就是?Provider.of<T>(context);?這里的范型?<T>?指定了獲取?FirstScreen?向上尋找最近的儲存了 T 的祖先節(jié)點的數(shù)據(jù)员辩。

我們通過這個方法獲取了頂層的 CounterModel 及 textSize盒粮。并在 Text 組件中進行使用,如圖5使用起來也容易奠滑,一般在無狀態(tài)組件中放入build中丹皱。

圖五

在有狀態(tài)組件中最好使用,有些界面一進入會行網(wǎng)絡(luò)請求养叛,如果放入圖五的build中會無限調(diào)用种呐,所以根據(jù)自己的需求來決定調(diào)用方式。

圖六

如果只是應(yīng)用的話上面幾個介紹就可以使用Provider了弃甥。

下面講一下提升的方案爽室,比如我只是點擊觸發(fā)了一個按鈕,不希望當(dāng)前界面重新繪制,要怎么辦呢阔墩?

圖五中嘿架,floatingActionButton出現(xiàn)了Consumer,如果你細心看的話發(fā)會現(xiàn)啸箫。

Consumer 使用了Builder模式耸彪,收到更新通知就會通過 builder 重新構(gòu)建。Consumer<T>?代表了它要獲取哪一個祖先中的 Model忘苛。

Consumer 的 builder 實際上就是一個 Function蝉娜,它接收三個參數(shù)?(BuildContext context, T model, Widget child)。

context: context 就是 build 方法傳進來的 BuildContext 在這里就不細說了.

T:T也很簡單扎唾,就是獲取到的最近一個祖先節(jié)點中的數(shù)據(jù)模型召川。

child:它用來構(gòu)建那些與 Model 無關(guān)的部分,在多次運行 builder 中胸遇,child 不會進行重建荧呐。

然后它會返回一個通過這三個參數(shù)映射的 Widget 用于構(gòu)建自身。

在這個浮動按鈕的例子中纸镊,我們通過?Consumer?獲取到了頂層的?CounterModel?實例倍阐。并在浮動按鈕 onTap 的 callback 中調(diào)用其?increment?方法。

而且我們成功抽離出?Consumer?中不變的部分逗威,也就是浮動按鈕中心的?Icon?并將其作為 child 參數(shù)傳入 builder 方法中峰搪。


圖七

可以發(fā)現(xiàn),Consumer?就是通過?Provider.of<T>(context)?來實現(xiàn)的庵楷。但是從實現(xiàn)來講?Provider.of<T>(context)?比?Consumer?簡單好用太多罢艾,為啥我要搞得那么復(fù)雜捏。

實際上?Consumer?非常有用尽纽,它的經(jīng)典之處在于能夠在復(fù)雜項目中咐蚯,極大地縮小你的控件刷新范圍。Provider.of<T>(context)?將會把調(diào)用了該方法的 context 作為聽眾弄贿,并在?notifyListeners?的時候通知其刷新春锋。

舉個例子來說,我們的 FirstScreen 使用了?Provider.of<T>(context)?來獲取數(shù)據(jù)差凹,SecondScreen 則沒有期奔。

你在 FirstScreen 中的 build 方法中添加一個?print('first screen rebuild');

然后在 SecondScreen 中的 build 方法中添加一個?print('second screen rebuild');

點擊第二個頁面的浮動按鈕,那么你會在控制臺看到這句輸出危尿。

first screen rebuild

首先這證明了?Provider.of<T>(context)?會導(dǎo)致調(diào)用的 context 頁面范圍的刷新呐萌。

那么第二個頁面刷新沒有呢? 刷新了谊娇,但是只刷新了?Consumer?的部分肺孤,甚至連浮動按鈕中的?Icon?的不刷新我們都給控制了。你可以在?Consumer?的 builder 方法中驗證,這里不再啰嗦

假如你在你的應(yīng)用的?頁面級別?的 Widget 中赠堵,使用了?Provider.of<T>(context)小渊。會導(dǎo)致什么后果已經(jīng)顯而易見了,每當(dāng)其狀態(tài)改變的時候茫叭,你都會重新刷新整個頁面酬屉。雖然你有 Flutter 的自動優(yōu)化算法給你撐腰,但你肯定無法獲得最好的性能揍愁。

所以在這里我建議各位盡量使用?Consumer?而不是?Provider.of<T>(context)?獲取頂層數(shù)據(jù)呐萨。

以上便是一個最簡單的使用 Provider 的例子。Consumer最多支持取六個Modal不能無限所有Modal,通常情況下是夠用了吗垮。

控制你的刷新范圍

在 Flutter 中垛吗,組合大于繼承的特性隨處可見。常見的 Widget 實際上都是由更小的 Widget 組合而成烁登,直到基本組件為止。為了使我們的應(yīng)用擁有更高的性能蔚舀,控制 Widget 的刷新范圍便顯得至關(guān)重要饵沧。

我們已經(jīng)通過前面的介紹了解到了,在 Provider 中獲取 Model 的方式會影響刷新范圍赌躺。所有狼牺,請盡量使用 Consumer 來獲取祖先 Model,以維持最小刷新范圍礼患。

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


這個問題實際上得分兩步是钥。

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

實際上在祖先節(jié)點中共享數(shù)據(jù)這件事我們已經(jīng)在之前的文章中接觸過很多次了,都是通過系統(tǒng)的 InheritedWidget 進行實現(xiàn)的缅叠。

Provider 也不例外悄泥,在所有 Provider 的 build 方法中,返回了一個 InheritedProvider肤粱。

class InheritedProvider<T> extends InheritedWidget

Flutter 通過在每個 Element 上維護一個?InheritedWidget?哈希表來向下傳遞 Element 樹中的信息弹囚。通常情況下,多個 Element 引用相同的哈希表领曼,并且該表僅在 Element 引入新的?InheritedWidget?時改變鸥鹉。

通知刷新

通知刷新這一步實際上在講各種 Provider 的時候已經(jīng)講過了,其實就是使用了 Listener 模式庶骄。Model 中維護了一堆聽眾毁渗,然后 notifiedListener 通知刷新。

為什么選擇 Provider

Provider 不僅做到了提供數(shù)據(jù)单刁,而且它擁有著一套完整的解決方案灸异,覆蓋了你會遇到的絕大多數(shù)情況。就連 BLoC 未解決的那個棘手的 dispose 問題,和 ScopedModel 的侵入性問題绎狭,它也都解決了细溅。

然而它就是完美的嗎,并不是儡嘶,至少現(xiàn)在來說喇聊。Flutter Widget 構(gòu)建模式很容易在 UI 層面上組件化,但是僅僅使用 Provider蹦狂,Model 和 View 之間還是容易產(chǎn)生依賴誓篱。

我們只有通過手動將 Model 轉(zhuǎn)化為 ViewModel 這樣才能消除掉依賴關(guān)系,所以假如各位有組件化的需求凯楔,還需要另外處理窜骄。

不過對于大多數(shù)情況來說,Provider 足以優(yōu)秀摆屯,它能夠讓你開發(fā)出簡單邻遏、高性能層次清晰?的應(yīng)用虐骑。

我應(yīng)該如何選擇狀態(tài)管理

介紹了這么多狀態(tài)管理准验,你可能會發(fā)現(xiàn),一些狀態(tài)管理之間職責(zé)并不沖突廷没。例如 BLoC 可以結(jié)合 RxDart 庫變得很強大糊饱,很好用。而 BLoC 也可以結(jié)合 Provider / ScopedModel 一起使用颠黎。那我應(yīng)該選擇哪種狀態(tài)管理方式呢另锋。

我的建議是遵守以下幾點:

使用狀態(tài)管理的目的是為了讓編寫代碼變得更簡單,任何會增加你的應(yīng)用復(fù)雜度的狀態(tài)管理狭归,統(tǒng)統(tǒng)都不要用夭坪。

選擇自己能夠 hold 住的,BLoC / Rxdart / Redux / Fish-Redux 這些狀態(tài)管理方式都有一定上手難度唉铜,不要選自己無法理解的狀態(tài)管理方式台舱。

在做最終決定之前,敲一敲 demo潭流,真正感受各個狀態(tài)管理方式給你帶來的 好處/壞處 然后再做你的決定竞惋。

附上我的github學(xué)習(xí)地址:FlutterStudy,希望能幫到一起前行的你,有問題提issue灰嫉。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拆宛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子讼撒,更是在濱河造成了極大的恐慌浑厚,老刑警劉巖股耽,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異钳幅,居然都是意外死亡物蝙,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門敢艰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诬乞,“玉大人,你說我怎么就攤上這事钠导≌鸺担” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵牡属,是天一觀的道長票堵。 經(jīng)常有香客問我,道長逮栅,這世上最難降的妖魔是什么悴势? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮措伐,結(jié)果婚禮上瞳浦,老公的妹妹穿的比我還像新娘。我一直安慰自己废士,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布蝇完。 她就那樣靜靜地躺著官硝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪短蜕。 梳的紋絲不亂的頭發(fā)上氢架,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音朋魔,去河邊找鬼岖研。 笑死,一個胖子當(dāng)著我的面吹牛警检,可吹牛的內(nèi)容都是我干的孙援。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼扇雕,長吁一口氣:“原來是場噩夢啊……” “哼拓售!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起镶奉,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤础淤,失蹤者是張志新(化名)和其女友劉穎崭放,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鸽凶,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡币砂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了玻侥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片决摧。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖使碾,靈堂內(nèi)的尸體忽然破棺而出蜜徽,到底是詐尸還是另有隱情,我是刑警寧澤票摇,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布拘鞋,位于F島的核電站,受9級特大地震影響矢门,放射性物質(zhì)發(fā)生泄漏盆色。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一祟剔、第九天 我趴在偏房一處隱蔽的房頂上張望隔躲。 院中可真熱鬧,春花似錦物延、人聲如沸宣旱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浑吟。三九已至,卻和暖如春耗溜,著一層夾襖步出監(jiān)牢的瞬間组力,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工抖拴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留燎字,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓阿宅,卻偏偏與公主長得像候衍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子家夺,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容