Flutter中BLoC與provider的對比

本文重點分享Flutter中主流狀態(tài)管理庫:BLoC與provider的簡單用法和對比

背景

筆者在今年的惡劣行情下锚贱,終于勇敢的跳槽了碍彭。來到新公司從事自己真心追求的Flutter技術(shù)開發(fā)探越。目前手中的項目是一個完整的Flutter項目朵逝,正在不斷迭代,與我一起共同成長侧蘸。因此,我會逐步把自己的心得記錄下來鹉梨,與君共享讳癌!

前言

作為前端開發(fā)者,MVVM開發(fā)模式應(yīng)該不陌生存皂。MVVM模式是目前前端項目中的主流架構(gòu)晌坤。MVVM — — model、view旦袋、viewModel骤菠。model表示頁面狀態(tài)(即頁面綁定的數(shù)據(jù))、view表示頁面視圖疤孕、viewModel將model和view進行綁定商乎,并且做業(yè)務(wù)邏輯(包括網(wǎng)絡(luò)請求,數(shù)據(jù)更新胰柑、頁面視圖更新驅(qū)動等)的處理截亦。從而實現(xiàn)視圖爬泥、數(shù)據(jù)柬讨、業(yè)務(wù)邏輯完全分離,使得項目結(jié)構(gòu)清晰明朗袍啡,可維護度高踩官。其中,數(shù)據(jù)的改變可以看成頁面狀態(tài)的改變境输,對這些頁面狀態(tài)如何管理的更合理蔗牡,是MVVM架構(gòu)的重中之重。本篇文章最主要也是講解筆者在Flutter中嗅剖,對狀態(tài)管理的使用心得辩越。

Flutter狀態(tài)管理講解

在Flutter中,狀態(tài)管理早已是老生常談的問題了信粮。直到Flutter將provider替代provide作為官方推薦的狀態(tài)管理庫黔攒,F(xiàn)lutter關(guān)于狀態(tài)管理的爭論才開始趨于平靜。

那么狀態(tài)管理為何這么重要呢?這里有一個業(yè)務(wù)場景可以給大家體會下:

假設(shè)服務(wù)器每隔十秒通過websocket給APP推送一次數(shù)據(jù)督惰,數(shù)據(jù)包含文章內(nèi)容不傅,同時也包含閱讀數(shù)、點贊數(shù)等赏胚;APP有兩個頁面访娶,A頁面顯示文章列表,點擊列表項進入B頁面查看文章詳情觉阅。每隔十秒服務(wù)器的消息到達后崖疤,需要實時更新A、B頁面的內(nèi)容典勇。

在Flutter中戳晌,該如何實現(xiàn)?你可能想到在每個頁面注冊一個websocket的接收器痴柔,在各個頁面收到websocket消息通知的時候沦偎,通過setState去更新頁面視圖;撇開性能不講咳蔚,如果有10個頁面豪嚎,就需要定義10個接收器,每個接收器還需要分別處理數(shù)據(jù)然后setState更新視圖谈火。開發(fā)效率上已經(jīng)大打折扣侈询,出錯率極高。

在上面的例子中糯耍,作為前端開發(fā)者肯定會希望只在一個地方接收數(shù)據(jù)扔字,只要數(shù)據(jù)一改變,視圖就實時更新温技,無需每個頁面都多setState操作革为。我們把數(shù)據(jù)接收器定義成更新事件的發(fā)布者,每個頁面都是一個監(jiān)聽者舵鳞。發(fā)布者發(fā)出事件后震檩,監(jiān)聽者馬上可以收到,視圖馬上更新蜓堕。其實這就是典型的發(fā)布訂閱模式抛虏。顯而易見,大部分前端包括Flutter中的狀態(tài)管理套才,都是基于發(fā)布訂閱的設(shè)計模式出發(fā)出發(fā)的迂猴。

Flutter中的發(fā)布訂閱模式,可以直接使用stream流機制背伴。(關(guān)于stream的系統(tǒng)學(xué)習(xí)請見:https://juejin.im/post/6844904163407577096)沸毁。

以上面的例子儡率,我們需要一個websocket接收器,接收消息后通過streamController.skin.add發(fā)布事件以清,頁面中會注冊監(jiān)聽器:streamController.stream.listen儿普,在監(jiān)聽回調(diào)中實現(xiàn)setState去更新視圖。

事實上掷倔,F(xiàn)lutter目前已有的狀態(tài)管理眉孩,如rxdart、BLoC勒葱、fluter_redux浪汪、provider等,都離不開對stream流進行封裝凛虽,再加入widget的封裝演化出StreamBuilder死遭、BlocBuilder等布局組件,從而達到無需setState就能實時更新視圖的效果凯旋。(關(guān)于Flutter狀態(tài)管理的演變過程呀潭,可見:https://juejin.im/post/6844904035439345671)

進入主題

很感謝大家讀到這里,前面花費了大量文字解釋并代入Flutter的狀態(tài)管理至非,無非就是為了說明狀態(tài)管理是Flutter項目中不可獲取的一部分钠署。這篇文章重點要講的,就是如何使用荒椭、如何選用Flutter狀態(tài)管理庫谐鼎。目前一般公司最常用的狀態(tài)管理庫是:BLoC和Provider。接下來重點講解兩者的使用和比較趣惠。

BLoC

目錄結(jié)構(gòu)

BLoC是谷歌提出的一種設(shè)計模式狸棍,利用流的方式實現(xiàn)界面的異步渲染和重繪,我們可以非澄肚模快速的通過BLoC實現(xiàn)業(yè)務(wù)與界面的分離草戈。一般情況下,我們會在項目中引入flutter_bloc這個庫傍菇。一個BLoC管理猾瘸,有三個文件:bloc、event丢习、state;

BLoC目錄結(jié)構(gòu)

使用方法

當(dāng)一個組件需要使用到BLoC狀態(tài)管理時淮悼,需要在調(diào)用組件之前咐低,需要聲明下BLoC的提供者,具體寫法如下:

BlocProvider<BadgesBloc>(

????create: (context) =>BadgesBloc(),

????child: UserPage()

)

當(dāng)一個頁面有多個BLoC提供者袜腥,或者整個app有幾個通用的BLoC提供者见擦,有多個頁面都需要使用钉汗,即可提前在加載app之前全局聲明±鹇牛可以使用MultiBlocProvider進行聲明损痰,具體寫法如下:

MultiBlocProvider(

? providers: [

????BlocProvider<BadgesBloc>(create:(context) => BadgesBloc()),

????BlocProvider(create: (context) =>XXX()),

],

????child: MaterialApp()

)

使用中,頁面布局將使用BlocBuilder創(chuàng)建widget酒来,用戶在頁面中通過BlocProvider.of(context).add()發(fā)起事件卢未,

///? 布局示例

BlocBuilder<BadgesBloc, BadgesState>(

????// 接收bloc返回的state,視圖與state中的變量進行綁定

????builder: (context, state) {

????var isShowBadge = false;

????if (state is BadgesInitialState) {

????????isShowBadge = state.unReadNotification;

????}

????return Badge(

????????showBadge: isShowBadge,

????????shape: BadgeShape.circle,

????????position: BadgePosition(top: -3, right: -3),

????????child: Icon( Icons.notifications_none, color: Color(0xFFFFFFFF),

????????),

????);

})

/// 頁面發(fā)起事件

// 發(fā)出的重設(shè)Badge的事件堰汉,事件要求傳參為bool

BlocProvider.of<BadgesBloc>(context).add(ResetBadgeEvent(true));?

此時在bloc中就會接收到事件辽社,判斷發(fā)起的事件是event中的哪個事件,然后返回對應(yīng)的state翘鸭。

@override

Stream<BadgesState> mapEventToState(BadgesEvent event) async* {

????if (event is ResetBadgeEvent) {

????????yield BadgesInitialState(event.unReadNotification);?

????}?

}

Stream<BadgesInitialState> _mapGetActivityCountState(isShow) async* {

? ? // 此處更改狀態(tài)的值滴铅,讓上面的視圖代碼可以根據(jù)此值進行更新

? ? ?yield BadgesInitialState(isShow);

}

同時需要補上event、state的代碼截圖

event
state

使用心得

不難得出就乓,BLoC使用起來結(jié)構(gòu)相對復(fù)雜汉匙,每一個狀態(tài)則需要三個文件。而且發(fā)出事件時生蚁,需要用到context盹兢。即常理下,只能在build生命周期內(nèi)發(fā)出事件守伸。若是上述案例中的websocket監(jiān)聽中要發(fā)出事件绎秒,將會顯得困難,需要使用全局的context(全局的context可使用GlobalKey<NavigatorState> )尼摹。這一點勢必會成為BLoC的一個很重大的弊端(后面筆者會有詳細(xì)解釋)见芹。

Provider

Provider是Flutter官方自己維護的,也是官方最為推薦的狀態(tài)管理庫蠢涝,它的特點是:不復(fù)雜玄呛、好理解,可控度高和二。我們會在項目中引入provider這個庫徘铝,Provider沒有像BLoC那樣負(fù)責(zé)的目錄結(jié)構(gòu),下面直接講解用法惯吕。

使用方法

當(dāng)一個組件需要使用到Provider狀態(tài)管理時惕它,需要在調(diào)用組件之前,需要聲明下Provider的提供者废登,具體寫法如下:

ChangeNotifierProvider<LoginViewModel>.value(

????notifier: LoginViewModel(), ????

????child:LoginPage(),

)

當(dāng)一個頁面有多個Provider提供者淹魄,或者整個app有幾個通用的Provider提供者,有多個頁面都需要使用堡距,即可提前在加載app之前全局聲明甲锡≌捉叮可以使用MultiProvider進行聲明,具體寫法如下:

MultiProvider(

????providers: [

? ??????ChangeNotifierProvider<LoginViewModel>( create: (_) => LoginViewModel(),),

? ??????ChangeNotifierProvider<HomeViewModel>( create: (_) =>?HomeViewModel(),),

????],

????child: MaterialApp()

)

使用中缤沦,頁面布局需在build中創(chuàng)建一個provider對象虎韵,之后直接在widget中綁定viewModel中的數(shù)據(jù)或者觸發(fā)事件即可

/// 創(chuàng)建provider對象

var loginVM = Provider.of<LoginViewModel>(context);

Column(

????children: <Widget>[

????????new Padding(

????????????padding: EdgeInsets.only(top: 85),

????????????child: new Container(

????????????????height: 85.h, width: 486.w,

????????????????child: TextFormField(

? ??????????????????// 綁定viewModel的數(shù)據(jù)

????????????????????controller: loginVM.userNameController,

????????????????????decoration: InputDecoration(

????????????????????????hintText: "請輸入用戶名",

????????????????????????icon: Icon(Icons.person),

????????????????????????hintStyle: TextStyle(color: Colors.grey, fontSize: 24.sp),

????????????),

????????????validator: (value) {

????????????????return value.trim().length > 0 ? null : "必填選項"; }

????????)

????)

),

????????new Padding(

????????????padding: EdgeInsets.only(top: 40),

????????????child: new Container(

????????????????height: 90.h, width: 486.w,

????????????????child: new RaisedButton(

? ? ? ? ? ? ? ? ? ? ? ?// 點擊觸發(fā)viewModel中的方法

? ? ? ? ? ? ? ? ? ? ? onPressed: () { loginVM.loginHandel(context)},

????????????color: const Color(0xff00b4ed), shape: StadiumBorder(),

????????????child: new Text( "登錄",

????????????????????style: new TextStyle(color: Colors.white, fontSize: 32.sp),

????????????),

????????),

????)

)]

我們來看看viewModel中的寫法,class必須繼承ChangeNotifier缸废,當(dāng)有數(shù)據(jù)需要更新的時候包蓝,調(diào)用notifyListeners(),頁面就會刷新呆奕。

viewModel-1
viewModel-2

使用心得

Provider確實使用起來方便很多养晋。在更新時,無需使用頁面手動觸發(fā)梁钾,也就是無需過多的跟context打交道绳泉,可以避免BLoC使用全局key的弊端。最重要的一點是姆泻,provider非常符合我們對mvvm的一般認(rèn)識零酪,在build中聲明provider后,直接就可以調(diào)用provider中的值進行視圖渲染拇勃。

對比分析

上面簡單說明BLoC和Provider兩種最常用的狀態(tài)管理框架的用法四苇。接下來將是這篇文章的重中之重,分析兩者的區(qū)別:

一方咆、理解難易程度

BLoC是一種設(shè)計模式月腋,主要思想是基于Dart Stream流的發(fā)布訂閱者模式。使用者需要對Stream有較為深刻的理解瓣赂。另外榆骚,在使用上與傳統(tǒng)的MVVM模式還是有所差別的。相比之下煌集,Provider就更容易理解妓肢,只需在頁面中聲明一次Provider,即可直接在視圖中引用數(shù)據(jù)苫纤,理解起來特別容易碉钠。?

二、使用限制

BLoC對上下文context的依賴要更強卷拘,BLoC中發(fā)布者通過.add發(fā)布事件喊废,但發(fā)布時必須攜帶上下文context,視圖中通過BlocBuilder作為訂閱者恭金,接收到state改變時操禀,rebuild布局。相比之下横腿,Provider的viewModel只需要繼承ChangeNotifier颓屑,更新時直接notifyListeners(),隨時更新耿焊,無需綁定上下文揪惦。這一點個人認(rèn)為是provider很大的一個優(yōu)勢。

因為MVVM模式在應(yīng)用中罗侯,無非就是解決視圖和數(shù)據(jù)業(yè)務(wù)層的耦合器腋。如果在viewModel層還需要依賴context,那么在業(yè)務(wù)邏輯負(fù)責(zé)的情況下钩杰,勢必會更加麻煩纫塌。

三、易用程度

這個就無需多說了讲弄,Provider完勝BLoC措左。創(chuàng)建一個BLoC狀態(tài)管理,需要創(chuàng)建State避除、Event怎披、Bloc三個文件去管理,Provider一個搞定瓶摆。

四凉逛、 顆粒度掌控

顆粒度即局部刷新的能力,其實這也是狀態(tài)管理中最頭疼的問題群井。雖然Flutter高效的渲染機制状飞,讓我們可以忽略刷新帶來的內(nèi)存問題。

但是追求極致的程序員們书斜,就算我的頁面有100個數(shù)據(jù)诬辈,綁定在100個組件上;我改變了一個數(shù)據(jù)菩佑,我就只想頁面刷新這一個組件自晰,其他99個不動。

因此就引出一個顆粒度的掌控能力稍坯。BLoC中酬荞,只要是BlocBuilder包裹下的組件,當(dāng)State中的任一數(shù)據(jù)改變瞧哟,整個組件都會rebuild混巧,這是必然的。為了控制更小的局部刷新勤揩,只能不斷拆分更細(xì)的BLoC咧党;

同樣,Provider要做到局部刷新陨亡,可以使用Consumer控件包裹傍衡,(這里跟BlocBuilder是一個原理)深员,也需要拆分更多的viewModel層。在局部刷新這個問題上蛙埂,其實都是一樣不分伯仲倦畅,如果一定要比個高低,我覺得BLoC的做法更加直觀绣的,反正我BlocBuilder包裹的夠細(xì)叠赐,就越能精致顆粒度。

總結(jié)

綜上屡江,業(yè)界以及個人其實都會比較偏向使用provider作為狀態(tài)管理的首選庫芭概。但BLoC雖然用起來比較負(fù)責(zé),但不可否認(rèn)其在大型項目確實也挺可控惩嘉;

其實不妨可以結(jié)合使用罢洲,筆者目前的項目就是結(jié)合使用Provider和BLoC,效果也還不錯宏怔。遇到狀態(tài)復(fù)雜的奏路,就偷偷懶用Provider;一般頁面下臊诊,就使用BLoC以便更好的把控顆粒度鸽粉。倒也用的挺香,

總之抓艳,選擇哪種狀態(tài)管理沒有特定的規(guī)律触机,上手時間、可維護性玷或、開發(fā)成本都是需要考慮的主觀因素儡首,還是要視團隊和具體應(yīng)用場景而定。如果業(yè)務(wù)場景只是一個輸入框和按鈕偏友,過度的模式設(shè)計其實就會顯得畫蛇添足蔬胯。需要大家理性選擇。


希望大家多多指導(dǎo)我位他!

再會
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末氛濒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鹅髓,更是在濱河造成了極大的恐慌舞竿,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窿冯,死亡現(xiàn)場離奇詭異骗奖,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門执桌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鄙皇,“玉大人,你說我怎么就攤上這事鼻吮∮叮” “怎么了较鼓?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵椎木,是天一觀的道長。 經(jīng)常有香客問我博烂,道長香椎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任禽篱,我火速辦了婚禮畜伐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘躺率。我一直安慰自己玛界,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布悼吱。 她就那樣靜靜地躺著慎框,像睡著了一般。 火紅的嫁衣襯著肌膚如雪后添。 梳的紋絲不亂的頭發(fā)上笨枯,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音遇西,去河邊找鬼馅精。 笑死,一個胖子當(dāng)著我的面吹牛粱檀,可吹牛的內(nèi)容都是我干的洲敢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼茄蚯,長吁一口氣:“原來是場噩夢啊……” “哼压彭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起第队,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤哮塞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后凳谦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忆畅,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了家凯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缓醋。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖绊诲,靈堂內(nèi)的尸體忽然破棺而出送粱,到底是詐尸還是另有隱情,我是刑警寧澤掂之,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布抗俄,位于F島的核電站,受9級特大地震影響世舰,放射性物質(zhì)發(fā)生泄漏动雹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一跟压、第九天 我趴在偏房一處隱蔽的房頂上張望胰蝠。 院中可真熱鬧,春花似錦震蒋、人聲如沸茸塞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽钾虐。三九已至,卻和暖如春梗搅,著一層夾襖步出監(jiān)牢的瞬間禾唁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工无切, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留荡短,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓哆键,卻偏偏與公主長得像掘托,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子籍嘹,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344