bloc+rxdart 項目實戰,flutter的mvc方案

文章結構:web

  • bloc+rxdart的幾大優勢
  • 示例代碼簡單實現調用網絡接口,而後刷新頁面顯示
  • bloc+rxdart實現原理
  • bloc+rxdart經典使用場景,解決了業務場景中的兩個問題。

1.bloc+rxdart的幾大優勢

1).實現MVC模式

widget只作UI展現,bloc實現控制邏輯,model作數據封裝。 我在運營線的新需求開發中實際使用了bloc作了一個新的頁面,就是商機敗北頁面。 這個頁面有三級的敗北緣由選擇,敗北緣由是級聯的,還有每次選擇設備後要從新請求庫存,提交敗北等操做。ajax

2).實現跨層級model訪問和方法調用

在同一個頁面中跨widget數據獲取數據和操做比較多,例如提交敗北的時候,須要獲取多個子widget中的數值,若是沒有使用bloc的話,就須要在子widget的構造方法中傳遞model或回調方法。數組

若是頁面層級較多,這種model和回調方法就須要逐級傳遞。 例如在下圖左邊,須要把submiModel或回調方法從最上層一直傳遞到最底部,才能作到收集全部的子widget中的信息,最終集合全部的widget中的參數到submitModel中,提交敗北請求。緩存

下面右邊的代碼就是這樣逐層傳遞參數的方法,若是層級不少的話會很是頭疼,不只要重複的聲明變量,並且若是修改的話要每一個地方都改。bash

而使用了bloc的話,構造函數就特別乾淨,不須要逐層傳遞model或者回調方法。服務器

下面的兩塊代碼都是頁面widget結構中最下層的兩個widget,不須要在構造方法中傳遞任何參數或回調方法,可是一樣能調用拿到model和調用回調方法。怎麼實現的繼續往下看。:)網絡

3).不直接使用setState方法(StreamController)

以前在項目中直接使用setState會產生一些問題,例如當前State是unmounted狀態,這時直接調用setState會拋異常。異步

而使用bloc的話刷新頁面是使用以下方式。關鍵的代碼是_requestController.add ide

4). 更加簡潔的局部刷新(StreamBuilder)

咱們現有項目中有一些很是複雜的頁面,可是隻使用了一個文件,全部的業務邏輯和widget組件都在這個頁面裏面。函數

下面是一段這種混雜網絡請求、widget顯示的代碼示例。

有多個網絡請求和對應的數據顯示widget,不論哪一個請求返回或者用戶點擊事件,只要調用一個setState就能刷新頁面更新顯示。

這樣相比於多個子widget的方式就不用逐層傳遞數據和回調方法。可是破壞了面向對象開發的基本原則:信息隱藏,好比修改某個widget的一個小功能,由於在一個很大的文件中修改,其中業務邏輯錯綜複雜,改一個小功能可能就會影響其餘邏輯,產生意料不到的後果,這一塊的代碼就變得很是難以維護。

同時直接調用setState刷新整個頁面的性能也有問題,也會產生拋異常的問題。


而使用bloc的方式會更加自由,能夠像第二點中的圖中同樣使用多個子widget,在子widget中進行刷新。

也能夠直接改造上圖的代碼,在一個widget中實現局部刷新。

第四個優勢其實和第三個是一併實現的,都是經過streamBuidler和streamController,這二者是配套使用的。其實bloc就是streamBuilder+streamController+ancestorWidgetOfExactType,和web端的ajax比較相似,幾項現有技術整合出了新的東西

StreamBuilder要傳入一個stream對象才能實現刷新,而這個stream要從streamController中獲取的。

當在上面的代碼中調用_requestController.add(deviceModel)後,下面的StreamBuilder就會自動調用builder方法,實現局部刷新StreamBuilder,而不是刷新外層的InkWell。

StreamBuilder是一個Widget,能夠在任意地方插入,實現局部刷新很是簡單。

2.簡單示例代碼

要實現一個BlocProvider,現有項目中已經集成,整個項目只須要一個BlocProvider,能夠放在Utils目錄中。

實現Bloc其實就只用這一個類和flutter自帶的方法就好了,代碼很是簡單,並不須要在yaml文件中引入第三方庫。

全部的bloc都須要繼承BlocBase,一般一個bloc對應一個會刷新頁面的業務邏輯(如網絡請求、切換tab)。

bloc初始化能夠在 任意父頁面、爺爺頁面中,獲取只須要使用BlocProvider.of(context)。
使用bloc代碼以下:
有三點須要注意的。 1.bloc要保存在state中,否則會由於widget從新構建而丟失。 2.bloc須要調用dispose,在bloc中的dispose中會調用streamController的close方法,最好把bloc的dispose和create在同一個state中調用。 3.BlocProvider要在更外面一層建立。

下面是bloc建立和blocProvider的代碼。

3.bloc+rxdart實現原理

1).bloc實現原理

bloc觸發刷新的方式就是使用StreamBuilder+StreamController,可是直接使用StreamController有一個很大的缺點就是隻能進行一對一的listen,且模式很是單一。因此須要使用到rxdart,下面會講到。

bloc另外一重要特性就是跨層級獲取bloc,在bloc能夠獲取到model或者調用方法。

其中使用了ancestorWidgetOfExactType,由於flutter widget是樹狀結構的,結構層次保存在BuildContext中,能夠從子節點依次往父節點找符合類型的widget,因此全部使用了BlocProvider包裹的widget均可以經過of方法找到,再返回widget中的bloc。(bloc最終是保存在外一層的state中)。

final type = _typeOf<BlocProvider<T>>();
BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
複製代碼

在下圖中,最下層的widget調用of方法後,能夠直接獲取到頂層頁面的submitBloc,由於flutter widget是樹狀結構的,結構層次保存在BuildContext中,能夠從子節點依次往父節點找符合類型的widget,因此全部使用了BlocProvider包裹的widget均可以經過of方法找到,再返回widget中的bloc。(bloc最終是保存在外一層的state中)。

2).rxdart實現原理

StreamController實現了觀察者模式,監聽者不是直接被調用,而是處於觀察狀態,當event加入streamController後,監聽者得到異步回調。

rxdart是對StreamController的擴展,提供了更多的模式。分爲兩個部分Subject和Observable。
其中Observable對stream封裝後提供多個處理方法,例如map、expand、merge、every、contact。

var obs = Observable(Stream.fromIterable([1,2,3,4,5]))
    .map((item)=>++item);
obs.listen(print);
輸出:2 3 4 5 6
複製代碼

Subject是對StreamController的擴展,經常使用的有如下幾種。

PublishSubject:StreamController廣播版,streamController只能有一個listener,PublishSubject能夠屢次listen。下面的其餘幾種Subject也都是廣播版。

BehaviorSubject: 緩存最近一次的事件。若是先發生event,後listen,也能收到緩存的event。

ReplaySubject: 緩存全部的事件,以前加入的全部event,listen後,都會順序發送過來。

4.經典使用場景

1).多個網絡圖片組件共用一個http下載。

開發zn_web_image這個網絡圖片下載緩存組件的時候,爲了測試數據加載緩存的請求,把圖片連接放在一個數組中並屢次重複。發現一樣的圖片連接在上面已經下載完成後,下面還會重複下載。

發現這是因爲每次都建立新的bloc致使的,經過使用一個bloc list解決了這個問題。

static ZNImageBLocList _getInstance() {
  if (_instance == null) {
    _instance = new ZNImageBLocList._internal();
  }
  return _instance;
}

List<ZNImageBloc> blocList;
 
ZNImageBloc getBloc(String url,ZNImageConfig config){
  for(ZNImageBloc bloc in blocList){
    if(bloc.imageUrl==url){
      return bloc;
    }
  }
  ZNImageBloc bloc = new ZNImageBloc(url, config);
  blocList.add(bloc);
  return bloc;
}
複製代碼
class ZnWebImageState extends State<ZnWebImage> {
  ZNImageBloc bloc;

  @override
  void dispose() {
    // TODO: implement dispose
    bloc.dispose();
    super.dispose();
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();

    bloc = ZNImageBLocList.instance.getBloc(widget.url, widget.config);
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return BlocProvider<ZNImageBloc>(
      bloc: bloc,
      child: ZNImageContainer(),
    );
  }
}
複製代碼

這樣多個zn_web_image的widget就能共用同一個bloc,在下載過程當中同步顯示進度條,下載完成後同時收到通知顯示下載好的圖片。

2).tabBarView中多個頁面共用一個是否經過認證屬性。

bloc在徵信項目中也解決了一個很頭疼的問題,就是否經過認證的狀態,這個狀態全局共用,且有多個變動入口,認證狀態變動後要求全部頁面更新顯示。

用到我的認證的地方有多個入口:

1.第一個tab中的首頁頂部。

2.確認訂單頁面,能夠從第一個tab首頁進入,也能夠從第二個tab的訂單列表進入。

3.第四個tab的我的中心頁面。

最開始沒有使用bloc的時候,寫了多個網絡請求獲取認證狀態,isAuth屬性也在每一個頁面分別保存。

1.首頁經過requestAuth從服務器端獲取isAuth屬性。

2.確認訂單頁面也須要從requestAuth獲取isAuth屬性。雖然首頁經過請求獲取到了isAuth屬性,從首頁進入確認訂單頁面能夠傳遞isAuth屬性。可是從訂單列表進入時沒有這個屬性,訂單列表是和首頁同時初始化的,仍是須要網絡請求。

3.我的中心頁面也須要從requestAuth獲取isAuth屬性。雖然首頁經過請求獲取到了isAuth屬性,可是我的中心頁面是和首頁同時在tabbarView中初始化的,不能經過構造方法傳遞isAuth屬性。

第二個問題就是從其中一個頁面進入認證頁面完成認證後,其餘全部頁面都須要刷新認證狀態,沒有使用bloc時,在這些頁面的生命週期方法中寫了從新發起網絡請求來刷新isAuth狀態。

這樣很是的麻煩,並且工做量大不少。

使用bloc以後,isAuth這樣全局共用的屬性,只須要在MaterialApp外面加一層BlocProvider就好了,任何一個子頁面都能直接獲取isAuth,而且能同步isAuth的狀態更新,刷新UI顯示。

//我的認證bloc
CreditAuthBloc authBloc;

@override
void dispose() {
  authBloc.dispose();
  super.dispose();
}

@override
void initState() {
  super.initState();
  authBloc = CreditAuthBloc();
}


@override
Widget build(BuildContext context) {
  return BlocProvider<CreditAuthBloc>(bloc: authBloc,child: MaterialApp(
    title: '',
    initialRoute:'/',
    home: Scaffold(
        body: Column(
          children: <Widget>[
            Expanded(
              child: TabBarView(physics: NeverScrollableScrollPhysics(),controller: tabController, children: [
                ZNMarketPage(),//首頁
                ZNOrderList(),//訂單列表
                ZNBill(),
                ZNMyCenter(),//我的中心
              ]),
            ),
          ],
        )),
  ),);
}
複製代碼

子頁面獲取bloc

//使用bloc中的auth
//  int userAuthStatus = 0; //我的認證 0: 未認證, 1: 已認證
  CreditAuthBloc authBloc;
  
  @override
  void didChangeDependencies() {
    // TODO: implement didChangeDependencies
    super.didChangeDependencies();
    authBloc = BlocProvider.of<CreditAuthBloc>(context);
  }
複製代碼

經過streamBuilder使用bloc

相關文章
相關標籤/搜索