Flutter 中使用 Bloc 来处理数据更新 UI

标签: 编程学习 Flutter学习

其实可以把 Bloc 做为一个管道,在这个管道中流(数据)的驱动方式为展示了分离的策略,可以很方便的把业务与UI的代码分离开来。

Bloc 侧重于利用 流(Stream) 来构成的异步世界,

Bloc -> Stream -> Widgets

总体上来说就是分离业务代码,降低耦合,从而实现“低耦合”,防止业务的增多导致代码太过混乱。

来用这种思路做一个小Demo

这是举例中使用的数据模型

class BlocModel {
    final String name;
    BlocModel({this.name = '0'});
}

这里是 Bloc 的主体,分为初始化、更新数据、销毁数据流,我这里采用的是 BehaviorSubject,BehaviorSubject也是一个广播流,但是它能记录下最新一次的事件,并在新的收听者收听的时候将记录下的事件作为第一帧发送给收听者(也可以去使用 StreamController,这个根据自己的业务需求来选择)。

class TestBloc {
  //这个就是ui需要使用的stream
  Stream<BlocModel> currentStream;
  // 这个sink用于添加数据
  Sink<BlocModel> currentSink;
  ///当前数据的流入流出
  BehaviorSubject<BlocModel> _currentSubject;

  initBloc() {
    _currentSubject = BehaviorSubject<BlocModel>();
    currentStream = _currentSubject.stream;
    currentSink = _currentSubject.sink;
  }

  updateData(BlocModel model) {
    if(model != null) {
      currentSink.add(model);
    }
  }

  dispose() {
    //销毁
    currentStream = null;
    currentSink = null;
    _currentSubject.close();
  }
}

这里使用的是 Provider 来进行操作

class TestBlocProvider<TestBloc> extends StatefulWidget {
  final TestBloc bloc;
  final Widget child;
  TestBlocProvider({
    Key key,
    @required this.bloc,
    @required this.child,
  }) : super(key: key);

  ///返回bloc实例
  static TestBloc of<TestBloc>(BuildContext context){
    TestBlocProvider<TestBloc> provider = context.findAncestorWidgetOfExactType<TestBlocProvider<TestBloc>>();
    return provider.bloc;
  }
  static Type _typeOf<T>() => T;

  @override
  State<StatefulWidget> createState() => _TestBlocProviderState<TestBloc>();
}

class _TestBlocProviderState<T> extends State<TestBlocProvider<TestBloc>> {
  @override
  void dispose() {
    //便于资源的释放
    widget.bloc.dispose();
    super.dispose();
  }

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

在 UI 中的使用, 主要注意的是使用 StreamBuilder 去构建 UI 才能实现 Stream 的监听。而且我这里为了当前页面操作方便直接使用的是 StatefulWidgeto(╯□╰)o

class BlocWidget extends StatefulWidget {
  final TestBloc testBloc;
  BlocWidget({this.testBloc});

  @override
  State<StatefulWidget> createState() => _BlocState();
}

class _BlocState extends State<BlocWidget> {

  @override
  void initState() {
    // TODO: implement initState
    super.initState();

    //这里偷懒使用延时模拟用户操作,也可以用点击之类的操作来模拟o(╯□╰)o
    Future.delayed(new Duration(seconds: 10), () async {
      widget.testBloc.updateData(BlocModel(name: '100'));
    });
  }

  @override
  Widget build(BuildContext context) {
    /// 获取 bloc 对象
    TestBloc bloc = TestBlocProvider.of<TestBloc>(context);
    return StreamBuilder(
        initialData: BlocModel(name: '1'),
        stream: bloc.currentStream,
        builder: (_, AsyncSnapshot<BlocModel> snapshot) {
          if (snapshot.data != null) {
            return Scaffold(
              appBar: AppBar(
                title: Text('${snapshot.data.name}'),
                elevation: 0,
                centerTitle: true,
              ),
              body: Container(
                child: Center(
                  child: Text('Hello World'),
                ),
              ),
            );
          }
          return Container();
        },
    );
  }
}

初始化我直接搞在demo的入口了,哪里用放在哪里就行了o(╯□╰)o

 var bloc = TestBloc()..initBloc();
 runApp(
   MaterialApp(
     title: 'Flutter Demo',
     theme: ThemeData(
       visualDensity: VisualDensity.adaptivePlatformDensity,
     ),
     home: TestBlocProvider<TestBloc>(
       bloc: bloc,
       child: BlocWidget(testBloc: bloc,),
     ),
   ),
 );

既可以单个模块使用这种方式,可以来修改整个项目的架构,使整个项目都支持这种写法,可以降低代码的耦合度。但是这个也是要根据业务来选择,如果整个项目业务都很简单,甚至简单的一些页面使用 StateFulWidget 就可以实现了,完全没有必要这样用;但是如果经常遇到项目中几个模块之间的数据共享,那就比较有作用了。