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

相关文章
相关标签/搜索