美文网首页Flutter学习
Flutter 基于Bloc框架的封装

Flutter 基于Bloc框架的封装

作者: lizubing1992 | 来源:发表于2019-06-20 16:41 被阅读80次

    1.页面状态的bloc封装

    1.1 定义一个基类用于bloc用于处理页面状态

    状态主要有:loading,error,empty,以及展示内容的showContent

    enum PageEnum {
      showLoading,
      showError,
      showEmpty,
      showContent,
    }
    
    

    1.2 定义一个枚举表示页面状态,另外还需定义事件的类,传递一些必要的数据

    bloc流供baseWidget做状态的变化

    class PageStatusEvent {
      String errorDesc; //错误数据,主要是展示错误页面
      bool isRefresh;//主要用于list列表数据
      PageEnum pageStatus; 页面状态
      PageStatusEvent({this.errorDesc,this.isRefresh, this.pageStatus});
    }
    

    1.3 BaseBloc封装

    class BaseBloc {
      void dispose() {
        _pageEvent.close();
      }
      ///请求专用的类
      Repository repository = new Repository();
    
      ///主要是事件通知
      BehaviorSubject<PageStatusEvent> _pageEvent =
          BehaviorSubject<PageStatusEvent>();
    
      get pageEventSink => _pageEvent.sink;
    
      get pageEventStream => _pageEvent.stream.asBroadcastStream();
    
      postPageEmpty2PageContent(bool isRefresh, Object list) {
        pageEventSink.add(new PageStatusEvent(errorDesc : "", isRefresh: true,
            pageStatus: ObjectUtil.isEmpty(list)
                ? PageEnum.showEmpty
                : PageEnum.showContent));
      }
      postPageError(bool isRefresh, String errorMsg) {
        pageEventSink.add(
            new PageStatusEvent(errorDesc : errorMsg, isRefresh: isRefresh, pageStatus: PageEnum.showError));
      }
    }
    

    主要提供了页面状态的Stream,提供子类使用,postPageEmpty2PageContent,postPageError 主要是的页面状态调用方法

    2.BaseWidget封装

    @override
      Widget build(BuildContext context) {
        return _buildWidgetDefault();
      }
    
      ///构建默认视图
      Widget _buildWidgetDefault() {
        return WillPopScope(
          child: Scaffold(
            appBar: buildAppBar(),
            body: _buildBody(),
          ),
        );
      }
    
      ///子类实现,构建各自页面UI控件
      Widget buildWidget(BuildContext context);
    
      ///构建内容区
      Widget _buildBody() {
        bloc = BlocProvider.of<B>(context);
        return new StreamBuilder(
            stream: bloc.pageEventStream,
            builder:
                (BuildContext context, AsyncSnapshot<PageStatusEvent> snapshot) {
              PageStatusEvent status;
              bool isShowContent = false;
              if (snapshot == null || snapshot.data == null) {
                isShowContent = false;
                status =
                    PageStatusEvent(errorDesc : "", isRefresh: true, pageStatus: PageEnum.showLoading);
              } else {
                status = snapshot.data;
                if ((!status.isRefresh) ||
                    (status.pageStatus == PageEnum.showContent &&
                        status.isRefresh)) {
                  isShowContent = true;
                } else {
                  isShowContent = false;
                }
              }
              return Container(
                ///内容区背景颜色
                color: Colours.colorPrimaryWindowBg,
                child: Stack(
                  children: <Widget>[
                    buildWidget(context),
                    Offstage(
                      offstage: isShowContent,
                      child: getErrorWidget(status),
                    ),
                  ],
                ),
              );
            });
      }
    

    通过pageEventStream 事件来处理页面的状态,默认情况下展示loading状态,通过使用Stack 类似Android中的Framelayout帧布局来初始化loading页面和真正的业务布局。通过isShowContent来控制ErrorWidget视图的展示与否

     Widget getErrorWidget(PageStatusEvent status) {
        current = status.pageStatus;
        if (status != null && status.isRefresh) {
          if (status.pageStatus == PageEnum.showEmpty) {
            return _buildEmptyWidget();
          } else if (status.pageStatus == PageEnum.showError) {
            return _buildErrorWidget(status.errorDesc);
          } else {
            return _buildLoadingWidget();
          }
        }
        return _buildLoadingWidget();
      }
    

    错误页面的构建,可以自己自定义,详细的代码就不贴不来了,会根据status状态来返回对应的视图

    void showLoadSuccess() {
        if (current != PageEnum.showContent) {
          current = PageEnum.showContent;
          //展示内容
          bloc.pageEventSink
              .add(PageStatusEvent(errorDesc : "", isRefresh: true, pageStatus: PageEnum.showContent));
        }
      }
    
      void showEmpty() {
        if (current != PageEnum.showEmpty) {
          current = PageEnum.showEmpty;
          //展示空页面
          bloc.pageEventSink
              .add(PageStatusEvent(errorDesc : "", isRefresh: true, pageStatus: PageEnum.showEmpty));
        }
      }
    
      void showError() {
        if (current != PageEnum.showError) {
          current = PageEnum.showError;
          //展示错误页面
          bloc.pageEventSink
              .add(PageStatusEvent(errorDesc : "", isRefresh: true, pageStatus: PageEnum.showError));
        }
      }
    
      void showLoading() {
        if (current != PageEnum.showLoading) {
          current = PageEnum.showLoading;
          //展示loading页面
          bloc.pageEventSink
              .add(PageStatusEvent(errorDesc : "", isRefresh: true, pageStatus: PageEnum.showLoading));
        }
      }
    

    另外还需要提供子类调用的四个状态更改的方法

    3.bloc页面调用

    class BarCodeBloc extends BaseBloc {
      final BehaviorSubject<String> _qrCodeController = BehaviorSubject<String>();
    
      get onQrCodeSink => _qrCodeController.sink;
    
      get onQrCodeStream => _qrCodeController.stream;
    
      Future getQrCode(String custId) {
        repository.getQrCodeData(custId, onSuccess: (data) {
          onQrCodeSink.add(data);
          postPageEmpty2PageContent(true, data);
        }, onFailure: (error) {
          postPageError(true, error.errorDesc);
        });
      }
    
      @override
      void dispose() {
        super.dispose();
        _qrCodeController.close();
      }
    }
    

    这是一个普通的二维码页面展示,根据api接口返回数据,直接调用postPageEmpty2PageContent用于展示业务布局,以及postPageError展示网络失败的布局

    4.基本页面的使用逻辑

     @override
      Widget buildWidget(BuildContext context) {
        return new StreamBuilder(
            stream: bloc.onQrCodeStream,
            builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
              return Scaffold(
                body: Container(
                  alignment: Alignment.center,
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: <Widget>[
                      new QrImage(
                        data: snapshot.data,
                        size: Dimens.dp(200),
                      ),
                    ],
                  ),
                ),
              );
            });
      }
    

    展示其实直接调用就可以了,不需要管理页面相关的状态逻辑,都是在父类帮忙完成了

    5.List列表相关的封装

    移动端开发很大一部分都是和ListView列表有点关,最好统一封装一下

    5.1 上拉加载下拉刷新的控件封装

    基于框架 pullToRefresh

    //下拉刷新和上拉加载的回调
    typedef void OnLoadMore();
    typedef void OnRefresh();
    
    class RefreshScaffold extends StatefulWidget {
      const RefreshScaffold(
          {Key key,
          @required this.controller,
          this.enablePullUp: true,
          this.enablePullDown: true,
          this.onRefresh,
          this.onLoadMore,
          this.child,
          this.bottomBar,
          this.headerWidget,
          this.itemCount,
          this.itemBuilder})
          : super(key: key);
    
      final RefreshController controller;
      final bool enablePullUp;
      final bool enablePullDown;
      final OnRefresh onRefresh;
      final OnLoadMore onLoadMore;
      final Widget child;
      //底部按钮
      final Widget bottomBar;
      //固定header的Widget
      final PreferredSize headerWidget;
      final int itemCount;
      final IndexedWidgetBuilder itemBuilder;
    
      @override
      State<StatefulWidget> createState() {
        return new RefreshScaffoldState();
      }
    }
    
    ///   with AutomaticKeepAliveClientMixin 用于保持列表的状态
    class RefreshScaffoldState extends State<RefreshScaffold>
        with AutomaticKeepAliveClientMixin {
      @override
      void initState() {
        super.initState();
        SchedulerBinding.instance.addPostFrameCallback((_) {
          widget.controller.requestRefresh();
        });
      }
    
      @override
      Widget build(BuildContext context) {
        super.build(context);
        return new Scaffold(
          appBar: widget.headerWidget,
          body: new SmartRefresher(
              controller: widget.controller,
              enablePullDown: widget.enablePullDown,
              enablePullUp: widget.enablePullUp,
              onRefresh: widget.onRefresh,
              onLoading: widget.onLoadMore,
              footer: ListFooterView(),
              header: MaterialClassicHeader(),
              child: widget.child ??
                  new ListView.builder(
                    itemCount: widget.itemCount,
                    itemBuilder: widget.itemBuilder,
                  )),
          bottomNavigationBar: widget.bottomBar,
        );
      }
    
      @override
      bool get wantKeepAlive => true;
    }
    
    

    主要是增加保持页面状态的wantKeepAlive回调,以及对应的一些页面header或者底部Bottom的封装

    5.2 BaseListWidget封装

    /// B:对应 BLoc 数据加载的Bloc
    /// E: 列表数据Entity
    abstract class BaseListState<T extends BaseListWidget, B extends BaseBloc,
        E extends Object> extends BaseState<T, B> {
      RefreshController controller = new RefreshController();
    
      @override
      Widget buildWidget(BuildContext context) {
        bloc.pageEventStream.listen((PageStatusEvent event) {
          if (event.isRefresh) {
            controller.refreshCompleted();
            //这句有必要的,实测不加上会导致加载更多无法回调
            controller.loadComplete();
          } else {
            if (event.pageStatus == PageEnum.showEmpty) {
              controller.loadNoData();
            } else if (event.pageStatus == PageEnum.showError) {
              controller.loadFailed();
            } else {
              controller.loadComplete();
            }
          }
        });
        return new StreamBuilder(
            stream: blocStream,
            builder: (BuildContext context, AsyncSnapshot<List<E>> snapshot) {
              return RefreshScaffold(
                controller: controller,
                enablePullDown: isLoadMore(),
                onRefresh: onRefresh,
                onLoadMore: onLoadMore,
                child: new ListView.builder(
                  itemCount: snapshot.data == null ? 0 : snapshot.data.length,
                  itemBuilder: (BuildContext context, int index) {
                    E model = snapshot.data[index];
                    return buildItem(model);
                  },
                ),
                bottomBar: buildBottomBar(),
                headerWidget: buildHeaderWidget(),
              );
            });
      }
    
      ///默认存在分页
      bool isLoadMore() {
        return true;
      }
    
      ///加载数据
      get blocStream;
    
      ///刷新回调
      void onRefresh();
    
      ///加载回调
      void onLoadMore();
    
      ///构建Item
      Widget buildItem(E entity);
    
      @override
      void onErrorClick() {
        super.onErrorClick();
        controller.requestRefresh();
      }
    
      @override
      void dispose() {
        controller.dispose();
        super.dispose();
      }
    
      Widget buildBottomBar() {
        return null;
      }
    
      PreferredSize buildHeaderWidget() {
        return null;
      }
    

    提供三个泛型来控制布局的相关的数据,B对应Bloc,E对应的列表的实体类,提供了blocStream 的Bloc 刷新回调onRefresh,onLoadMore加载更多回调,构建item的回调等

    pageEventStream的监听主要处理下来刷新和加载更多的逻辑

     bloc.pageEventStream.listen((PageStatusEvent event) {
          if (event.isRefresh) {
            controller.refreshCompleted();
            //这句有必要的,实测不加上会导致加载更多无法回调
            controller.loadComplete();
          } else {
            if (event.pageStatus == PageEnum.showEmpty) {
              controller.loadNoData();
            } else if (event.pageStatus == PageEnum.showError) {
              controller.loadFailed();
            } else {
              controller.loadComplete();
            }
          }
        });
    

    5.3 普通的列表调用方式

    只需要实现对应的方法即可,代码就比较清爽了

    class _LoanVisitPageState
        extends BaseListState<LoanVisitPage, LoanVisitBloc, TaskEntity> {
      final String labelId;
    
      _LoanVisitPageState(this.labelId);
    
      @override
      void onRefresh() {
        bloc.onRefresh(labelId: labelId);
      }
    
      @override
      void onLoadMore() {
        bloc.onLoadMore(labelId: labelId);
      }
    
      @override
      String setEmptyMsg() {
        return Ids.noVisitTask;
      }
    
      @override
      get blocStream => bloc.loanVisitStream;
    
      @override
      Widget buildItem(TaskEntity entity) {
        return new LoanVisitItem(entity);
      }
    }
    
    

    5.4 list普通列表的bloc调用

    class LoanVisitBloc extends BaseBloc {
      BehaviorSubject<List<TaskEntity>> _loanVisit =
          BehaviorSubject<List<TaskEntity>>();
    
      get _loanVisitSink => _loanVisit.sink;
    
      get loanVisitStream => _loanVisit.stream;
    
      List<TaskEntity> _reposList = new List();
      int _taskPage = 1;
    
     //列表数据请求
      Future getLoanVisitList(String labelId, int page) {
        bool isRefresh;
        if (page == 1) {
          _reposList.clear();
          isRefresh = true;
        } else {
          isRefresh = false;
        }
        return repository.getVisitList(NetApi.RETURN_VISIT, page, 20,
            onSuccess: (list) {
          _reposList.addAll(list);
          _loanVisitSink.add(UnmodifiableListView<TaskEntity>(_reposList));
          postPageEmpty2PageContent(isRefresh, list);
        }, onFailure: (error) {
          postPageError(isRefresh, error.errorDesc);
        });
      }
    
      @override
      void dispose() {
        _loanVisit.close();
      }
    
      @override
      Future getData({String labelId, int page}) {
        return getLoanVisitList(labelId, page);
      }
    
      @override
      Future onLoadMore({String labelId}) {
        _taskPage +=1 ;
        return getData(labelId: labelId, page: _taskPage);
      }
    
      @override
      Future onRefresh({String labelId}) {
        _taskPage = 1;
        return getData(labelId: labelId, page: 1);
      }
    }
    

    提供刷新和加载更多的方法,也是比较一般的请求

    6.总结

    借鉴了很多网上的文章,并且结合项目做了修改,bloc的好处避免了setState的损耗,对于页面的状态的管理是很好的,后续会提供一个demo点击链接

    相关文章

      网友评论

        本文标题:Flutter 基于Bloc框架的封装

        本文链接:https://www.haomeiwen.com/subject/tgyjqctx.html