Flutter_bloc usage and partial source code analysis

flutter_bloc is a bloc third-party library. This library is very convenient for you to integrate the bloc mode. This library combines RXDart. At present, rxdart is used in our project.

bloc mode

BLoC is a way to build applications using reactive programming, which is a completely asynchronous world composed of streams.

Introduction to commonly used concepts

  • reactive programming: Reactive programming, an event-based model.

  • Stream: A stream is a series of asynchronous data..

  • Observable implements and extends Stream. It combines the commonly used stream and streamTransformer into a very useful API. You can treat it as a stream.

  • streamController: Subjec in rxdart can be used instead of streamController, and subject can be regarded as an enhanced version of streamController. According to the number of data stream listeners, Stream data streams can be divided into single-subscription streams and multi-subscription streams.

  • Subject: Implements and extends StreamController, which conforms to all the specifications of StreamController. If you used StreamController before, you can directly replace it with Subject.

  • StreamBuilder: Wrapping stateful components, streambuilder will listen to a stream from BLoC, listen to new data, generate a new snapshot, and call the builder method again, and the Widget will be rebuilt.

work flow chart:

insert image description here

Add data through the sink of StreamController, and then send it to Stream through StreamController, while subscribers listen by calling Stream's listen() method. The listen() method will return a StreamSubscription object, which supports pausing the data stream, operations such as redo and cancel.

Advantages of BLoC mode

Compared with the traditional setState method, StreamBuilder is a great improvement, because it does not need to forcibly rebuild the entire component tree and its subcomponents, only need to rebuild the components wrapped by StreamBuilder.

  • Cubit : is Streama special type of that is used as Blocthe base of the class, a Cubitfunction that can expose trigger state changes.
  • BlocProvider: A Flutter widget that BlocProvider.of<T>(context)provides a cubit to its children. It is used as a dependency injection widget so that a single instance of a subbit can be provided to multiple widgets in a subtree.
BlocProvider({
    Key key,
  	//创建一个Cubit的实例
    @required Create<T> create,
    Widget child,
  //默认情况下,BlocProvider将懒惰地创建cubit,这意味着当通过BlocProvider.of <BlocA>(上下文)查找cubit时,将执行创建。
    bool lazy,
  }) : this._(
          key: key,
          create: create,
          dispose: (_, bloc) => bloc?.close(),
          child: child,
          lazy: lazy,
        );
  • BlocBuilder : BlocBuilder is a Flutter widget that requires a cubit and a builder function. BlocBuilder handles building widgets in response to new states. BlocBuilder is very similar to StreamBuilder, but has a simpler API to reduce the amount of boilerplate code required. The builder function may be called multiple times and should be a pure function that returns the widget based on state. It has the same function as StreamBuilder, but it simplifies the implementation details of StreamBuilder and reduces some necessary template code
 const BlocBuilder({
    Key key,
    @required this.builder,
   //如果省略cubit参数,则BlocBuilder将使用BlocProvider和当前的BuildContext自动执行查找。
    C cubit,
   //根据返回 true/false 来决定是否更新页面
    BlocBuilderCondition<S> buildWhen,
  })  : assert(builder != null),
        super(key: key, cubit: cubit, buildWhen: buildWhen);


typedef BlocBuilderCondition<S> = bool Function(S previous, S current);
  • MultiBlocProvider is a Flutter widget that combines multiple BlocProvider widgets into one. MultiBlocProvider improves readability and eliminates the need to nest multiple BlocProviders.
  • A BlocListener is a Flutter widget that accepts a BlocWidgetListener and an optional cubit, and invokes the listener in response to state changes in the cubit. It should be used for functionality that needs to happen once per state change, such as navigation, showing a SnackBar, showing a dialog, etc. Specify a cubit only if you wish to provide a cubit that is not accessible through the BlocProvider and the current BuildContext. (Looking at the source code, we know that BlocBuilder encapsulates BlocListener inside)
BlocListener<BlocA, BlocAState>(
  cubit: blocA,
  listener: (context, state) {
   //根据BlocA的中的值在此处进行操作,比如想弹个窗
  }
)

A simple example can be seen in Android Studio.

There are two ways to get the cubit instance, one is to pay attention to the update, and the other is not to pay attention to the update:

  1. Not concerned
// with extensions
context.read<BlocA>();

// without extensions
BlocProvider.of<BlocA>(context);

For example, the two buttons in the figure below:

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-t2Yh366o-1611042317793)(https://i.loli.net/2021/01/18/7jGlTcsA6zxVm4g.png )]

They do not need to be redrawn, no matter how the value changes, the UI of these two buttons does not need to be changed, then the above method can be used to obtain cubit at this time.

  1. focus on

    // with extensions
    context.watch<BlocA>();
    
    // without extensions
    BlocProvider.of<BlocA>(context, listen: true);
    
  2. Change when certain conditions are met

final isPositive = context.select((CounterBloc b) => b.state > 0);

The above code snippet will only be rebuilt if the state of CounterBloc is switched from positive to negative.

Cubit

Cubit is Streama special type of that is used as Blocthe basis for the class. A Cubitcan expose functions that trigger state changes.

State is output Cubitfrom and represents part of the application state. Can notify UI component state and redraw some parts of itself according to the current state

When creating a Cubit, we need to define Cubitthe type of state the will manage. For the above CounterCubit, state can be intrepresented by a , but in more complex cases it may be necessary to use class(classes) instead of primitive types.

class CounterCubit extends Cubit<int> {
  CounterCubit(int initialState) : super(initialState);
  void increment() => emit(state + 1);
}

Each Cubithas the ability emitto output a new state via .

In the code snippet above, CounterCubita incrementpublic method named is exposed that can be called externally to notify the to CounterCubitincrement its state. When calling increment, we can stateaccess the current state of the through the getter Cubit, and 1emit emita new state by adding to the current state.

emitThe function is protected, which means it can only Cubitbe used internally.

Source code analysis

//因为是 abstract , 所以我们可以重写它的方法来扩展实现想要的功能
abstract class Cubit<State> extends Stream<State> {
  Cubit(this._state) {
    _observer.onCreate(this);
  }

  State get state => _state;

  BlocObserver get _observer => Bloc.observer;

  StreamController<State> _controller;

  State _state;

  bool _emitted = false;

  @protected
  @visibleForTesting
  void emit(State state) {
    //初始化_controller  多订阅
    _controller ??= StreamController<State>.broadcast();
    
    //如果[Cubit]已关闭或发出的[state]等于当前[state],则[emit]不执行任何操作。
    if (_controller.isClosed) return;
    if (state == _state && _emitted) return;
    onChange(Change<State>(currentState: this.state, nextState: state));
    //将[状态]更新为提供的[状态]。
    _state = state;
    _controller.add(_state);
    _emitted = true;
  }

  ///通知[Cubit]触发[onError]的[错误]。
  void addError(Object error, [StackTrace stackTrace]) {
    onError(error, stackTrace);
  }

 	///给定的[change]发生[change]时调用。
  ///当发出一个新的“状态”时发生[变化]。在更新“ cubit”的“ state”之前调用[onChange]。 [onChange]是为特定“cubit”添加日志记录分析的好地方
  @mustCallSuper
  void onChange(Change<State> change) {
    // ignore: invalid_use_of_protected_member
    _observer.onChange(this, change);
  }

  /// 每当[Cubit]中发生[错误]时调用。
  /// 默认情况下,所有[错误]都将被忽略,并且[Cubit]功能将不受影响。
  ///一个在[Cubit]级别处理错误的好地方。
  @protected
  @mustCallSuper
  void onError(Object error, StackTrace stackTrace) {
    // ignore: invalid_use_of_protected_member
    _observer.onError(this, error, stackTrace);
    assert(() {
      throw CubitUnhandledErrorException(this, error, stackTrace);
    }());
  }

  /// 将订阅添加到Stream<State>中.
  /// 返回一个[StreamSubscription],它使用提供的[onData],[onError]和[onDone]处理程序处理Stream <State>中的事件。
  @override
  StreamSubscription<State> listen(
    void Function(State) onData, {
    Function onError,
    void Function() onDone,
    bool cancelOnError,
  }) {
    _controller ??= StreamController<State>.broadcast();
    return _controller.stream.listen(
      onData,
      onError: onError,
      onDone: onDone,
      cancelOnError: cancelOnError,
    );
  }

  @override
  bool get isBroadcast => true;

  ///关闭[Cubit]。
  ///当调用close时,将不再发出新状态。
  @mustCallSuper
  Future<void> close() {
    _observer.onClose(this);
    _controller ??= StreamController<State>.broadcast();
    return _controller.close();
  }
}

BlocListener

class _BlocListenerBaseState<C extends Cubit<S>, S>
    extends SingleChildState<BlocListenerBase<C, S>> {
  StreamSubscription<S> _subscription;
  S _previousState;
  C _cubit;

  @override
  void initState() {
    super.initState();
    _cubit = widget.cubit ?? context.read<C>();
    _previousState = _cubit.state;
    _subscribe();
  }

  @override
  void didUpdateWidget(BlocListenerBase<C, S> oldWidget) {
    super.didUpdateWidget(oldWidget);
    final oldCubit = oldWidget.cubit ?? context.read<C>();
    final currentCubit = widget.cubit ?? oldCubit;
    if (oldCubit != currentCubit) {
      if (_subscription != null) {
        _unsubscribe();
        _cubit = currentCubit;
        _previousState = _cubit.state;
      }
      _subscribe();
    }
  }

  @override
  Widget buildWithChild(BuildContext context, Widget child) => child;

  @override
  void dispose() {
    _unsubscribe();
    super.dispose();
  }

  void _subscribe() {
    if (_cubit != null) {
      _subscription = _cubit.listen((state) {
        if (widget.listenWhen?.call(_previousState, state) ?? true) {
          widget.listener(context, state);
        }
        _previousState = state;
      });
    }
  }

  void _unsubscribe() {
    if (_subscription != null) {
      _subscription.cancel();
      _subscription = null;
    }
  }
}

Example project address

Guess you like

Origin blog.csdn.net/u011272795/article/details/112842510