[Flutter Package]状态管理之BLoC的封装

一、状态管理难题

在Flutter中有一个很有争论的部分——揪净到底应该怎样状态管理

我一开始坚持移动端的思路,使用MVC或者MVP等,还有的同学支持前端思路,使用Redux或者RxDart等,也有人说Google推荐使用BLoC,还有像我翻译的一篇文章一样:[译]让我来帮你理解和选择Flutter状态管理方案, 使用Redux和BLoC的混合。

  • MVC和MPV中状态和刷新是分离的
  • Redux把所有状态存到一起有些臃肿,也会存在UI不必要的刷新,这在App上需要尽量避免
  • BLoC是使用Stream或者RxDart等工具,将数据(状态)独立出去,然后当状态有更新的时候,数据使用者自动更新

我一直在想,能不能尽量使用在iOS上开发的方式来做,后来发现行不通,原因是flutter的渲染方式和移动端完全不同,它采用的React的思路。

移动端的UI控件可以通过修改其属性改变外观,但是flutter和RN,改变样式基本是靠重新渲染,所以想要更新内容,就要改变state,然后再通过setState()更新UI。

所以flutter里更新UI,先天是割裂的,这一点和React一样,所以就需要观察者(或是什么类似的)设计模式的封装,来降低更新UI的复杂程度,减少耦合或者过多的状态声明。

二、BLoC介绍

说了这么多,其实就是想说,BLoC还是值得一试的,他能解决状态和UI揉杂在一起的问题,也没有Redux这么重,适合用于简单业务场景的数据同步。

BLoC是一种设计模式,官方并没有给出封装的代码,网上搜到的代码大同小异(不知道是不是Google给的最佳实践),但是他们的共同特点就是,初始值赋值上有问题,UI的初始值,没有用BLoC的数据部分给出的初始值,只是恰好两者值相等,那当需要改变初始值的时候,就需要改很多处,这显然不可接受。(原因这里就不再赘述了)

刚刚也说了,BLoC的实现,使用了Stream或者RxDart,我倾向于使用Stream,因为它是内置的库,RxDart功能我没有评估,但是显然针对简单业务场景,过于重了。

关于BLoC的实现细节,这里推荐大家看鑫磊的文章:Flutter | 状态管理探索篇——BLoC(三),我在这里不详细说

BLoC的结构如下:

我也不知道这应该归类为观察者模式,还是生产者消费者模式,总之是:

  • BLoC数据模块,持有一个StreamController,来管理stream

  • UI需要展示数据的地方,使用StreamBuilder来监听stream变化

  • 当stream里的数据变化时,就会自动刷新子UI。

  • 需要更新数据时,比如点击了某个按钮,则会操作BLoC数据模块,通过其StreamController更新里面的数据,这样UI就会自动刷新了。

三、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),
        ),
      );
    });
  }
}
复制代码

四、总结

当你想要更灵活地管理状态和UI的时候,直接使用这个package可以让你远离复杂的Redux、RxDart、Stream等概念,只需要一个数据类和两个容器Widegt就可以了,数据和UI很好的分离,库对代码的入侵也比较少。希望可以帮助到你。

*通过我的简单测试,这个BLoC的封装比较好用,但是对性能没有仔细测试,这个package也需要不断完善,希望大家多多提出意见,一起改进。

猜你喜欢

转载自juejin.im/post/5be42e9e5188256ccc192c68