flutter_bloc状态管理框架使用和原理学习总结

flutter_bloc今天发布了4.0.0版本,现关于其使用和原理做一个简单的总结。

flutter_bloc官网:
https://github.com/felangel/bloc
https://bloclibrary.dev/#/flutterbloccoreconcepts?id=flutter-bloc-core-concepts
pub.dev上的介绍,包括了多个Examples
https://pub.dev/packages/flutter_bloc

和provider状态管理框架类似,一下API也类似,只是flutter_bloc基于了Stream做了一些包装,并在内部使用了rxDart,归根到底还是一个观察者模式的使用,通过该方式可以将原来的StatefulWidget转换成StatelessWidget,避免了多次调用setState()导致的性能损耗。

这里以Counter为例子,简单介绍其使用:
https://bloclibrary.dev/#/fluttercountertutorial

一 使用篇

0 添加依赖

这里介绍一个库equatable,可以在使用对象比较时简化我们定义类的“==”和“hashCode”工作
https://pub.dev/packages/equatable

name: flutter_counter
description: A new Flutter project.
version: 1.0.0+1

environment:
  sdk: ">=2.0.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^4.0.0
  meta: ^1.1.6
  equatable: ^1.1.1

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true

1 定义Event类型

由于bloc(Business Logic Component)其实会将事件Event的变化映射到状态State的变化(通过自定义Bloc来完成两者的转化),来将业务逻辑Business Logic从UI层抽离出来,所以我们需要先定义事件Event。

enum CounterEvent { increment, decrement }

官网的Counter例子使用的是枚举类型定义事件类,我们也可以按照Flutter Bloc构建轻量级MVVM中的方式来定义。

abstract class CounterEvent extends Equatable {
  const CounterEvent();
}

//点位增加 传入某个值,做更新操作
class ButtonPressAddX extends CounterEvent {
  final int x;

  const ButtonPressAddX({
    @required this.x,
  });

  @override
  // TODO: implement props
  List<Object> get props => [x];

  @override
  String toString() {
    return 'ButtonPressAddX { x: $x }';
  }
}

//点位数值整体重置,所以传入整个状态对象, event 本身不对值进行篡改
class ButtonPressedAdd extends CounterEvent {
  final PointState point;

  const ButtonPressedAdd({
    @required this.point,
  });

  @override
  List<Object> get props => [point];

  @override
  String toString() => getString();

  String getString() {
    var x = point.x;
    var y = point.y;
    var z = point.z;
    return 'ButtonPressedAdd { x: $x, y: $y , z: $z }';
  }
}

class ButtonPressedReduce extends CounterEvent {
  final PointState point;

  const ButtonPressedReduce({
    @required this.point,
  });

  @override
  List<Object> get props => [point];

  @override
  String toString() => getString();

  String getString() {
    var x = point.x;
    var y = point.y;
    var z = point.z;
    return 'ButtonPressedReduce { x: $x, y: $y , z: $z }';
  }
}

2 定义State类型

官网的Counter例子里的State是一个int值,所以不需要再自定义类型,当然我们可以自定义一个类:

abstract class MyState extends Equatable {
    const MyState();
}

class StateA extends MyState {
    final String property;

    const StateA(this.property);

    @override
    List<Object> get props => [property]; // pass all properties to props
}

3 定义Bloc类型

这里以官网的Counter例子为例,在自定义的Bloc中完成事件Event和State状态的转换,这里通过复写两个方法,一个初始化状态的方法,一个mapEventToState转换方法,注意在该方法中每次都要返回一个新的State对象:

@override
Stream<MyState> mapEventToState(MyEvent event) async* {
    // always create a new instance of the state you are going to yield
    yield state.copyWith(property: event.property);
}


@override
Stream<MyState> mapEventToState(MyEvent event) async* {
    final data = _getData(event.info);
    // always create a new instance of the state you are going to yield
    yield MyState(data: data);
}

或者按照Flutter Bloc构建轻量级MVVM中的方式来定义CounterBloc:


class CounterBloc extends Bloc<CounterEvent, PointState> {
  var point;

  CounterBloc({this.point});

  //初始化 状态机 如果没传参 就构建一个(0,0,0)的对象
  @override
  PointState get initialState => point != null ? point : PointState(0, 0, 0);

  @override
  Stream<PointState> mapEventToState(CounterEvent event) async* {
    PointState currentState = PointState(state.x, state.y, state.z);
    if (event is ButtonPressedReduce) {
      currentState.x = state.x - 1;
      currentState.y = event.point.y;
      currentState.z = state.z - 1;
      yield currentState;
    } else if (event is ButtonPressedAdd) {
      currentState.x = state.x + 1;
      currentState.y = event.point.y;
      currentState.z = state.z + 1;
      yield currentState;
      //重置状态
    } else if (event is ButtonPressReset) {
      yield PointState.reset();
      //单点位数值增加
    } else if (event is ButtonPressAddX) {
      yield state.update(event.x, currentState.y, currentState.z);
    }
  }
}

4 使用BlocProvider和BlocBuilder

使用BlocProvider在需要该Bloc的Widget Tree上进行包裹,这里直接放到了整个页面上,可以根据需要放到适当的Widget Tree层级上,同时这里的BlocProvider也自动处理了CounterBloc的关闭操作,所以我们不必使用一个StatefulWidget。

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: BlocProvider<CounterBloc>(
        create: (context) => CounterBloc(),
        child: CounterPage(),
      ),
    );
  }
}

然后就是在页面中使用BlocBuilder,我们通过扩展函数的形式或者使用BlocProvider.of(context)获取CounterBloc,然后在builder函数中,使用当前的State对象值count,并通过counterBloc对象进行事件Event变化的操作:

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
  //这里也可以使用扩展函数的形式 二选一
    final CounterBloc counterBloc = context.bloc<BlocA>()
    final CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context);

    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: BlocBuilder<CounterBloc, int>(
        builder: (context, count) {
          return Center(
            child: Text(
              '$count',
              style: TextStyle(fontSize: 24.0),
            ),
          );
        },
      ),
      floatingActionButton: Column(
        crossAxisAlignment: CrossAxisAlignment.end,
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          Padding(
            padding: EdgeInsets.symmetric(vertical: 5.0),
            child: FloatingActionButton(
              child: Icon(Icons.add),
              onPressed: () {
                counterBloc.add(CounterEvent.increment);
              },
            ),
          ),
          Padding(
            padding: EdgeInsets.symmetric(vertical: 5.0),
            child: FloatingActionButton(
              child: Icon(Icons.remove),
              onPressed: () {
                counterBloc.add(CounterEvent.decrement);
              },
            ),
          ),
        ],
      ),
    );
  }
}

官方例子的全部代码见:
https://github.com/felangel/bloc/blob/master/packages/flutter_bloc/example/lib/main.dart
在例子中还使用了Bloc来控制主题theme实现darkMode的切换。

5 BlocListener、BlocConsumer和RepositoryProvider

两个关于BlocListener的使用例子:
https://bloclibrary.dev/#/recipesfluttershowsnackbar
https://bloclibrary.dev/#/recipesflutternavigation
如果要在State变化时,除了更新UI之外还要做一些其他的事情,那么这时候就可以使用BlocListener,BlocListener包含了一个listener用以做除UI更新之外的事情,该逻辑不能放到BlocBuilder里的builder中,因为这个方法会被Flutter框架调用多次,builder方法应该只是一个返回Widget的函数。

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final dataBloc = BlocProvider.of<DataBloc>(context);
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: BlocListener<DataBloc, DataState>(
        listener: (context, state) {
          if (state is Success) {
            Scaffold.of(context).showSnackBar(
              SnackBar(
                backgroundColor: Colors.green,
                content: Text('Success'),
              ),
            );
          }
        },
        child: BlocBuilder<DataBloc, DataState>(
          builder: (context, state) {
            if (state is Initial) {
              return Center(child: Text('Press the Button'));
            }
            if (state is Loading) {
              return Center(child: CircularProgressIndicator());
            }
            if (state is Success) {
              return Center(child: Text('Success'));
            }
          },
        ),
      ),
      floatingActionButton: Column(
        crossAxisAlignment: CrossAxisAlignment.end,
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          FloatingActionButton(
            child: Icon(Icons.play_arrow),
            onPressed: () {
              dataBloc.add(FetchData());
            },
          ),
        ],
      ),
    );
  }
}

这三者的使用可以见官网介绍:
https://bloclibrary.dev/#/flutterbloccoreconcepts
https://pub.dev/packages/flutter_bloc

6 Bloc代码生成插件的使用

Bloc Code Generator可以方便的生成构建Bloc的模版代码,在项目工程上右键,New -> New Bloc -> Generate New Bloc
在这里插入图片描述
一些命名State和Event的公约:https://bloclibrary.dev/#/blocnamingconventions

7 使用注意事项 与provider和Redux的对比

简要介绍一下和provider的关系:
provider被设计用于依赖项注入(其通过包装InheritedWidget实现)。您仍然需要弄清楚如何管理状态(通过ChangeNotifier,Bloc,Mobx等)。
Bloc库在内部使用provider以轻松在整个Widget 树中提供和访问bloc。

首先BlocProvider继承了SingleChildStatelessWidget, 复写的buildWithChild中返回了一个InheritedProvider,而InheritedProvider就是provider库中的API,

class BlocProvider<T extends Bloc<dynamic, dynamic>>
    extends SingleChildStatelessWidget with BlocProviderSingleChildWidget {
...
 @override
  Widget buildWithChild(BuildContext context, Widget child) {
    return InheritedProvider<T>(
      create: _create,
      dispose: _dispose,
      child: child,
      lazy: lazy,
    );
  }
...
}

参考:https://bloclibrary.dev/#/faqs

8 如何使用Bloc构建一个架构

首先上面的例子中Bloc的使用都局限在一个页面中 ,那么在多个页面之间甚至是整个app中,如何共享同一个Bloc呢,这里可以参考:https://bloclibrary.dev/#/recipesflutterblocaccess

参考:
Flutter Architecture Samples - Brian Egan
Flutter Shopping Card Example
Flutter TDD Course - ResoCoder

9 测试相关

由于Bloc的设计就是将业务逻辑从UI层中抽离,所以测试也变得更加容易,首线添加测试用的库:

dev_dependencies:
  test: ^1.3.0
  bloc_test: ^5.0.0

详细步骤参考:https://bloclibrary.dev/#/testing

二 原理篇

看一下Bloc的源码,发现它是继承Stream的,而什么是Stream呢,可以看文末的参考一,

abstract class Bloc<Event, State> extends Stream<State> implements Sink<Event> {
  final PublishSubject<Event> _eventSubject = PublishSubject<Event>();

  BehaviorSubject<State> _stateSubject;

  /// Returns the current [state] of the [bloc].
  State get state => _stateSubject.value;

  /// Returns the [state] before any `events` have been [add]ed.
  State get initialState;

  /// Returns whether the `Stream<State>` is a broadcast stream.
  @override
  bool get isBroadcast => _stateSubject.isBroadcast;

  /// {@macro bloc}
  Bloc() {
    _stateSubject = BehaviorSubject<State>.seeded(initialState);
    _bindStateSubject();
  }
...

  void _bindStateSubject() {
    Event currentEvent;

    transformStates(transformEvents(_eventSubject, (Event event) {
      currentEvent = event;
      return mapEventToState(currentEvent).handleError(_handleError);
    })).forEach(
      (State nextState) {
        if (state == nextState || _stateSubject.isClosed) return;
        final transition = Transition(
          currentState: state,
          event: currentEvent,
          nextState: nextState,
        );
        try {
          BlocSupervisor.delegate.onTransition(this, transition);
          onTransition(transition);
          _stateSubject.add(nextState);
        } on Object catch (error) {
          _handleError(error);
        }
      },
    );
  }
...
}

同时这里对于Event和State的处理,出现了PublishSubject<Event>和BehaviorSubject<State>,那这里Subject又是什么呢?

这时候RxDart就出场了,原来的Stream流和控制器StreamController的概念,被扩展成了Observable和Subject。

Dart RxDart
Stream Observable
StreamController Subject

这里一共有三种类型的Subject,

1 PublishSubject

PublishSubject仅向监听器发送在订阅之后添加到Stream的事件
在这里插入图片描述

2 BehaviorSubject

BehaviorSubject也是一个广播StreamController,它返回一个Observable而不是一个Stream。
在这里插入图片描述
与PublishSubject的主要区别在于BehaviorSubject还将最后发送的事件发送给刚刚订阅的监听器。

3 ReplaySubject

ReplaySubject也是一个广播StreamController,它返回一个Observable而不是一个Stream。

在这里插入图片描述
默认情况下,ReplaySubject将Stream已经发出的所有事件作为第一个事件发送到任何新的监听器。

参考:
Dart | 什么是Stream
Flutter | 状态管理拓展篇——RxDart(四)
Flutter Bloc构建轻量级MVVM
Stream
StreamController
[译]Flutter响应式编程:Streams和BLoC
Reactive Programming - Streams - BLoC

发布了82 篇原创文章 · 获赞 86 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/unicorn97/article/details/105658378