小菜以前尝试过 provider 状态管理工具,简单便捷;但在新的项目中,相关同学采用的是 Bloc 状态管理工具,且前段时间何时简单了解了 Stream 相关知识,因此趁机学习一下基本的 Bloc 相关的内容;

Bloc

      Bloc 可以方便的把样式与业务逻辑区分开,从而使开发更便捷,可重用性更好,测试也更加方便; 8601.png       在使用 Bloc 之前需要提前了解一下如下几个概念;

Event

      Event 事件作为 Bloc 的输入,一般是为了响应用户交互(例如按钮按下)或生命周期事件(例如页面加载)而添加它们。可以使用枚举类型定义事件类,对于相对复杂的事件可以联合业务定义为 class

enum TestEvent { onEvent1, onEvent2 ... onEventN}

States

      States 状态作为 Bloc 的输出,一般用于 UI 状态的更新,页面更新绘制等;一般需要定义不同的数据类型来表示数据状态的变更;

class TestState {
    final int state1;
    final String state2;
    ...
    final User stateN;
    
    TestState(this.state1, this.state2 ... this.stateN);
}

Transitions

      Transitions 转场作为从一个状态到下一个状态的过度,过渡由当前状态,事件和下一个状态组成;例如小菜上述定义的 TestEvent 中的各个 onEvent 中状态变更等均可以视为 Transitions 转场;onTransitionBlocstate 更新之前被调用,常用于记录 Bloc 日志和分析;

@override
void onTransition(Transition<NumberEvent, int> transition) {
  print('====onTransition===${transition}');
  super.onTransition(transition);
}

Streams

      作为异步数据传输;其中使用 async* 时可以使用 yield 关键字并返回一个 Stream 数据;

Stream<int> testStream(int max) async* {
    for (int i = 0; i < max; i++) {
        yield i;
    }
}

Blocs

      Bloc 作为将 Stream 输入的 Event 事件转换为输出的 States 状态;每个自定义的 Bloc 必须继承自基础的 Bloc;通过复写 initialStatemapEventToState 方法来完成事件 EventState 状态的转换;       initialStateBloc 初始化状态,该状态是接收任何事件之前的状态;       mapEventToStateEvent 作为参数,返回的是 Streamstate 状态,通过 state 属性随时访问当前的块状态;       每个 Bloc 都有一个 add 方法,用来添加新的 EventmapEventToState 中;       Bloc 通过 onError 方法获取异常信息并处理等;

class TestBloc extends Bloc<TestEvent, int> {
  @override
  int get initialState => 0;

  @override
  Stream<int> mapEventToState(TestEvent event) async* {
    switch (event) {
      case TestEvent.onEvent1:
        yield state - 1;
        break;
      case TestEvent.onEvent2:
        yield state + 1;
        break;
    }
  }
  
  @override
  void onError(Object error, StackTrace stackTrace) {
    print('====onError===$error, $stackTrace');
    super.onError(error, stackTrace);
  }
}

8602.png

BlocDelegate

      BlocDelegateBloc 的委托,可以在全局或需要的作用域范围内统一管理 onTransitiononError 等;

class NumberBlocDelegate extends BlocDelegate {
  @override
  void onEvent(Bloc bloc, Object event) {
    print('====Delegate.onEvent===$bloc====$event');
    super.onEvent(bloc, event);
  }

  @override
  void onTransition(Bloc bloc, Transition transition) {
    print('====Delegate.onTransition===$bloc====$transition');
    super.onTransition(bloc, transition);
  }

  @override
  void onError(Bloc bloc, Object error, StackTrace stackTrace) {
    print('====Delegate.onError===$error, $stackTrace');
    super.onError(bloc, error, stackTrace);
  }
}

8603.png

TestCode

      小菜尝试了最简单的 Bloc,点击按钮会数字会递增,目前更新 UI 是通过 setState() 方式更新数据,在下一节中会尝试用 FlutterBloc 方式进行数据更新;

import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';

class BlocPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _BlocPageState();
}

class _BlocPageState extends State<BlocPage> {
  var _number = 0;
  NumberBloc _bloc = NumberBloc();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text('Bloc Page')),
        body: Center(
            child: Text('当前 Number = $_number\n下次 Number = ${_bloc.state}',
                style: TextStyle(fontSize: 20.0, color: Colors.blue))),
        floatingActionButton: FloatingActionButton(
            child: Icon(Icons.add),
            onPressed: () {
              _bloc.add(NumberEvent.addEvent);
              setState(() => _number = _bloc.state);
            }));
  }
}

enum NumberEvent { addEvent }

class NumberBloc extends Bloc<NumberEvent, int> {
  @override
  int get initialState => 10;

  @override
  Stream<int> mapEventToState(NumberEvent event) async* {
    switch (event) {
      case NumberEvent.addEvent:
        yield state + 5;
        break;
    }
  }

  @override
  void onTransition(Transition<NumberEvent, int> transition) {
    print('====onTransition===${transition}');
    super.onTransition(transition);
  }

  @override
  void onError(Object error, StackTrace stackTrace) {
    print('====onError===$error, $stackTrace');
    super.onError(error, stackTrace);
  }
}

8604.gif


      小菜初步体验了 Bloc,初步感觉比 Provider 稍微复杂一些,但是分工更为明确;而小菜对 Bloc 的 应用还不够熟练,下一节重点尝试 FlutterBloc 对于 UI 的数据更新等;如有错误,请多多指导!

来源: 阿策小和尚