[譯]Flutter 響應(yīng)式編程:Steams 和 BLoC 實(shí)踐范例(2) - BLoC 的范圍和初始化

原文:Reactive Programming - Streams - BLoC - Practical Use Cases 是作者 Didier BoelensReactive Programming - Streams - BLoC 寫(xiě)的后續(xù)

閱讀本文前建議先閱讀前篇,前篇中文翻譯有兩個(gè)版本:

  1. [譯]Flutter響應(yīng)式編程:Streams和BLoC by JarvanMo
    忠于原作的版本

  2. Flutter中如何利用StreamBuilder和BLoC來(lái)控制Widget狀態(tài) by 吉原拉面
    省略了一些初級(jí)概念,補(bǔ)充了一些個(gè)人解讀

前言

在了解 BLoC, Reactive ProgrammingStreams 概念后星著,我又花了些時(shí)間繼續(xù)研究牵触,現(xiàn)在非常高興能夠與你們分享一些我經(jīng)常使用并且個(gè)人覺(jué)得很有用的模式(至少我是這么認(rèn)為的)处铛。這些模式為我節(jié)約了大量的開(kāi)發(fā)時(shí)間错蝴,并且讓代碼更加易讀和調(diào)試果覆。

目錄

(由于原文較長(zhǎng)呜笑,翻譯發(fā)布時(shí)進(jìn)行了分割)

  1. BlocProvider 性能優(yōu)化
    結(jié)合 StatefulWidgetInheritedWidget 兩者優(yōu)勢(shì)構(gòu)建 BlocProvider

  2. BLoC 的范圍和初始化
    根據(jù) BLoC 的使用范圍初始化 BLoC

  3. 事件與狀態(tài)管理
    基于事件(Event) 的狀態(tài) (State) 變更響應(yīng)

  4. 表單驗(yàn)證
    根據(jù)表單項(xiàng)驗(yàn)證來(lái)控制表單行為 (范例中包含了表單中常用的密碼和重復(fù)密碼比對(duì))

  5. Part Of 模式
    允許組件根據(jù)所處環(huán)境(是否在某個(gè)列表/集合/組件中)調(diào)整自身的行為

文中涉及的完整代碼可在 GitHub 查看夫否。

2. BLoC 的范圍和初始化

要回答「要在哪初始化 BLoC?」這個(gè)問(wèn)題叫胁,需要先搞清楚 BLoC 的可用范圍(scope)凰慈。

2.1. 應(yīng)用中任何地方可用

在實(shí)際應(yīng)用中,常常需要處理如用戶(hù)鑒權(quán)驼鹅、用戶(hù)檔案微谓、用戶(hù)設(shè)置項(xiàng)、購(gòu)物籃等等需要在 App 中任何組件都可訪(fǎng)問(wèn)的數(shù)據(jù)或狀態(tài)输钩,這里總結(jié)了適用這種情況的兩種 BLoC 方案:

2.1.1. 全局單例 (Global Singleton)

這種方案使用了一個(gè)不在Widget視圖樹(shù)中的 Global 對(duì)象豺型,實(shí)例化后可用供所有 Widget 使用。

bloc_singleton.dart

import 'package:rxdart/rxdart.dart';

class GlobalBloc {
  ///
  /// Streams related to this BLoC
  ///
  BehaviorSubject<String> _controller = BehaviorSubject<String>();
  Function(String) get push => _controller.sink.add;
  Stream<String> get stream => _controller;

  ///
  /// Singleton factory
  ///
  static final GlobalBloc _bloc = new GlobalBloc._internal();
  factory GlobalBloc(){
    return _bloc;
  }
  GlobalBloc._internal();
  
  ///
  /// Resource disposal
  ///
  void dispose(){
    _controller?.close();
}

GlobalBloc globalBloc = GlobalBloc();

要使用全局單例 BLoC买乃,只需要 import 后調(diào)用定義好的方法即可:

import 'global_bloc.dart';

class MyWidget extends StatelessWidget {
    @override
    Widget build(BuildContext context){
        globalBloc.push('building MyWidget'); //調(diào)用 push 方法添加數(shù)據(jù) 
        return Container();
    }
}

如果你想要一個(gè)唯一的姻氨、可從應(yīng)用中任何組件訪(fǎng)問(wèn)的 BLoC 的話(huà),這個(gè)方案還是不錯(cuò)的剪验,因?yàn)椋?/p>

  • 簡(jiǎn)單易用
  • 不依賴(lài)任何 BuildContext
  • 當(dāng)然也不需要通過(guò) context 查找 BlocProvider 的方式來(lái)獲取 BLoC
  • 釋放資源也很簡(jiǎn)單肴焊,只需將 application Widget 基于 StatefulWidget 實(shí)現(xiàn),然后重寫(xiě)其 dispose() 方法功戚,在 dispose() 中調(diào)用 globalBloc.dispose() 即可

我也不知道具體是為啥娶眷,很多較真的人反對(duì)全局單例方案,所以…我們?cè)賮?lái)看另一種實(shí)現(xiàn)方案吧…

2.1.2. 注入到視圖樹(shù)頂層

在 Flutter 中啸臀,包含所有頁(yè)面的ancestor本身必須是 MaterialApp 的父級(jí)茂浮。 這是因?yàn)轫?yè)面(或者說(shuō)Route)其實(shí)是作為所有頁(yè)面共用的 Stack 中的一項(xiàng),被包含在 OverlayEntry 中的壳咕。

換句話(huà)說(shuō)席揽,每個(gè)頁(yè)面都有自己獨(dú)立于任何其它頁(yè)面Buildcontext。這也解釋了為啥不用任何技巧是沒(méi)辦法實(shí)現(xiàn)兩個(gè)頁(yè)面(或路由)之間數(shù)據(jù)共享的谓厘。

因此幌羞,必須將 BlocProvider 作為 MaterialApp 的父級(jí)才能實(shí)現(xiàn)在應(yīng)用中任何位置都可使用 BLoC,如下所示:

bloc_on_top.dart

void main() => runApp(Application());

class Application extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider<AuthenticationBloc>(
      bloc: AuthenticationBloc(),
      child: MaterialApp(
        title: 'BLoC Samples',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: InitializationPage(),
      ),
    );
  }
}

2.2. 在子視圖樹(shù)(多個(gè)頁(yè)面或組件)中可用

大多數(shù)時(shí)候竟稳,我們只需要在應(yīng)用的部分頁(yè)面/組件樹(shù)中使用 BLoC属桦。舉個(gè)例子,在一個(gè) App 中有類(lèi)似論壇的功能模塊他爸,在這個(gè)功能模塊中我們需要用到 BLoC 來(lái)實(shí)現(xiàn):

  • 與后端服務(wù)器交互聂宾,獲取、添加诊笤、更新帖子
  • 在特定的頁(yè)面列出需要顯示的數(shù)據(jù)

顯然我們不需要將論壇的 BLoC 實(shí)現(xiàn)成全局可用系谐,只需在涉及論壇的視圖樹(shù)中可用就行了。

那么可采用通過(guò) BlocProviderBLoC 作為模塊子樹(shù)的根(父級(jí))注入的方式讨跟,如下所示:

bloc_init_root.dart

class MyTree extends StatelessWidget {
  @override
  Widget build(BuildContext context){
    return BlocProvider<MyBloc>(
      bloc: MyBloc(),
      child: Column(
        children: <Widget>[
          MyChildWidget(),
        ],
      ),
    );
  }
}

class MyChildWidget extends StatelessWidget {
  @override 
  Widget build(BuildContext context){
    MyBloc = BlocProvider.of<MyBloc>(context);
    return Container();
  }
}

這樣纪他,該模塊下所有 Widget 都可以通過(guò)調(diào)用 BlocProvider.of 來(lái)獲取 BLoC.

注意

上面給出的并不是最佳方案,因?yàn)槊看?MyTree 重構(gòu)(rebuild)時(shí)都會(huì)重新初始化 BLoC 晾匠,帶來(lái)的結(jié)果是:

  • 丟失 BLoC 中已經(jīng)存在的數(shù)據(jù)內(nèi)容
  • 重新初始化BLoC 要占用 CPU 時(shí)間

在這個(gè)例子中更好的方式是使用 StatefulWidget 茶袒,利用其持久化 State 的特性解決上述問(wèn)題,代碼如下:

bloc_init_root_2.dart

class MyTree extends StatefulWidget {
 @override
  _MyTreeState createState() => _MyTreeState();
}
class _MyTreeState extends State<MyTree>{
  MyBloc bloc;
  
  @override
  void initState(){
    super.initState();
    bloc = MyBloc();
  }
  
  @override
  void dispose(){
    bloc?.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context){
    return BlocProvider<MyBloc>(
      bloc: bloc,
      child: Column(
        children: <Widget>[
          MyChildWidget(),
        ],
      ),
    );
  }
}

這樣實(shí)現(xiàn)的話(huà)凉馆,即使 MyTree 組件重構(gòu)薪寓,也不會(huì)重新初始化 BLoC,而是直接使用之前的BLoC實(shí)例澜共。

2.3. 單一組件中可用

如果只在某一個(gè)組件(Widget)中使用 BLoC向叉,只需要在該組件內(nèi)構(gòu)建 BLoC 實(shí)例即可。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末咳胃,一起剝皮案震驚了整個(gè)濱河市植康,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌展懈,老刑警劉巖销睁,帶你破解...
    沈念sama閱讀 222,378評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異存崖,居然都是意外死亡冻记,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)来惧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)冗栗,“玉大人,你說(shuō)我怎么就攤上這事∮缇樱” “怎么了钠至?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,983評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)胎源。 經(jīng)常有香客問(wèn)我棉钧,道長(zhǎng),這世上最難降的妖魔是什么涕蚤? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,938評(píng)論 1 299
  • 正文 為了忘掉前任宪卿,我火速辦了婚禮,結(jié)果婚禮上万栅,老公的妹妹穿的比我還像新娘佑钾。我一直安慰自己,他們只是感情好烦粒,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布休溶。 她就那樣靜靜地躺著,像睡著了一般撒遣。 火紅的嫁衣襯著肌膚如雪邮偎。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,549評(píng)論 1 312
  • 那天义黎,我揣著相機(jī)與錄音禾进,去河邊找鬼。 笑死廉涕,一個(gè)胖子當(dāng)著我的面吹牛泻云,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播狐蜕,決...
    沈念sama閱讀 41,063評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼宠纯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了层释?” 一聲冷哼從身側(cè)響起婆瓜,我...
    開(kāi)封第一講書(shū)人閱讀 39,991評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贡羔,沒(méi)想到半個(gè)月后廉白,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,522評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乖寒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評(píng)論 3 342
  • 正文 我和宋清朗相戀三年猴蹂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片楣嘁。...
    茶點(diǎn)故事閱讀 40,742評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡磅轻,死狀恐怖珍逸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情聋溜,我是刑警寧澤谆膳,帶...
    沈念sama閱讀 36,413評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站勤婚,受9級(jí)特大地震影響摹量,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜馒胆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凝果。 院中可真熱鬧祝迂,春花似錦、人聲如沸器净。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,572評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)山害。三九已至纠俭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間浪慌,已是汗流浹背冤荆。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,671評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留权纤,地道東北人钓简。 一個(gè)月前我還...
    沈念sama閱讀 49,159評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像汹想,于是被迫代替她去往敵國(guó)和親外邓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評(píng)論 2 361

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