flutter_bloc使用解析---騷年饭尝,你還在手搭bloc嗎肯腕!

flutter_bloc使用將從下圖的三個(gè)維度說(shuō)明

flutter_bloc

前言

  • 首先,有很多的文章在說(shuō)flutter bloc模式的應(yīng)用钥平,但是百分之八九十的文章都是在說(shuō)实撒,使用StreamController+StreamBuilder搭建bloc姊途,提升性能的會(huì)加上InheritedWidget,這些文章看了很多知态,真正寫(xiě)使用bloc作者開(kāi)發(fā)的flutter_bloc卻少之又少捷兰。沒(méi)辦法,只能去bloc的github上去找使用方式负敏,最后去bloc官網(wǎng)翻文檔贡茅。

  • 蛋痛,各位叼毛其做,就不能好好說(shuō)說(shuō)flutter_bloc的使用嗎顶考?非要各種抄bloc模式提出作者的那倆篇文章。現(xiàn)在妖泄,搞的雜家這個(gè)伸手黨要自己去翻文檔總結(jié)(手動(dòng)滑稽)驹沿。

表情1

項(xiàng)目效果(建議PC瀏覽器打開(kāi))

問(wèn)題

初次使用flutter_bloc框架,可能會(huì)有幾個(gè)疑問(wèn)

  • state里面定義了太多變量蹈胡,某個(gè)事件只需要更新其中一個(gè)變量渊季,其它的變量賦相同值麻煩
  • 進(jìn)入某個(gè)模塊,進(jìn)行初始化操作:復(fù)雜的邏輯運(yùn)算罚渐,網(wǎng)絡(luò)請(qǐng)求等却汉,入口在哪定義

準(zhǔn)備工作

說(shuō)明

  • 這里說(shuō)明下,文章里把BlocBuilder放在頂層荷并,因?yàn)楸旧眄?yè)面非常簡(jiǎn)單合砂,也是為了更好呈現(xiàn)頁(yè)面結(jié)構(gòu),所以才放在頂層源织,如果需要更加顆良饶拢化控件更新區(qū)域,請(qǐng)將BlocBuilder包裹你需要更新的控件區(qū)域即可

引用

  • 我覺(jué)得學(xué)習(xí)一個(gè)模式或者框架的時(shí)候雀鹃,最主要的是把主流程跑通,起碼可以符合標(biāo)準(zhǔn)的堆頁(yè)面励两,這樣的話黎茎,就可以把這玩意用起來(lái),再遇到想要的什么細(xì)節(jié)当悔,就可以自己去翻文檔傅瞻,畢竟大體上已經(jīng)懂了,寫(xiě)過(guò)了幾個(gè)頁(yè)面盲憎,也有些體會(huì)嗅骄,再去翻文檔就很快能理解了
  • 實(shí)際上Bloc給的API也不多,就幾個(gè)API饼疙,相關(guān)API使用說(shuō)明都寫(xiě)在文章最后

庫(kù)

flutter_bloc: ^6.1.1 #狀態(tài)管理框架
equatable: ^1.2.3 #增強(qiáng)組件相等性判斷
  • 看看flutter_bloc都推到6.0了溺森,別再用StreamController手搭Bloc了!

插件

在Android Studio設(shè)置的Plugins里,搜索:Bloc

插件搜索

安裝重啟下屏积,就OK了

  • 右擊相應(yīng)的文件夾医窿,選擇“Bloc Class”,我在main文件夾新建的炊林,填入的名字:main姥卢,就自動(dòng)生成下面三個(gè)文件;:main_bloc渣聚,main_event独榴,main_state;main_view是我自己新建奕枝,用來(lái)寫(xiě)頁(yè)面的棺榔。
新建bloc文件
目錄結(jié)構(gòu)新建bloc文件
  • 是不是覺(jué)得,還在手動(dòng)新建這些bloc文件low爆了倍权;就好像fish_redux掷豺,不用插件,讓我手動(dòng)去創(chuàng)建那六個(gè)文件薄声,寫(xiě)那些模板代碼当船,真的要原地爆炸。

Bloc范例

效果

  • 好了默辨,嗶嗶了一堆德频,看下咱們要用flutter_bloc實(shí)現(xiàn)的效果。
bloc演示
  • 直接開(kāi)Chrome演示缩幸,大家在虛擬機(jī)上跑也一樣壹置。

初始化代碼

來(lái)看下這三個(gè)生成的bloc文件:main_bloc,main_event表谊,main_state

  • main_bloc:這里就是咱們主要寫(xiě)邏輯的頁(yè)面了
    • mapEventToState方法只有一個(gè)參數(shù)钞护,后面自動(dòng)帶了一個(gè)逗號(hào),格式化代碼就分三行了爆办,建議刪掉逗號(hào)难咕,格式化代碼。
class MainBloc extends Bloc<MainEvent, MainState> {
  MainBloc() : super(MainInitial());

  @override
  Stream<MainState> mapEventToState(
    MainEvent event,
  ) async* {
    // TODO: implement mapEventToState
  }
}
  • main_event:這里是執(zhí)行的各類事件距辆,有點(diǎn)類似fish_redux的action層
@immutable
abstract class MainEvent {}
  • main_state:狀態(tài)數(shù)據(jù)放在這里保存余佃,中轉(zhuǎn)
@immutable
abstract class MainState {}

class MainInitial extends MainState {}

實(shí)現(xiàn)

  • 主入口
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MainPage(),
    );
  }
}
  • 說(shuō)明
    • 這里對(duì)于簡(jiǎn)單的頁(yè)面,state的使用抽象狀態(tài)繼承實(shí)現(xiàn)的方式跨算,未免有點(diǎn)麻煩爆土,這里我進(jìn)行一點(diǎn)小改動(dòng),state的實(shí)現(xiàn)類別有很多诸蚕,官網(wǎng)寫(xiě)demo也有不用抽象類步势,直接class氧猬,類似實(shí)體類的方式開(kāi)搞的。
    • 相關(guān)代碼的注釋寫(xiě)的比較多立润,大家可以著重看看
  • main_bloc
    • state變量是框架內(nèi)部定義的狂窑,會(huì)默認(rèn)保存上一次同步的MainSate對(duì)象的值
class MainBloc extends Bloc<MainEvent, MainState> {
  MainBloc() : super(MainState(selectedIndex: 0, isExtended: false));

  @override
  Stream<MainState> mapEventToState(MainEvent event) async* {
    ///main_view中添加的事件,會(huì)在此處回調(diào)桑腮,此處處理完數(shù)據(jù)泉哈,將數(shù)據(jù)yield,BlocBuilder就會(huì)刷新組件
    if (event is SwitchTabEvent) {
      ///獲取到event事件傳遞過(guò)來(lái)的值,咱們拿到這值塞進(jìn)MainState中
      ///直接在state上改變內(nèi)部的值,然后yield,只能觸發(fā)一次BlocBuilder,它內(nèi)部會(huì)比較上次MainState對(duì)象,如果相同,就不build
      yield MainState()
        ..selectedIndex = event.selectedIndex
        ..isExtended = state.isExtended;
    } else if (event is IsExtendEvent) {
      yield MainState()
        ..selectedIndex = state.selectedIndex
        ..isExtended = !state.isExtended;
    }
  }
}
  • main_event:在這里就能看見(jiàn)破讨,view觸發(fā)了那些事件了丛晦;維護(hù)起來(lái)也很爽,看看這里提陶,也很快能懂頁(yè)面在干嘛了
@immutable
abstract class MainEvent extends Equatable{
  const MainEvent();
}
///切換NavigationRail的tab
class SwitchTabEvent extends MainEvent{
  final int selectedIndex;

  const SwitchTabEvent({@required this.selectedIndex});

  @override
  List<Object> get props => [selectedIndex];
}
///展開(kāi)NavigationRail,這個(gè)邏輯比較簡(jiǎn)單,就不用傳參數(shù)了
class IsExtendEvent extends MainEvent{
  const IsExtendEvent();

  @override
  List<Object> get props => [];
}
  • main_state:state有很多種寫(xiě)法烫沙,在bloc官方文檔上,不同項(xiàng)目state的寫(xiě)法也很多

    • 這邊變量名可以設(shè)置為私用隙笆,用get和set可選擇性的設(shè)置讀寫(xiě)權(quán)限锌蓄,因?yàn)槲疫@邊設(shè)置的倆個(gè)變量全是必用的,讀寫(xiě)均要撑柔,就設(shè)置公有類型瘸爽,不用下劃線“_”去標(biāo)記私有了。

    • 對(duì)于生成的模板代碼铅忿,我們?cè)谶@:去掉@immutable注解剪决,去掉abstract;

    • 這里說(shuō)下加上@immutable和abstract的作用檀训,這邊是為了標(biāo)定不同狀態(tài)柑潦,這種寫(xiě)法,會(huì)使得代碼變得更加麻煩峻凫,用state不同狀態(tài)去標(biāo)定業(yè)務(wù)事件渗鬼,代價(jià)太大,這邊用一個(gè)變量去標(biāo)定荧琼,很容易輕松代替

class MainState{
   int selectedIndex;
   bool isExtended;
  
   MainState({this.selectedIndex, this.isExtended});
}
  • main_view
    • 這邊就是咱們的界面層了乍钻,很簡(jiǎn)單,將需要刷新的組件铭腕,用BlocBuilder包裹起來(lái),使用BlocBuilder:提供的state去賦值就ok了多糠,context去添加執(zhí)行的事件累舷,context用StatelessWidget中提供的或者BlocBuilder提供的都行
class MainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return _buildBg(children: [
      //側(cè)邊欄
      _buildLeftNavigation(),

      //右邊主體內(nèi)容
      Expanded(child: Center(
        child: BlocBuilder<MainBloc, MainState>(builder: (context, state) {
          return Text(
            "選擇Index:" + state.selectedIndex.toString(),
            style: TextStyle(fontSize: 30.0),
          );
        }),
      ))
    ]);
  }

  Widget _buildBg({List<Widget> children}) {
    ///創(chuàng)建BlocProvider的,表明該P(yáng)age夹孔,我們是用MainBloc被盈,MainBloc是屬于該頁(yè)面的Bloc了
    return BlocProvider(
      create: (BuildContext context) => MainBloc(),
      child: Scaffold(
        appBar: AppBar(title: Text('Bloc')),
        body: Row(children: children),
      ),
    );
  }

  //增加NavigationRail組件為側(cè)邊欄
  Widget _buildLeftNavigation() {
    return BlocBuilder<MainBloc, MainState>(builder: (context, state) {
      return NavigationRail(
        backgroundColor: Colors.white,
        elevation: 3,
        extended: state.isExtended,
        labelType: state.isExtended
            ? NavigationRailLabelType.none
            : NavigationRailLabelType.selected,
        //側(cè)邊欄中的item
        destinations: [
          NavigationRailDestination(
            icon: Icon(Icons.add_to_queue),
            selectedIcon: Icon(Icons.add_to_photos),
            label: Text("測(cè)試一"),
          ),
          NavigationRailDestination(
            icon: Icon(Icons.add_circle_outline),
            selectedIcon: Icon(Icons.add_circle),
            label: Text("測(cè)試二"),
          ),
          NavigationRailDestination(
            icon: Icon(Icons.bubble_chart),
            selectedIcon: Icon(Icons.broken_image),
            label: Text("測(cè)試三"),
          ),
        ],
        //頂部widget
        leading: _buildNavigationTop(),
        //底部widget
        trailing: _buildNavigationBottom(),
        selectedIndex: state.selectedIndex,
        onDestinationSelected: (int index) {
          ///添加切換tab事件
          BlocProvider.of<MainBloc>(context)
              .add(SwitchTabEvent(selectedIndex: index));
        },
      );
    });
  }

  Widget _buildNavigationTop() {
    return Center(
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Container(
          width: 80,
          height: 80,
          decoration: BoxDecoration(
            shape: BoxShape.circle,
            image: DecorationImage(
              image: NetworkImage(
                "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3383029432,2292503864&fm=26&gp=0.jpg",
              ),
              fit: BoxFit.fill,
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildNavigationBottom() {
    return Container(
      child: BlocBuilder<MainBloc, MainState>(
        builder: (context, state) {
          return FloatingActionButton(
            onPressed: () {
              ///添加NavigationRail展開(kāi),收縮事件
              BlocProvider.of<MainBloc>(context).add(IsExtendEvent());
            },
            child: Icon(state.isExtended ? Icons.send : Icons.navigation),
          );
        },
      ),
    );
  }
}

Bloc范例優(yōu)化

反思

從上面的代碼來(lái)看析孽,實(shí)際存在幾個(gè)隱式問(wèn)題,這些問(wèn)題只怎,剛開(kāi)始使用時(shí)候袜瞬,沒(méi)異常的感覺(jué),但是使用bloc久了后身堡,感覺(jué)肯定越來(lái)越強(qiáng)烈

  • state問(wèn)題
    • 初始化問(wèn)題:這邊初始化是在bloc里邓尤,直接在構(gòu)造方法里面賦初值的,state中一旦變量多了贴谎,還是這么寫(xiě)汞扎,會(huì)感覺(jué)極其難受,不好管理擅这。需要優(yōu)化
    • 可以看見(jiàn)這邊我們只改動(dòng)selectedIndex或者isExtended澈魄;另一個(gè)變量不需要變動(dòng),需要保持上一次的數(shù)據(jù)仲翎,進(jìn)行了此類:state.selectedIndex或者state.isExtended賦值痹扇,一旦變量達(dá)到十幾個(gè)乃至幾十個(gè),還是如此寫(xiě)溯香,是讓人極其崩潰的鲫构。需要優(yōu)化
  • bloc問(wèn)題
    • 如果進(jìn)行一個(gè)頁(yè)面,需要進(jìn)行復(fù)雜的運(yùn)算或者請(qǐng)求接口后逐哈,才能知曉數(shù)據(jù)芬迄,進(jìn)行賦值,這里肯定需要一個(gè)初始化入口昂秃,初始化入口需要怎樣去定義呢禀梳?

插件

因?yàn)楣俜讲寮傻膶?xiě)法,和調(diào)整后寫(xiě)法差距有點(diǎn)大肠骆,而且官方插件不支持生成view層和相關(guān)設(shè)置算途,此處我就擼了一個(gè)插件,完善了相關(guān)功能

請(qǐng)注意蚀腿,wrap代碼和提示代碼片段嘴瓤,參靠了官方插件規(guī)則

Wrap Widget 規(guī)則來(lái)著:intellij_generator_plugin

快捷代碼生成規(guī)則來(lái)著: intellij_generator_plugin

  • 在Android Studio里面搜索 flutter bloc
image-20210612163803311
  • 生成模板代碼
bloc
  • 支持修改后綴
image-20210612165242515
  • Wrap Widget (alt + enter):RepositoryProvider,BlocConsumer莉钙,BlocBuilder廓脆,BlocProvider,BlocListener
image-20210612164717855
  • 輸入 bloc 可生成快捷代碼片段
image-20210612164905296

優(yōu)化實(shí)現(xiàn)

這邊完整走一下流程磁玉,讓大家能有個(gè)完整的思路

  • state:首先來(lái)看看我們對(duì)state中的優(yōu)化停忿,這邊進(jìn)行了倆個(gè)很重要優(yōu)化,增加倆個(gè)方法:init()和clone()
    • init():這里初始化統(tǒng)一用init()方法去管理
    • clone():這邊克隆方法蚊伞,是非常重要的席赂,一旦變量達(dá)到倆位數(shù)以上吮铭,就能深刻體會(huì)該方法是多么的重要
class MainState {
  int selectedIndex;
  bool isExtended;

  ///初始化方法,基礎(chǔ)變量也需要賦初值,不然會(huì)報(bào)空異常
  MainState init() {
    return MainState()
      ..selectedIndex = 0
      ..isExtended = false;
  }

  ///clone方法,此方法實(shí)現(xiàn)參考fish_redux的clone方法
  ///也是對(duì)官方Flutter Login Tutorial這個(gè)demo中copyWith方法的一個(gè)優(yōu)化
  ///Flutter Login Tutorial(https://bloclibrary.dev/#/flutterlogintutorial)
  MainState clone() {
    return MainState()
      ..selectedIndex = selectedIndex
      ..isExtended = isExtended;
  }
}
  • event
    • 這邊定義一個(gè)MainInit()初始化方法,同時(shí)去掉Equatable繼承颅停,在我目前的使用中谓晌,感覺(jué)它用處不大。癞揉。纸肉。
@immutable
abstract class MainEvent {}

///初始化事件,這邊目前不需要傳什么值
class MainInitEvent extends MainEvent {}

///切換NavigationRail的tab
class SwitchTabEvent extends MainEvent {
  final int selectedIndex;

  SwitchTabEvent({@required this.selectedIndex});
}

///展開(kāi)NavigationRail,這個(gè)邏輯比較簡(jiǎn)單,就不用傳參數(shù)了
class IsExtendEvent extends MainEvent {}
  • bloc
    • 這增加了初始化方法,請(qǐng)注意烧董,如果需要進(jìn)行異步請(qǐng)求毁靶,同時(shí)需要將相關(guān)邏輯提煉一個(gè)方法,咱們?cè)谶@里配套Future和await就能解決在異步場(chǎng)景下同步數(shù)據(jù)問(wèn)題
    • 這里使用了克隆方法逊移,可以發(fā)現(xiàn)预吆,我們只要關(guān)注自己需要改變的變量就行了,其它的變量都在內(nèi)部賦值好了胳泉,我們不需要去關(guān)注拐叉;這就大大的便捷了頁(yè)面中有很多變量,只需要變動(dòng)一倆個(gè)變量的場(chǎng)景
    • 注意:如果變量的數(shù)據(jù)未改變扇商,界面相關(guān)的widget是不會(huì)重繪的凤瘦;只會(huì)重繪變量被改變的widget
class MainBloc extends Bloc<MainEvent, MainState> {
  MainBloc() : super(MainState().init());

  @override
  Stream<MainState> mapEventToState(MainEvent event) async* {
    ///main_view中添加的事件,會(huì)在此處回調(diào)案铺,此處處理完數(shù)據(jù)蔬芥,將數(shù)據(jù)yield,BlocBuilder就會(huì)刷新組件
    if (event is MainInitEvent) {
      yield await init();
    } else if (event is SwitchTabEvent) {
      ///獲取到event事件傳遞過(guò)來(lái)的值,咱們拿到這值塞進(jìn)MainState中
      ///直接在state上改變內(nèi)部的值,然后yield,只能觸發(fā)一次BlocBuilder,它內(nèi)部會(huì)比較上次MainState對(duì)象,如果相同,就不build
      yield switchTap(event);
    } else if (event is IsExtendEvent) {
      yield isExtend();
    }
  }

  ///初始化操作,在網(wǎng)絡(luò)請(qǐng)求的情況下,需要使用如此方法同步數(shù)據(jù)
  Future<MainState> init() async {
    return state.clone();
  }

  ///切換tab
  MainState switchTap(SwitchTabEvent event) {
    return state.clone()..selectedIndex = event.selectedIndex;
  }

  ///是否展開(kāi)
  MainState isExtend() {
    return state.clone()..isExtended = !state.isExtended;
  }
}
  • view
    • view層代碼太多控汉,這邊只增加了個(gè)初始化事件笔诵,就不重新把全部代碼貼出來(lái)了,初始化操作直接在創(chuàng)建的時(shí)候姑子,在XxxBloc上使用add()方法就行了乎婿,就能起到進(jìn)入頁(yè)面,初始化一次的效果街佑;add()方法也是Bloc類中提供的谢翎,遍歷事件的時(shí)候,就特地檢查了add()這個(gè)方法是否添加了事件沐旨;說(shuō)明森逮,這是框架特地提供了一個(gè)初始化的方法
    • 這個(gè)初始化方式是在官方示例找到的
class MainPage extends StatelessWidget {
  ...
      
   Widget _buildBg({List<Widget> children}) {
    ///創(chuàng)建BlocProvider的,表明該P(yáng)age磁携,我們是用MainBloc吊宋,MainBloc是屬于該頁(yè)面的Bloc了
    return BlocProvider(
      create: (BuildContext context) => MainBloc()..add(MainInitEvent()),
      child: Scaffold(
        appBar: AppBar(title: Text('Bloc')),
        body: Row(children: children),
      ),
    );
  }
    
  ///下方其余代碼省略...........
}

搞定

  • OK,經(jīng)過(guò)這樣的優(yōu)化,解決了幾個(gè)痛點(diǎn)璃搜。實(shí)際在view中反復(fù)是要用BlocBuilder去更新view,寫(xiě)起來(lái)有點(diǎn)麻煩鳞上,這里我們可以寫(xiě)一個(gè)这吻,將其中state和context變量,往提出來(lái)的Widget方法傳值篙议,也是蠻不錯(cuò)的
  • 大家保持觀察者模式的思想就行了唾糯;觀察者(回調(diào)刷新控件)和被觀察者(產(chǎn)生相應(yīng)事件,添加事件鬼贱,去通知觀察者)移怯,bloc層是處于觀察者和被觀察者中間的一層,我們可以在bloc里面搞業(yè)務(wù)这难,搞邏輯舟误,搞網(wǎng)絡(luò)請(qǐng)求,不能搞基姻乓;拿到Event事件傳遞過(guò)來(lái)的數(shù)據(jù)嵌溢,把處理好的、符合要求的數(shù)據(jù)返回給view層的觀察者就行了蹋岩。
  • 使用框架赖草,不拘泥框架,在觀察者模式的思想上剪个,靈活的去使用flutter_bloc提供Api秧骑,這樣可以大大的縮短我們的開(kāi)發(fā)時(shí)間!

Bloc 8.0+新寫(xiě)法

破壞式改變

bloc8.0+的版本扣囊,對(duì)比之前的寫(xiě)法簡(jiǎn)直是破壞式的改變乎折,你如果升級(jí)到bloc 8.0及其以上的版本,之前寫(xiě)的bloc模式寫(xiě)法已經(jīng)完全不兼容了如暖,mapEventToState方法直接被移除了笆檀,一運(yùn)行項(xiàng)目,bloc內(nèi)部也會(huì)給出報(bào)錯(cuò)盒至,需要你手動(dòng)去注冊(cè)處理器

有一說(shuō)一酗洒,雖然是破壞式的改變寫(xiě)法,但是新寫(xiě)法是非常的優(yōu)雅枷遂,徹底改變了以前的mapEventToState方法中的各種判斷Event

這種徹底不兼容的做法樱衷,確實(shí)非常的激進(jìn),但是為了優(yōu)化的點(diǎn)酒唉,亦有可圈可點(diǎn)之處

新寫(xiě)法

  • 插件已經(jīng)支持了bloc8.0+的寫(xiě)法
image-20211210162539060

來(lái)看下版本生成代碼

  • view
class TestPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (BuildContext context) => TestBloc()..add(InitEvent()),
      child: Builder(builder: (context) => _buildPage(context)),
    );
  }

  Widget _buildPage(BuildContext context) {
    final bloc = BlocProvider.of<TestBloc>(context);

    return Container();
  }
}
  • bloc
class TestBloc extends Bloc<TestEvent, TestState> {
  TestBloc() : super(TestState().init()) {
    on<InitEvent>(_init);
  }

  void _init(InitEvent event, Emitter<TestState> emit) async {
    emit(state.clone());
  }
}
  • state
class TestState {
  TestState init() {
    return TestState();
  }

  TestState clone() {
    return TestState();
  }
}
  • event
abstract class TestEvent {}

class InitEvent extends TestEvent {}

可以發(fā)現(xiàn)Bloc層矩桂,完全不用寫(xiě)判斷了

計(jì)數(shù)器實(shí)例

寫(xiě)個(gè)計(jì)數(shù)器demo,大家來(lái)感受下

  • view
class BlBlocCounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (BuildContext context) => BlBlocCounterBloc()..add(InitEvent()),
      child: Builder(builder: (context) => _buildPage(context)),
    );
  }

  Widget _buildPage(BuildContext context) {
    final bloc = BlocProvider.of<BlBlocCounterBloc>(context);

    return Scaffold(
      appBar: AppBar(title: Text('Bloc-Bloc范例')),
      body: Center(
        child: BlocBuilder<BlBlocCounterBloc, BlBlocCounterState>(
          builder: (context, state) {
            return Text(
              '點(diǎn)擊了 ${bloc.state.count} 次',
              style: TextStyle(fontSize: 30.0),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => bloc.add(CounterIncrementEvent()),
        child: Icon(Icons.add),
      ),
    );
  }
}
  • bloc:所有事件入口痪伦,在頂部一目了然
class BlBlocCounterBloc extends Bloc<BlBlocCounterEvent, BlBlocCounterState> {
  BlBlocCounterBloc() : super(BlBlocCounterState().init()) {
    //頁(yè)面初始化時(shí)刻
    on<InitEvent>(_init);
    //計(jì)數(shù)器自增
    on<CounterIncrementEvent>(_increment);
  }

  void _init(InitEvent event, Emitter<BlBlocCounterState> emit) async {
    //處理一些初始化操作,然后刷新界面
    emit(state.clone());
  }

  ///自增
  void _increment(
    CounterIncrementEvent event,
    Emitter<BlBlocCounterState> emit,
  ) {
    state.count++;
    emit(state.clone());
  }
}
  • state
class BlBlocCounterState {
  late int count;

  BlBlocCounterState init() {
    return BlBlocCounterState()..count = 0;
  }

  BlBlocCounterState clone() {
    return BlBlocCounterState()..count = count;
  }
}
  • event
abstract class BlBlocCounterEvent {}

class InitEvent extends BlBlocCounterEvent {}

class CounterIncrementEvent extends BlBlocCounterEvent {}

總結(jié)

新寫(xiě)法侄榴,對(duì)Bloc層改動(dòng)是巨大的

可以發(fā)現(xiàn)雹锣,主要改變的就是對(duì)事件的處理;改動(dòng)后寫(xiě)法對(duì)比以前的寫(xiě)法癞蚕,優(yōu)雅了N倍

  • 所有事件入口全部歸納在一起
  • 可以輕松的從歸納事件入口蕊爵,跳轉(zhuǎn)到相應(yīng)的業(yè)務(wù)邏輯
  • 對(duì)事件的處理,不用寫(xiě)一堆判斷了h肷健(這是破壞式改變攒射,優(yōu)化的點(diǎn))

bloc層的新寫(xiě)法確實(shí)不錯(cuò),新項(xiàng)目如果用bloc恒水,可以無(wú)腦升級(jí)bloc 8.0会放,使用這種新寫(xiě)法;老項(xiàng)目頁(yè)面多的話钉凌,改動(dòng)起來(lái)咧最,成本確實(shí)非常的大,大家自己抉擇嘍

Cubit范例

  • Cubit是Bloc模式的一種簡(jiǎn)化版甩骏,去掉了event這一層窗市,對(duì)于簡(jiǎn)單的頁(yè)面,用Cubit來(lái)實(shí)現(xiàn)饮笛,開(kāi)發(fā)體驗(yàn)是大大的好啊咨察,下面介紹下該種模式的寫(xiě)法

創(chuàng)建

  • 首先創(chuàng)建Cubit一組文件,選擇“Cubit”福青,新建名稱填寫(xiě):Counter
image-20210612170053602

新建好后摄狱,他會(huì)生成三個(gè)文件:cubit,state无午,view媒役;來(lái)看下生成的代碼

模板代碼

  • counter_cubit
class CounterCubit extends Cubit<CounterState> {
  CounterCubit() : super(CounterState().init());
}
  • state
class CounterState {
  CounterState init() {
    return CounterState();
  }

  CounterState clone() {
    return CounterState();
  }
}
  • view
class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (BuildContext context) => CounterCubit(),
      child: Builder(builder: (context) => _buildPage(context)),
    );
  }

  Widget _buildPage(BuildContext context) {
    final cubit = BlocProvider.of<CounterCubit>(context);

    return Container();
  }
}

實(shí)現(xiàn)計(jì)時(shí)器

  • 來(lái)實(shí)現(xiàn)下一個(gè)灰常簡(jiǎn)單的計(jì)數(shù)器

效果

  • 來(lái)看下實(shí)現(xiàn)效果吧,這邊不上圖了宪迟,大家點(diǎn)擊下面的鏈接酣衷,可以直接體驗(yàn)Cubit模式寫(xiě)的計(jì)時(shí)器
  • 實(shí)現(xiàn)效果:點(diǎn)我體驗(yàn)實(shí)際效果

實(shí)現(xiàn)

實(shí)現(xiàn)很簡(jiǎn)單,三個(gè)文件就搞定次泽,看下流程:state -> cubit -> view

  • state:這個(gè)很簡(jiǎn)單穿仪,加個(gè)計(jì)時(shí)變量
class BlCubitCounterState {
  late int count;

  BlCubitCounterState init() {
    return BlCubitCounterState()..count = 0;
  }

  BlCubitCounterState clone() {
    return BlCubitCounterState()..count = count;
  }
}
  • cubit
    • 這邊加了個(gè)自增方法:increase()
    • event層實(shí)際是所有行為的一種整合,方便對(duì)邏輯過(guò)于復(fù)雜的頁(yè)面意荤,所有行為的一種維護(hù)啊片;但是過(guò)于簡(jiǎn)單的頁(yè)面,就那么幾個(gè)事件玖像,還單獨(dú)維護(hù)紫谷,就沒(méi)什么必要了
    • 在cubit層寫(xiě)的公共方法,在view里面能直接調(diào)用,更新數(shù)據(jù)使用:emit()
    • cubit層應(yīng)該可以算是:bloc層和event層一種結(jié)合后的簡(jiǎn)寫(xiě)
class BlCubitCounterCubit extends Cubit<BlCubitCounterState> {
  BlCubitCounterCubit() : super(BlCubitCounterState().init());

  ///自增
  void increment() => emit(state.clone()..count = ++state.count);
}
  • view
    • view層的代碼就非常簡(jiǎn)單了笤昨,點(diǎn)擊方法里面調(diào)用cubit層的自增方法就ok了
class BlCubitCounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (BuildContext context) => BlCubitCounterCubit(),
      child: Builder(builder: (context) => _buildPage(context)),
    );
  }

  Widget _buildPage(BuildContext context) {
    final cubit = BlocProvider.of<BlCubitCounterCubit>(context);

    return Scaffold(
      appBar: AppBar(title: Text('Bloc-Cubit范例')),
      body: Center(
        child: BlocBuilder<BlCubitCounterCubit, BlCubitCounterState>(
          builder: (context, state) {
            return Text(
              '點(diǎn)擊了 ${cubit.state.count} 次',
              style: TextStyle(fontSize: 30.0),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => cubit.increment(),
        child: Icon(Icons.add),
      ),
    );
  }
}

總結(jié)

在Bloc模式里面祖驱,如果頁(yè)面不是過(guò)于復(fù)雜,使用Cubit去寫(xiě)瞒窒,基本完全夠用了羹膳;但是如果業(yè)務(wù)過(guò)于復(fù)雜,還是需要用Bloc去寫(xiě)根竿,需要將所有的事件行為管理起來(lái),便于后期維護(hù)

OK就珠,Bloc的簡(jiǎn)化模塊寇壳,Cubit模式就這樣講完了,對(duì)于自己業(yè)務(wù)寫(xiě)的小項(xiàng)目妻怎,我就經(jīng)常用這個(gè)Cubit去寫(xiě)

全局Bloc

說(shuō)明

什么是全局Bloc壳炎?

  • BlocProvider介紹里面有這樣的形容:BlocProvider should be used to create new blocs which will be made available to the rest of the subtree(BlocProvider應(yīng)該被用于創(chuàng)建新的Bloc,這些Bloc將可用于其子樹(shù))
  • 這樣的話逼侦,我們只需要在主入口地方使用BlocProvider創(chuàng)建Bloc匿辩,就能使用全局的XxxBloc了,這里的全局XxxBloc榛丢,state狀態(tài)都會(huì)被保存的铲球,除非關(guān)閉app,否則state里面的數(shù)據(jù)都不會(huì)被還原晰赞!
  • 注意:在主入口創(chuàng)建的XxxBloc稼病,在主入口處創(chuàng)建了一次,在其它頁(yè)面均不需要再次創(chuàng)建掖鱼,在任何頁(yè)面只需要使用BlocBuilder然走,便可以定點(diǎn)刷新及其獲取全局XxxBloc的state數(shù)據(jù)

使用場(chǎng)景

  • 全局的主題色,字體樣式和大小等等全局配置更改戏挡;這種情況芍瑞,在需要全局屬性的地方,使用BlocBuilder對(duì)應(yīng)的全局XxxBloc泛型去刷新數(shù)據(jù)就行了
  • 跨頁(yè)面去調(diào)用事件褐墅,既然是全局的XxxBloc拆檬,這就說(shuō)明,我們可以在任何頁(yè)面掌栅,使用BlocProvider.of<XxxBloc>(context)調(diào)用全局XxxBloc中事件秩仆,這就起到了一種跨頁(yè)面調(diào)用事件的效果
    • 使用全局Bloc做跨頁(yè)面事件時(shí),應(yīng)該明白猾封,當(dāng)你關(guān)閉Bloc對(duì)應(yīng)的頁(yè)面澄耍,對(duì)應(yīng)全局Bloc中的并不會(huì)被回收,下次進(jìn)入頁(yè)面,頁(yè)面的數(shù)據(jù)還是上次退出頁(yè)面修改的數(shù)據(jù)齐莲,這里應(yīng)該使用StatefulWidget痢站,在initState生命周期處,初始化數(shù)據(jù)选酗;或者在dispose生命周期處阵难,還原數(shù)據(jù)源
    • 思考下:全局Bloc對(duì)象存在周期是在整個(gè)App存活周期,必然不能創(chuàng)建過(guò)多的全局Bloc芒填,跨頁(yè)面?zhèn)鬟f事件使用全局Bloc應(yīng)當(dāng)只能做折中方案

效果圖

globalBloc

使用

來(lái)看下怎么創(chuàng)建和使用全局Bloc吧呜叫!

  • 主入口配置
    • 全局的Bloc創(chuàng)建還是蠻簡(jiǎn)單的,這邊把MultiBlocProvider在Builder里面套在child上面就行了殿衰;當(dāng)然了朱庆,把MultiBlocProvider套在MaterialApp上也是可以的
    • 這樣我們就獲得一個(gè)全局的SpanOneCubit
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MainPage(),
      builder: (BuildContext context, Widget child) {
        return MultiBlocProvider(
          providers: [
            ///此處通過(guò)BlocProvider創(chuàng)建的Bloc或者Cubit是全局的
            BlocProvider<SpanOneCubit>(
              create: (BuildContext context) => SpanOneCubit(),
            ),
          ],
          child: child,
        );
      },
    );
  }
}

需要用倆個(gè)Bloc模塊來(lái)演示,這里分別用SpanOneCubitSpanTwoCubit來(lái)演示闷祥,其中SpanOneCubit是全局的

SpanOneCubit

  • state
    • 先來(lái)看看State模塊的代碼娱颊,這里很簡(jiǎn)單,只定義了count變量
class SpanOneState {
  int count;

  ///初始化方法
  SpanOneState init() {
    return SpanOneState()..count = 0;
  }

  ///克隆方法,針對(duì)于刷新界面數(shù)據(jù)
  SpanOneState clone() {
    return SpanOneState()..count = count;
  }
}
  • view
    • 這個(gè)頁(yè)面僅僅是展示計(jì)數(shù)變量的變化凯砍,因?yàn)樵谥魅肟谑褂昧?code>BlocProvider創(chuàng)建了SpanOneCubit箱硕,所以在這個(gè)頁(yè)面不需要再次創(chuàng)建层玲,直接使用BlocBuilder便可以獲取其state
    • 可以發(fā)現(xiàn)勾栗,這個(gè)頁(yè)面使用了StatefulWidget,在initState周期中一膨,初始化了數(shù)據(jù)源局待;這樣斑响,每次進(jìn)入頁(yè)面,數(shù)據(jù)源就不會(huì)保存為上一次改動(dòng)的來(lái)钳榨,都會(huì)被初始化為我們想要的值舰罚;這個(gè)頁(yè)面能接受到任何頁(yè)面調(diào)用其事件,這樣就實(shí)現(xiàn)類似于廣播的一種效果(
class CubitSpanOnePage extends StatefulWidget {
  @override
  _SpanOnePageState createState() => _SpanOnePageState();
}

class _SpanOnePageState extends State<CubitSpanOnePage> {
  @override
  void initState() {
    BlocProvider.of<BlocSpanOneCubit>(context).init();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(title: Text('跨頁(yè)面-One')),
      floatingActionButton: FloatingActionButton(
        onPressed: () => BlocProvider.of<BlocSpanOneCubit>(context).toSpanTwo(context),
        child: const Icon(Icons.arrow_forward_outlined),
      ),
      body: Center(
        child: BlocBuilder<BlocSpanOneCubit, BlocSpanOneState>(
          builder: (context, state) {
            return Text(
              'SpanTwoPage點(diǎn)擊了 ${state.count} 次',
              style: TextStyle(fontSize: 30.0),
            );
          },
        ),
      ),
    );
  }
}
  • cubit
    • cubit里面有三個(gè)事件薛耻,初始化营罢,跳轉(zhuǎn)頁(yè)面,計(jì)數(shù)自增
class SpanOneCubit extends Cubit<SpanOneState> {
  SpanOneCubit() : super(SpanOneState().init());

  void init() {
    emit(state.init());
  }

  ///跳轉(zhuǎn)到跨頁(yè)面
  void toSpanTwo(BuildContext context) {
    Navigator.push(context, MaterialPageRoute(builder: (context) => SpanTwoPage()));
  }

  ///自增
  void increase() {
    state..count = ++state.count;
    emit(state.clone());
  }
}

SpanTwoCubit

  • state
    • 使用count饼齿,記錄下我們點(diǎn)擊自增的次數(shù)
class SpanTwoState {
  int count;

  ///初始化方法
  SpanTwoState init() {
    return SpanTwoState()..count = 0;
  }

  ///克隆方法,針對(duì)于刷新界面數(shù)據(jù)
  SpanTwoState clone() {
    return SpanTwoState()..count = count;
  }
}
  • view
    • 這地方我們需要?jiǎng)?chuàng)建使用BlocProvider一個(gè)SpanTwoCubit饲漾,這是使用Bloc的常規(guī)流程
    • 在自增的點(diǎn)擊事件里,我們調(diào)用本模塊和SpanOneCubit中的自增方法缕溉,OK考传,這里我們就能同步的改變SpanOneCubit模塊的數(shù)據(jù)了!
class CubitSpanTwoPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => BlocSpanTwoCubit()..init(context),
      child: Builder(builder: (context) => _buildPage(context)),
    );
  }

  Widget _buildPage(BuildContext context) {
    final cubit = BlocProvider.of<BlocSpanTwoCubit>(context);

    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(title: Text('跨頁(yè)面-Two')),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          //改變SpanOneCubit模塊數(shù)據(jù)
          BlocProvider.of<BlocSpanOneCubit>(context).increase();

          //改變當(dāng)前頁(yè)面數(shù)據(jù)
          cubit.increase();
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
        child: BlocBuilder<BlocSpanTwoCubit, BlocSpanTwoState>(
          builder: (context, state) {
            return Text('當(dāng)前點(diǎn)擊了 ${state.count} 次',
                style: TextStyle(fontSize: 30.0));
          },
        ),
      ),
    );
  }
}
  • cubit
    • 平平無(wú)奇的業(yè)務(wù)代碼
class SpanTwoCubit extends Cubit<SpanTwoState> {
  SpanTwoCubit() : super(SpanTwoState().init());

  void init(BuildContext context){
    emit(state.init());
  }

  ///自增
  void increase() => emit(state.clone()..count = ++state.count);
}

總結(jié)

OK证鸥,這樣便用全局Bloc實(shí)現(xiàn)了類似廣播的一種效果

  • 使用全局去刷新:主題僚楞,字體樣式和大小之類勤晚,每個(gè)頁(yè)面都要使用BlocBuilder對(duì)應(yīng)的全局bloc去刷新對(duì)應(yīng)的全局view模塊

Bloc API說(shuō)明

BlocBuilder

BlocBuilder是Flutter窗口小部件,需要Blocbuilder函數(shù)泉褐。BlocBuilder處理構(gòu)建小部件以響應(yīng)新?tīng)顟B(tài)赐写。BlocBuilder與非常相似,StreamBuilder但具有更簡(jiǎn)單的API膜赃,可以減少所需的樣板代碼量挺邀。該builder函數(shù)可能會(huì)被多次調(diào)用,并且應(yīng)該是一個(gè)純函數(shù)跳座,它會(huì)根據(jù)狀態(tài)返回小部件端铛。

看看BlocListener是否要響應(yīng)狀態(tài)更改“執(zhí)行”任何操作,例如導(dǎo)航疲眷,顯示對(duì)話框等沦补。

如果省略cubit參數(shù),BlocBuilder將使用BlocProvider和當(dāng)前函數(shù)自動(dòng)執(zhí)行查找BuildContext咪橙。

BlocBuilder<BlocA, BlocAState>(
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

僅當(dāng)您希望提供一個(gè)范圍僅限于單個(gè)窗口小部件且無(wú)法通過(guò)父級(jí)BlocProvider和當(dāng)前類訪問(wèn)的bloc時(shí),才指定該bloc BuildContext虚倒。

BlocBuilder<BlocA, BlocAState>(
  cubit: blocA, // provide the local cubit instance
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

為了對(duì)何時(shí)builder調(diào)用該函數(shù)進(jìn)行細(xì)粒度的控制美侦,buildWhen可以提供一個(gè)可選的選項(xiàng)。buildWhen獲取先前的塊狀態(tài)和當(dāng)前的塊狀態(tài)并返回一個(gè)布爾值魂奥。如果buildWhen返回true菠剩,builder將使用進(jìn)行調(diào)用,state并且小部件將重新生成耻煤。如果buildWhen返回false具壮,builder則不會(huì)調(diào)用state且不會(huì)進(jìn)行重建。

BlocBuilder<BlocA, BlocAState>(
  buildWhen: (previousState, state) {
    // return true/false to determine whether or not
    // to rebuild the widget with state
  },
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

BlocProvider

BlocProvider是Flutter小部件哈蝇,可通過(guò)為其子元素提供塊BlocProvider.of<T>(context)棺妓。它用作依賴項(xiàng)注入(DI)小部件,以便可以將一個(gè)塊的單個(gè)實(shí)例提供給子樹(shù)中的多個(gè)小部件炮赦。

在大多數(shù)情況下怜跑,BlocProvider應(yīng)使用它來(lái)創(chuàng)建新的bloc,這些bloc將可用于其余子樹(shù)吠勘。在這種情況下性芬,由于BlocProvider負(fù)責(zé)創(chuàng)建塊,它將自動(dòng)處理關(guān)閉bloc剧防。

BlocProvider(
  create: (BuildContext context) => BlocA(),
  child: ChildA(),
);

默認(rèn)情況下植锉,BlocProvider將懶惰地創(chuàng)建bloc,這意味著create當(dāng)通過(guò)查找塊時(shí)將執(zhí)行該bloc BlocProvider.of<BlocA>(context)峭拘。

要覆蓋此行為并強(qiáng)制create立即運(yùn)行俊庇,lazy可以將其設(shè)置為false狮暑。

BlocProvider(
  lazy: false,
  create: (BuildContext context) => BlocA(),
  child: ChildA(),
);

在某些情況下,BlocProvider可用于向小部件樹(shù)的新部分提供現(xiàn)有的bloc暇赤。當(dāng)需要將現(xiàn)有bloc用于新路線時(shí)心例,這將是最常用的。在這種情況下鞋囊,BlocProvider由于不會(huì)創(chuàng)建bloc止后,因此不會(huì)自動(dòng)關(guān)閉該bloc。

BlocProvider.value(
  value: BlocProvider.of<BlocA>(context),
  child: ScreenA(),
);

然后從ChildAScreenA中檢索BlocA

// with extensions
context.read<BlocA>();

// without extensions
BlocProvider.of<BlocA>(context)復(fù)制到剪貼板錯(cuò)誤復(fù)制的

MultiBlocProvider

MultiBlocProvider是Flutter小部件溜腐,可將多個(gè)BlocProvider小部件合并為一個(gè)译株。 MultiBlocProvider提高了可讀性,消除了嵌套多個(gè)元素的需求BlocProviders挺益。通過(guò)使用歉糜,MultiBlocProvider我們可以從:

BlocProvider<BlocA>(
  create: (BuildContext context) => BlocA(),
  child: BlocProvider<BlocB>(
    create: (BuildContext context) => BlocB(),
    child: BlocProvider<BlocC>(
      create: (BuildContext context) => BlocC(),
      child: ChildA(),
    )
  )
)

至:

MultiBlocProvider(
  providers: [
    BlocProvider<BlocA>(
      create: (BuildContext context) => BlocA(),
    ),
    BlocProvider<BlocB>(
      create: (BuildContext context) => BlocB(),
    ),
    BlocProvider<BlocC>(
      create: (BuildContext context) => BlocC(),
    ),
  ],
  child: ChildA(),
)

BlocListener

BlocListener是Flutter小部件,它帶有BlocWidgetListener和一個(gè)可選Bloc望众,listener以響應(yīng)bloc中的狀態(tài)變化匪补。它應(yīng)用于需要在每次狀態(tài)更改時(shí)發(fā)生一次的功能,例如導(dǎo)航烂翰,顯示a SnackBar夯缺,顯示aDialog等。

listener`與in和函數(shù)不同甘耿,每次狀態(tài)更改(**不**包括初始狀態(tài))僅被調(diào)用一次踊兜。`builder``BlocBuilder``void

如果省略cubit參數(shù),BlocListener將使用BlocProvider和當(dāng)前函數(shù)自動(dòng)執(zhí)行查找BuildContext佳恬。

BlocListener<BlocA, BlocAState>(
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  child: Container(),
)

僅當(dāng)您希望提供無(wú)法通過(guò)BlocProvider和當(dāng)前訪問(wèn)的bloc時(shí)捏境,才指定該bloc BuildContext

BlocListener<BlocA, BlocAState>(
  cubit: blocA,
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  child: Container()
)

為了對(duì)何時(shí)listener調(diào)用該函數(shù)進(jìn)行細(xì)粒度的控制毁葱,listenWhen可以提供一個(gè)可選的選項(xiàng)垫言。listenWhen獲取先前的bloc狀態(tài)和當(dāng)前的bloc狀態(tài)并返回一個(gè)布爾值。如果listenWhen返回true倾剿,listener將使用調(diào)用state骏掀。如果listenWhen返回false,listener則不會(huì)調(diào)用state柱告。

BlocListener<BlocA, BlocAState>(
  listenWhen: (previousState, state) {
    // return true/false to determine whether or not
    // to call listener with state
  },
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  child: Container(),
)

MultiBlocListener

MultiBlocListener是Flutter小部件截驮,可將多個(gè)BlocListener小部件合并為一個(gè)。 MultiBlocListener提高了可讀性际度,消除了嵌套多個(gè)元素的需求BlocListeners葵袭。通過(guò)使用,MultiBlocListener我們可以從:

BlocListener<BlocA, BlocAState>(
  listener: (context, state) {},
  child: BlocListener<BlocB, BlocBState>(
    listener: (context, state) {},
    child: BlocListener<BlocC, BlocCState>(
      listener: (context, state) {},
      child: ChildA(),
    ),
  ),
)

至:

MultiBlocListener(
  listeners: [
    BlocListener<BlocA, BlocAState>(
      listener: (context, state) {},
    ),
    BlocListener<BlocB, BlocBState>(
      listener: (context, state) {},
    ),
    BlocListener<BlocC, BlocCState>(
      listener: (context, state) {},
    ),
  ],
  child: ChildA(),
)

BlocConsumer

BlocConsumer公開(kāi)builderlistener以便對(duì)新?tīng)顟B(tài)做出反應(yīng)乖菱。BlocConsumer與嵌套類似BlocListener坡锡,BlocBuilder但減少了所需的樣板數(shù)量蓬网。BlocConsumer僅應(yīng)在需要重建UI和執(zhí)行其他對(duì)狀態(tài)更改進(jìn)行響應(yīng)的情況下使用cubitBlocConsumer取需要BlocWidgetBuilderBlocWidgetListener和任選的cubit鹉勒,BlocBuilderConditionBlocListenerCondition帆锋。

如果cubit省略該參數(shù),BlocConsumer將使用BlocProvider和當(dāng)前函數(shù)自動(dòng)執(zhí)行查找 BuildContext禽额。

BlocConsumer<BlocA, BlocAState>(
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

可選的listenWhen锯厢,buildWhen可以實(shí)現(xiàn),以更精細(xì)地控制何時(shí)listenerbuilder被調(diào)用脯倒。在listenWhenbuildWhen將在每個(gè)被調(diào)用cubit state的變化实辑。它們各自采用先前的state和當(dāng)前的,state并且必須返回a bool藻丢,以確定是否將調(diào)用builderand / orlistener函數(shù)剪撬。以前state會(huì)被初始化為statecubit的時(shí)候BlocConsumer被初始化。listenWhen并且buildWhen是可選的悠反,如果未實(shí)現(xiàn)残黑,則默認(rèn)為true

BlocConsumer<BlocA, BlocAState>(
  listenWhen: (previous, current) {
    // return true/false to determine whether or not
    // to invoke listener with state
  },
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  buildWhen: (previous, current) {
    // return true/false to determine whether or not
    // to rebuild the widget with state
  },
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

RepositoryProvider

RepositoryProvider是Flutter小部件斋否,它通過(guò)為其子節(jié)點(diǎn)提供存儲(chǔ)庫(kù)RepositoryProvider.of<T>(context)萍摊。它用作依賴項(xiàng)注入(DI)小部件,以便可以將存儲(chǔ)庫(kù)的單個(gè)實(shí)例提供給子樹(shù)中的多個(gè)小部件如叼。BlocProvider應(yīng)該用于提供塊,而RepositoryProvider只能用于存儲(chǔ)庫(kù)穷劈。

RepositoryProvider(
  create: (context) => RepositoryA(),
  child: ChildA(),
);

然后ChildA我們可以通過(guò)以下方式檢索Repository實(shí)例:

// with extensions
context.read<RepositoryA>();

// without extensions
RepositoryProvider.of<RepositoryA>(context)

MultiRepositoryProvider

MultiRepositoryProvider是Flutter小部件笼恰,將多個(gè)RepositoryProvider小部件合并為一個(gè)。 MultiRepositoryProvider提高了可讀性歇终,消除了嵌套多個(gè)元素的需求RepositoryProvider社证。通過(guò)使用,MultiRepositoryProvider我們可以從:

RepositoryProvider<RepositoryA>(
  create: (context) => RepositoryA(),
  child: RepositoryProvider<RepositoryB>(
    create: (context) => RepositoryB(),
    child: RepositoryProvider<RepositoryC>(
      create: (context) => RepositoryC(),
      child: ChildA(),
    )
  )
)

至:

MultiRepositoryProvider(
  providers: [
    RepositoryProvider<RepositoryA>(
      create: (context) => RepositoryA(),
    ),
    RepositoryProvider<RepositoryB>(
      create: (context) => RepositoryB(),
    ),
    RepositoryProvider<RepositoryC>(
      create: (context) => RepositoryC(),
    ),
  ],
  child: ChildA(),
)

最后

相關(guān)地址

系列文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市奕短,隨后出現(xiàn)的幾起案子宜肉,更是在濱河造成了極大的恐慌,老刑警劉巖翎碑,帶你破解...
    沈念sama閱讀 223,126評(píng)論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谬返,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡日杈,警方通過(guò)查閱死者的電腦和手機(jī)遣铝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門(mén)佑刷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人酿炸,你說(shuō)我怎么就攤上這事瘫絮。” “怎么了填硕?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,941評(píng)論 0 366
  • 文/不壞的土叔 我叫張陵麦萤,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我廷支,道長(zhǎng)频鉴,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,294評(píng)論 1 300
  • 正文 為了忘掉前任恋拍,我火速辦了婚禮垛孔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘施敢。我一直安慰自己周荐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,295評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布僵娃。 她就那樣靜靜地躺著概作,像睡著了一般。 火紅的嫁衣襯著肌膚如雪默怨。 梳的紋絲不亂的頭發(fā)上讯榕,一...
    開(kāi)封第一講書(shū)人閱讀 52,874評(píng)論 1 314
  • 那天,我揣著相機(jī)與錄音匙睹,去河邊找鬼愚屁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛痕檬,可吹牛的內(nèi)容都是我干的霎槐。 我是一名探鬼主播,決...
    沈念sama閱讀 41,285評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼梦谜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼丘跌!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起唁桩,我...
    開(kāi)封第一講書(shū)人閱讀 40,249評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤闭树,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后荒澡,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蔼啦,經(jīng)...
    沈念sama閱讀 46,760評(píng)論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,840評(píng)論 3 343
  • 正文 我和宋清朗相戀三年仰猖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捏肢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奈籽。...
    茶點(diǎn)故事閱讀 40,973評(píng)論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鸵赫,靈堂內(nèi)的尸體忽然破棺而出衣屏,到底是詐尸還是另有隱情,我是刑警寧澤辩棒,帶...
    沈念sama閱讀 36,631評(píng)論 5 351
  • 正文 年R本政府宣布狼忱,位于F島的核電站,受9級(jí)特大地震影響一睁,放射性物質(zhì)發(fā)生泄漏钻弄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,315評(píng)論 3 336
  • 文/蒙蒙 一者吁、第九天 我趴在偏房一處隱蔽的房頂上張望窘俺。 院中可真熱鬧,春花似錦复凳、人聲如沸瘤泪。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,797評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)对途。三九已至,卻和暖如春髓棋,著一層夾襖步出監(jiān)牢的瞬間实檀,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,926評(píng)論 1 275
  • 我被黑心中介騙來(lái)泰國(guó)打工按声, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留膳犹,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,431評(píng)論 3 379
  • 正文 我出身青樓儒喊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親币呵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子怀愧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,982評(píng)論 2 361

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