[Flutter Package]狀態管理之BLoC的封裝

1、狀態管理難題

在Flutter中有一個頗有爭論的部分——揪淨該怎樣狀態管理前端

我一開始堅持移動端的思路,使用MVC或者MVP等,還有的同窗支持前端思路,使用Redux或者RxDart等,也有人說Google推薦使用BLoC,還有像我翻譯的一篇文章同樣:[譯]讓我來幫你理解和選擇Flutter狀態管理方案, 使用Redux和BLoC的混合。c#

  • MVC和MPV中狀態和刷新是分離的
  • Redux把全部狀態存到一塊兒有些臃腫,也會存在UI沒必要要的刷新,這在App上須要儘可能避免
  • BLoC是使用Stream或者RxDart等工具,將數據(狀態)獨立出去,而後當狀態有更新的時候,數據使用者自動更新

我一直在想,能不能儘可能使用在iOS上開發的方式來作,後來發現行不通,緣由是flutter的渲染方式和移動端徹底不一樣,它採用的React的思路。設計模式

移動端的UI控件能夠經過修改其屬性改變外觀,可是flutter和RN,改變樣式基本是靠從新渲染,因此想要更新內容,就要改變state,而後再經過setState()更新UI。bash

因此flutter裏更新UI,先天是割裂的,這一點和React同樣,因此就須要觀察者(或是什麼相似的)設計模式的封裝,來下降更新UI的複雜程度,減小耦合或者過多的狀態聲明。app

2、BLoC介紹

說了這麼多,其實就是想說,BLoC仍是值得一試的,他能解決狀態和UI揉雜在一塊兒的問題,也沒有Redux這麼重,適合用於簡單業務場景的數據同步。less

BLoC是一種設計模式,官方並無給出封裝的代碼,網上搜到的代碼大同小異(不知道是否是Google給的最佳實踐),可是他們的共同特色就是,初始值賦值上有問題,UI的初始值,沒有用BLoC的數據部分給出的初始值,只是剛好二者值相等,那當須要改變初始值的時候,就須要改不少處,這顯然不可接受。(緣由這裏就再也不贅述了)ide

剛剛也說了,BLoC的實現,使用了Stream或者RxDart,我傾向於使用Stream,由於它是內置的庫,RxDart功能我沒有評估,可是顯然針對簡單業務場景,過於重了。函數

關於BLoC的實現細節,這裏推薦你們看鑫磊的文章:Flutter | 狀態管理探索篇——BLoC(三),我在這裏不詳細說工具

BLoC的結構以下:post

我也不知道這應該歸類爲觀察者模式,仍是生產者消費者模式,總之是:

  • BLoC數據模塊,持有一個StreamController,來管理stream

  • UI須要展現數據的地方,使用StreamBuilder來監聽stream變化

  • 當stream裏的數據變化時,就會自動刷新子UI。

  • 須要更新數據時,好比點擊了某個按鈕,則會操做BLoC數據模塊,經過其StreamController更新裏面的數據,這樣UI就會自動刷新了。

3、BLoC封裝

不知不覺囉嗦了這麼多,其實這篇文章的目的是對BLoC進行封裝,使Stream類再也不暴露在業務代碼中。

Pub倉庫:

realank_flutter_bloc
國內鏡像

封裝分紅三部分:

RLKBaseBLoC: 數據類,存儲了任意類型的數據data(範型),還有一個改變數據的changeData方法。data供數據使用者來使用,而數據操做者使用changeData改變數據。

你能夠繼承它來定義本身的數據內容,同時增長一些方法,用於更新數據。 建立數據實例很簡單,把要存儲的初始數據傳進構造函數就能夠了:

class CountBLoC extends RLKBaseBLoC<int> {
//RLKBaseBLoC子類,增長了自加方法
  CountBLoC(int data) : super(data);
  increment() {
    changeData(data + 1);
  }
}

 CountBLoC(0);//RLKBaseBLoC實例
複製代碼

RLKBloCProvider: 這個是一個Widget,保存了RLKBaseBLoC實例,example中它包住了整個MaterialApp,它的做用就是爲須要用到數據的地方提供數據來源,它只要是全部使用到RLKBaseBLoC數據的widegt的共用根節點就能夠。若是你的RLKBaseBLoC數據,只是在頁面A中有不少地方展現,那麼RLKBloCProvider只須要包住頁面A。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RLKBloCProvider(//包住了整個APP,由於數據要被兩個頁面使用
      bloc: CountBLoC(0),//保存了RLKBaseBLoC實例
      child: MaterialApp(
        theme: ThemeData.light(),
        home: TopPage(),
      ),
    );
  }
}
複製代碼

RLKBLoCBuilder: 當須要使用RLKBaseBLoC裏面的數據的時候,就在這個widget外圍包住RLKBLoCBuilder,它的做用就是給你提供RLKBaseBLoC數據實例,好比example中,保存RLKBaseBLoC實例的RLKBloCProvider放在了main.dart中,可是當我想在TopPage中使用RLKBaseBLoC實例的時候,是沒法直接拿到這個實例的,經過RLKBLoCBuilder,就能夠在buider方法中,以參數的形式拿到RLKBaseBLoC實例了。

Center(child: RLKBLoCBuilder(builder: (BuildContext context, int data, RLKBaseBLoC bloc) {
        return Text(
          'You hit me: $data times',
        );
      }))
複製代碼

最後,當你想更新數據的時候,一樣是經過RLKBLoCBuilder拿到RLKBaseBLoC實例,而後對數據進行操做

class _STFState extends State<UnderPage> {
  @override
  Widget build(BuildContext context) {
    return RLKBLoCBuilder(builder: (BuildContext context, int data, RLKBaseBLoC bloc) {
      CountBLoC bloc2 = bloc as CountBLoC;
      return Scaffold(
        appBar: AppBar(
          title: Text('Under Page'),
        ),
        body: Center(
            child: Text(
          "You hit me: $data times",
          style: Theme.of(context).textTheme.display1,
        )),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            setState(() {});
            bloc2.increment();//改變數據
          },
          child: Icon(Icons.add),
        ),
      );
    });
  }
}
複製代碼

4、總結

當你想要更靈活地管理狀態和UI的時候,直接使用這個package可讓你遠離複雜的Redux、RxDart、Stream等概念,只須要一個數據類和兩個容器Widegt就能夠了,數據和UI很好的分離,庫對代碼的入侵也比較少。但願能夠幫助到你。

相關文章
相關標籤/搜索