• 借助Flutter的这个特性,Google在数据管理之路上提出了BLoC模式。

    BLoC模式由Paolo Soares和Cong Hui设计,并谷歌在2018的DartConf首次提出,全称Business Logic Component。

    在BLoC模式下,Widget与Data彻底解耦:

    这其实有点类似MVP、MVC模式,BLoC模式将整个App分为三层,Data Layer、BLoC Layer、UI Layer,Data Layer和UI Layer都只能和BLoC Layer双向通信,但它们之间彼此隔离。

    下面将官方的counter demo,用BLoC模式重写下,让大家了解下创建BLoC模式的一般范式。

    创建BLoC业务处理类

    BLoC类是一个业务逻辑处理类,不包含任何UI逻辑,且一个BLoC类只处理一种独立的业务逻辑,在官方的Demo中,业务逻辑有下面几个部分构成。

    所以创建的BLoC类,只对外暴露这两个业务,即对外的Stream和increment函数。

    abstract class BlocBase {
      void dispose();
    }

    class IncrementBloc implements BlocBase {
      // _私有化控制访问权限
      int _count;
      StreamController<int> _countController;

      IncrementBloc() {
        _count = 0;
        _countController = StreamController<int>();
      }

      Stream<int> get value => _countController.stream;

      increment() {
        _countController.sink.add(++_count);
      }

      dispose() {
        _countController.close();
      }
    }

    BlocBase仅仅封装了dispose函数,用于资源的释放。IncrementBloc就是这个业务的处理核心,通过Stream,让外界可以监听数据的改变。

    一个标准的BLoC类通常包含下面几个部分。

    创建BLoC管理类

    BLoC管理类是一个通用的处理类,借助StatefulWidget来实现了BLoC业务处理类的管理。同时,它也是数据和UI的粘合剂,用于将指定业务的BLoC类注入到具体的业务UI中。

    class BlocProvider<T extends BlocBase> extends StatefulWidget {
      BlocProvider({
        Key key,
        @required this.child,
        @required this.bloc,
      }) : super(key: key);

      final T bloc;
      final Widget child;

      @override
      _BlocProviderState<T> createState() => _BlocProviderState<T>();

      static T of<T extends BlocBase>(BuildContext context) {
        BlocProvider<T> provider = context.findAncestorWidgetOfExactType<BlocProvider<T>>();
        return provider.bloc;
      }
    }

    class _BlocProviderState<T> extends State<BlocProvider<BlocBase>> {
      @override
      void dispose() {
        widget.bloc.dispose();
        super.dispose();
      }

      @override
      Widget build(BuildContext context) {
        return widget.child;
      }
    }

    BLoC管理类实际上只做了两件事。

    创建BLoC UI

    @override
    Widget build(BuildContext context) {
      return BlocProvider<IncrementBloc>(
        bloc: IncrementBloc(),
        child: CounterPage(),
      );
    }

    class CounterPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        final IncrementBloc bloc = BlocProvider.of<IncrementBloc>(context);

        return Scaffold(
          body: Center(
            child: StreamBuilder<int>(
              stream: bloc.value,
              initialData: 0,
              builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
                return Text('You hit me: ${snapshot.data} times');
              },
            ),
          ),
          floatingActionButton: FloatingActionButton(
            child: const Icon(Icons.add),
            onPressed: () => bloc.increment(),
          ),
        );
      }
    }

    在UI层中,可以通过BlocProvider.of<IncrementBloc>(context)来获取指定类型的BLoC,这样就可以使用它内部定义好的接口和数据。

    这种方式做到了完全的解耦,只要定义好BLoC中的接口和数据模型,前端展示UI,就完全和数据无关了。

    在UI层中,需要做的就是通过StreamBuilder来解析要监听的数据,StreamBuilder的builder函数是一个AsyncWidgetBuilder,它能够异步构建widget,其参数AsyncSnapshot<int> snapshot就是流中的数据快照,可以通过snapshot.data来访问流中的数据,或者通过snapshot.hasError、snapshot.error来获取异常信息。

    BLoC流的单播与广播

    Flutter中的Stream分为两种,单播与多播,默认情况下创建的是单播Stream,这样的话,只能有一个StreamBuilder来监听,如果存在多个StreamBuilder监听同一个BLoC Stream,则需要将默认创建的Stream改成多播Stream。

    _countController = StreamController.broadcast<int>();

    在多页面使用的时候,有个地方需要注意,那就是流是实时的,不具有粘滞性。举个例子,比如在第一个界面在流中添加了一些数据,再打开第二个界面的时候,创建StreamBuilder之后,是无法直接获取流的最新数据的,因为这时候流中的的数据在StreamBuilder监听之前就已经结束了。所以这种情况下,要么是在创建StreamBuilder前,初始化initialData的值为流中最新的数据;要么是使用RxDart来强化流的功能。


    本文分享自微信公众号 - Android群英传(android_heroes)。
    如有侵权,请联系 [email protected] 删除。
    本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

    09-03 06:45