Flutter 研发阶段性总结(四) 基本设计模式BLoC

BLoC(Business Logic Component)设计模式是一个新鲜词汇, 在2018年1月的DartConf上被提出,其实是在Rx系列思想之下发展出来的响应式编程模式。
PS: 有时候从后端研发的角度来看,前端领域的一些想法确实挺前卫的。
其核心在于:将变量的变化看作一种流,并把Widget的状态绑定在流上,从而当变量改变时候,Widget接收到事件并作出响应的改变。因此BLoC可以做到完全将业务逻辑封装起来,Widget仅通过输入、输出和逻辑模块交互,做到完全的解耦。
BLoC的结构盗图一下:
Flutter 研发阶段性总结(四) 基本设计模式BLoC_第1张图片

BLoC的具体实现

这里的实现 90% 出自这位知乎大佬的文章,各位可以去围观一下:
在 Flutter 中使用 Bloc 来处理数据并更新 UI

流 Stream 的概念

可以把Stream看作管道,事件从一端输入,监听方从另一端获取事件并作出相应的改变。这里我们的Widget需要使用到StreamBuilder。

在Flutter中 StreamController 管理流, controller.sink 是入口, controller.stream 是出口

基本的BLoC实现

假设我们需要一个环境变量代表开和关,而App有多个页面的Switch开关控件与这个变量进行关联,我需要在任何页面拨动开关的时候,其它页面对应的开关的状态保持一致。
这里我们可以设想到的输入事件有两种:

  1. 变量的值改变 --> 导致开关状态变化
  2. 用户拨动开关 --> 导致值改变

因此会有两个Stream: 值的Stream,和用户操作事件Stream

class AppSwitchBLoC {
    bool _switchValue = false;
	StreamController<bool> _appSwitchController = StreamControler();
	// 用于值改变事件的输入(私有变量,不暴露出来)
	StreamSink<bool> get _inSwitch = _appSwitchController.sink;
	// 用于值改变事件的输入(值的输出暴露出来)
	Stream<bool> get outSwitchValue = _appSwitchController.stream;
	
	StreamController _switchActionController = StreamController();
	// 用户输入触发(暴露出来)
	StreamSink get changeSwitchValue = _switchActionController.sink;
	
	AppSwitchBLoC() {
	  // 指定_handleEvent函数处理用户的输入事件
	  _switchActionController.stream.listen(_handleEvent);
	}

    _handleEvent(data) {
      // 业务处理逻辑,这里就是将_switchValue的值变为用户输入的值
      _switchValue = data;
      // 将用户输入的值传递给值Stream, 从而触发界面元素Widget改变
      _inSwitch.add(_switchValue);
    } 
}

BLoC的状态管理

此代码完全来源于上面提到的知乎大佬,各位自行领悟代码…

abstract class BlocBase{
  void dispose();
}
class BlocProvider<T extends BlocBase> extends StatefulWidget{

  final T bloc;
  final Widget child;

  BlocProvider({
    Key key,
    @required this.child,
    @required this.bloc,
  }) : super(key: key);

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

  static Type _typeOf<T>() => T;

  static T of<T extends BlocBase>(BuildContext context){
    final type = _typeOf<BlocProvider<T>>();
    BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
    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;
  }
}

在此基础上,上边的AppSwitchBLoC变为:

class AppSwitchBLoC implements BlocBase {
	/// 源代码不变

	// 添加 dispose 方法
	@override
	void dispose() {
	  // 释放stream资源
	  _appSwitchController.close();
	  _switchActionController.close();
	}
}

界面与BLoC的绑定

借助上边的Provider即可
首先进入AppSwitch页面时,原先仅需要进入Page实例即可,这里需要用Provider封装一层。

/// 改造前
Navigator.of(context).push(new MaterialPageRoute(builder: (context){
  return AppSwitchPage();
}));

/// 改造后
Navigator.of(context).push(new MaterialPageRoute(builder: (context){
  return BlocProvider<AppSwitchPage>(
    bloc: AppSwitchBLoC(),
    child: AppSwitchPage()
  );
}));

此时在AppSwitchPage页面即可获取BLoC实例

class AppSwitchPage extends StatefulWidget {
  @override
  _AppSwitchPageState createState() => _AppSwitchPageState();
}

class _AppSwitchPageState extends State<AppSwitchPage> {
  @override
  Widget build(BuildContext context) {
    final AppSwitchBLoC bloc = BlocProvider.of<AppSwitchBLoC>(context);
    return Container(
      child: StreamBuilder<bool> (
        stream: bloc.outSwitchValue,
        initialData: false,
        builder: (context, AsyncSnapshot<bool> snapshot) {
          return Switch(
            onChange: (value) {
              // 这里输入用户操作后的开关值
              bloc.changeSwitchValue.add(value);
            },
            // 这里从流里监听值的变化
            value: snapshot.data,
          );
        }
      )
    );
  }
}

你可能感兴趣的:(App)