31

FlutterDojo设计之道—状态管理之路(三)

 3 years ago
source link: https://blog.csdn.net/x359981514/article/details/108332741
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Dart作为一个现代化的编程语言,吸收了很多语言的特点,特别是响应式编程的风格。

通过Dart提供的Stream机制,Flutter可以很轻松的构建响应式的编程方式,同时也让跨页面、跨Widget的数据管理问题迎刃而解。

Flutter的响应式编程,具有下面几个特点。

  • 数据的管理,围绕Stream进行,通过Stream的sink和listen,来进行数据的管理

  • Widget发出Stream后,无需感知外界的影响,同样的,Widget在listen Stream时,只需要根据数据的改变来构建UI

  • Widget之间不再耦合,通过Stream管道获取数据,互相无依赖

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

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

zem6Fz.jpg!mobile

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

  • App的业务逻辑处理都在BLoC中

  • Widget通过Sink向BLoC发送数据

  • BLoC通过Stream通知Widget重建UI

这其实有点类似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类通常包含下面几个部分。

  • 私有的model和StreamController

  • 公开的get方法返回Stream

  • 公开的业务处理函数

  • dispose函数

创建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管理类实际上只做了两件事。

  • 将业务UI作为其子Widget

  • 给业务UI提供指定的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,这样就可以使用它内部定义好的接口和数据。

在UI层中,有两种写法,一种是直接使用StatelessWidget,在build函数中初始化BlocProvider.of<IncrementBloc>(context),另一种是使用StatefulWidget,在didChangeDependencies()中进行初始化,因为didChangeDependencies相比initState来说,可以更加安全的获取Context。

这种方式做到了完全的解耦,只要定义好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来强化流的功能。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK