[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很好的分离,库对代码的入侵也比较少。但愿能够帮助到你。

相关文章
相关标签/搜索