flutter实现Bloc

770 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

什么是Bloc?

Bloc全称是Business Logic Component,通过管理状态的方式,实现当一个组件的状态发生改变的时候,可以通知其他组件的状态进行改变。

这里我们以Flutter官方的一个Demo来进行Bloc改造来进行分析,讲解。

  1. 首先通过Android Studio来新建一个Flutter项目。

image.png

image.png

  1. 新建一个BlocBase抽象类,里面有一个方法,叫做dispose,子类必须实现这个方法
abstract class BlocBase{
  void dispose();
}
  1. 新建一个BlocCounter类继承自BlocBase 并对齐做如下改造:
import 'dart:async';
import 'bloc_base.dart';

//继承BlocBase
class BlocCounter extends BlocBase {

  //初例化StreamController,数据类型为int
  final _controller = StreamController<int>();

  //获取到StreamController的sink,即入口可以添加数据
  get _counter => _controller.sink;

  //获取到StreamController的stream,即出口可以取数据
  get counter => _controller.stream;

  //增加计算器值
  void increment(int count) {
    //向流中添加数据
    _counter.add(++count);
  }

  //销毁
  void dispose() {
    //关闭流
    _controller.close();
  }
}

4、对main.dart做如下改造

import 'package:flutter/material.dart';

import 'blocs/bloc_counter.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Bloc示例',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState(BlocCounter());
}

class _MyHomePageState extends State<MyHomePage> {

  //组件计数变量
  int _counter = 0;

  //计数器Bloc
  final BlocCounter bloc;

  _MyHomePageState(this.bloc);

  //计数增加方法
  void _incrementCounter() {
    //调用bloc的方法
    bloc.increment(_counter);
  }

  @override
  void initState() {
    //监听Bloc里的数据
    bloc.counter.listen((_count) {
      //设置状态值
      setState(() {
        _counter = _count;
      });
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Bloc示例'),
      ),
      body: Center(
        child: Text(
          '$_counter',
          style: Theme.of(context).textTheme.display1,
        ),
      ),
      //增加按钮
      floatingActionButton: FloatingActionButton(
        //点击事件
        onPressed: _incrementCounter,
        child: Icon(Icons.add),
      ),
    );
  }
}

这样,我们就可以不用通过int类型,存放点击increment的数据,我们将这个数据保存在一个Bloc类里面,这样,如果说我们点击increment按钮,其他Bloc组件也可以监听到我们的改变,有点类似于React的Redux状态管理机制

实现效果

image.png

实现原理讲解

  1. 首先声明一个BlocCounter变量,然后在initState的生命周期上初始化这个变量,给他加一个监听器来监听count数据的改变,通过setState来刷新到页面当中去。但是要注意这点,我们页面加载的时候,默认是没有我们要监听的这个所谓Bloc的count变量的。所以我们还是需要在状态组件定义一个count变量。
//组件计数变量
int _counter = 0;

//计数器Bloc
final BlocCounter bloc;
@override
void initState() {
  //监听Bloc里的数据
  bloc.counter.listen((_count) {
    //设置状态值
    setState(() {
      _counter = _count;
    });
  });
  super.initState();
}
  1. 在我们点击下方fab的时候,我们会执行一个increment的方法。这个方法调用bloc里面的increment方法是的,我们改变数据的方法不会暴露在状态类中,这个要给Bloc自己内部去调用。然后increment方法通过内部自定义的sink来改变这个流中的数据。
//计数增加方法
void _incrementCounter() {
  //调用bloc的方法
  bloc.increment(_counter);
}

floatingActionButton: FloatingActionButton(
  //点击事件
  onPressed: _incrementCounter,
  child: Icon(Icons.add),
),

好,这样我们通过dart的流特性,就可以实现这种响应式编程的模式了,对于Stream流的理解,我的想法是它好像JS中的setInterval。也是可以规定多久多久对数据进行一次处理,

如果不考虑Websocket的话,可以用来做全双工通信的这样一个处理。可以定时多久多久向后台发送订单状态。然后提示后台清空消息队列。

Bloc是怎么通过监听一个改变通知其他任务队列进行更新的?

这里我们就需要新建一个provider类,对其他同树组件进行消息派发。那么我们就需要做到写一个类用来管理全局的消息状态。那么这个类就必须得要是有状态的,并且能够跨组件获取状态和他们的共享数据。

1.首先定义一个类,这个类是继承自有状态组件,并且他要管理一个Widget和这个Widget里面所有的blocs。


//返回类型
Type _typeOf<T>() => T;

class BlocProvider<T extends BlocBase> extends StatefulWidget{
    BlocProvider({
        Key key,
        @required this.child,
        @required this.blocs
    });
    final Widget child;
    final List<T> blocs;
    
    @override
    _BlocProviderState<T> createState() => _BlocProviderState<T>();

static List<T> of<T extends BlocBase>(BuildContext context) {
  final type = _typeOf<_BlocProviderInherited<T>>();
  //通过BuildContext可以跨组件获取对象
  //ancestorInheritedElementForWidgetOfExactType方法获得指定类型的InheritedWidget进而获取它的共享数据。
  _BlocProviderInherited<T> provider =
      context.ancestorInheritedElementForWidgetOfExactType(type)?.widget;
  //返回所有的blocs
  return provider?.blocs;
}

}
  1. 由于BlocProvider里面的类必须要互相之间能够传递状态。所以我们又要创建一个自定义的类,这个类它有BlocList和一个Widget,并且继承了InheritedWidget这样我们就可以吧数据在Widget树中向子Widget进行传递。
/**
 * InheritedWidget是Flutter的一个功能型的Widget基类
 * 它能有效地将数据在当前Widget树中向它的子Widget树传递
 */
class _BlocProviderInherited<T> extends InheritedWidget {
  _BlocProviderInherited({
    Key key,
    @required Widget child,
    @required this.blocs,
  }) : super(key: key, child: child);

  //所有的bloc
  final List<T> blocs;
  
  //用来告诉InheritedWidget如果对数据进行了修改,
  //是否必须将通知传递给所有子Widget(已注册/已订阅)
  @override
  bool updateShouldNotify(_BlocProviderInherited oldWidget) => false;
}

3.这样,我们就可以对具体的状态类进行编写了。

class _BlocProviderState<T extends BlocBase> extends State<BlocProvider<T>> {

  //重写销毁方法
  @override
  void dispose() {
    //关闭所有的bloc流
    widget.blocs.map((bloc) {
      bloc.dispose();
    });
    super.dispose();
  }

  @override
  Widget build(BuildContext context){
    return _BlocProviderInherited<T>(
      blocs: widget.blocs,
      child: widget.child,
    );
  }
}
  1. 在main.dart中使用方法如下:
//增加方法
void _incrementCounter() {
  //通过BlocProvider的of方法获取到所有bloc
  //然后取第一个bloc并调用其increment方法向流中添加数据
  BlocProvider.of<BlocCounter>(context).first.increment(_counter);
}

@override
void initState() {
  //通过BlocProvider的of方法获取到所有bloc
  //然后取第一个bloc并调用其listen进行监听流的数据
  BlocProvider.of<BlocCounter>(context).first.counter.listen((_count) {
    //设置状态,重新渲染界面
    setState(() {
      _counter = _count;
    });
  });
  super.initState();
}