Flutter 学习之旅(四十五) Flutter 状态 flutter_bloc学习(一)

在实际项目中我觉得大家一般不会直接使用StreamBuilder 的这种模式的BLoC,而是直接使用框架,网上经常提到到的框架有scoped_model ,flutter_bloc,flutter_redux, privoder 这些框架,

scoped_model

这个在前面文章已经分析过了,非常的小巧,利用了Microtask 微任务队列做的异步通信和Flutter中InheritedWidget 控件的特性,可用于全局变量的传递,如果只是局部变量的传递,那么直接利用Element 的结构树的特性直接找到与祖先绑定widget,即可以获取这个共享的数据,

我们今天就来学习一下flutter_bloc 这个框架,学会用不是目的,提升自身才是最大的收益 ,我们使用的版本是从官网上找的最新代码

  flutter_bloc: ^6.0.6

我们先来看一下简单的示例: 还是一个比较常见的计数器,这里我会根据官网的介绍,逐步的向高级用法延伸,所以前面的东西会比较简单,但是不要认为他不重要,即使是简单也是前后配合完成了事件的通信,简单的才是基础,非常重要

///监听类,BLoC矿建的静态全局变量,这里打印了onChange 方法,
class TsmCountObserve extends BlocObserver{
  @override
  void onChange(Cubit cubit, Change change) {
    printString('${cubit.runtimeType} '+"     "+'$change');
    super.onChange(cubit, change);
  }
}


/// 实例类 
class TsmCountCubit extends Cubit{
  TsmCountCubit(state) : super(state);
  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
}


class TsmFlutterBLoCPageBase extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    Bloc.observer = TsmCountObserve();
    return BlocProvider(
      create: (context)=>TsmCountCubit(0),
      child: TsmFlutterBLoCPage(),
    );
  }
}

class TsmFlutterBLoCPage extends StatefulWidget{
  @override
  State createState() =>_TsmFlutterBLoCState();
}

class _TsmFlutterBLoCState extends State{
  @override
  Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text('Flutter BLoC 学习'),
          centerTitle: true,
        ),
        body: BlocBuilder(
          builder: (con,count){
            return Container(
              child: Text(count.toString()),
              alignment: Alignment.center,
            );
          },
        ),
        floatingActionButton: FloatingActionButton(
          key: const Key('counterView_increment_floatingActionButton'),
          child: const Icon(Icons.add),
          onPressed: () => context.bloc().increment(),
        ),
      );
  }
}

使用flutter_bloc 框架的基础流程

由于Observe 是全局静态变量为了打印日志,可有可无,所以他不在使用flutter_bloc这个框架的基础流程中,

1>创建Privoider

如果大家从网上看到的代码的话,这个provider 一般包裹的是MaterialApp()这个类,这样更能体现出他的数据传递的特性,由于我这边demo涉及到关于bloc的代码太多,不能让每一个provider 都包裹MaterialApp,这里我让他包裹了一个StatefulWidget 来演示数据的传递,
Privoider的入参比较简单,一个事件源,一个child,实际还有一个lazy 这个属性,用来控制懒加载的,

2>BlocBuilder();接收数据

BlocBuilder来包裹在数据改变时需要变更的控件,参数包含一个build 用来构建控件的,注意他这里也可以提供另外的一个Cubit , 这里可以解释为事件源可能会有多个,你可以指定接收哪个信号源的信息,那么默认情况下是接收根 Privoider 提供的数据源,还是 直接包裹他的数据源呢,这里我们留一下一个疑问,在下面介绍源码的时候我们再根据源码具体说明,

bloc 发送数据

这里面非常巧妙的使用了拓展方法,将这个bloc() 方法添加到了buildContext 的方法里面,具体实现代码非常简单就一行

extension BlocProviderExtension on BuildContext {
  C bloc>() => BlocProvider.of(this);
}

最简单的实现方式已经完成了,虽然代码写起来非常简单,但是这里面有几个问题需要我们思考一下,

1.provider 既然可以包裹在MaterialApp()外层,肯定是使用了InheritedWidget,那么他是如何来存储这么需要的这个Cubit数据呢?

2..既然是bloc模式,那么StreamController 的close 是何时调用的,怎么调用的呢,

3.同时存在根Cubit和父Cubit 的情况下,在BLoCBuilder 默认情况下会主动获取哪个呢,

我们先看第三个问题,由于flutter_bloc的代码中引用了太多的provider的方法,所以这里面很多的方法和类都是使用Provider 这个包下面的,
我们先来看看provider包下面的这个方法,具体返回的是什么,

  static T of(BuildContext context, {bool listen = true}) {
    /// 获取element 
    final inheritedElement = _inheritedElementOf(context);
    if (listen) {
    ///如果listen为true , 则简历这个context与inheritedWidget 之间的联系
      context.dependOnInheritedElement(inheritedElement);
    }
    return inheritedElement.value;
  }

  static _InheritedProviderScopeElement _inheritedElementOf(BuildContext context, ) {
    _InheritedProviderScopeElement inheritedElement;
  ///如果这个控件本身就是 InheritedWidget 
    if (context.widget is _InheritedProviderScope) {
    ///遍历这个控件的祖先Element,找到第一个就打断,
      context.visitAncestorElements((parent) {
        inheritedElement = parent.getElementForInheritedWidgetOfExactType<
            _InheritedProviderScope>() as _InheritedProviderScopeElement;
        return false;
      });
    } else {
///通过这个_inheritedWidgets Map来获取的数据,所以这里不是遍历,而是直接根据类型来获取,也就是说,如果存在相同类型的
///inheritedWidgets  则后面的会替换掉前面已经添加进去的类型,即找到最近一个
      inheritedElement = context.getElementForInheritedWidgetOfExactType<
          _InheritedProviderScope>() as _InheritedProviderScopeElement;
    }
    if (inheritedElement == null) {
      throw ProviderNotFoundException(T, context.widget.runtimeType);
    }
    return inheritedElement;
  }

在_inheritedElementOf()这个方法里面告诉我们的答案就是找到最近的那个,

我们再来看看第2个问题 StreamController 的 close 是何时调用的,怎么调用的呢,
说道这里我们就不得不重新提一个Element 的生命周期的问题,在面前Element章节有说过传送门 ( https://www.jianshu.com/p/592561041c86 )
StatefulElement的管理着StatefulState 的生命方法,说道这里如果对element 的方法有过了解的肯定可以想到,这个dispse(),会由element的哪个方法去调用,没错就是unmount() 方法

///StatefulElement  的 unmount() 方法
  @override
  void unmount() {
    super.unmount();
    _state.dispose();
    _state._element = null;
    _state = null;
  }

我在看源码的过程中并没有找到provider他这个包里面包裹InheritedWidget 方法,所以就试着看一下Element 的源码,在他们unmount 方法中找到了dispose()这个方法的调用时机,
下面我们跟着源码来走一遍他的流程,我只是看了一个大概,部分实现逻辑比较多,我也没有具体看,

  BlocProvider({
    Key key,
    @required CreateBloc create,
    Widget child,
    bool lazy,
  }) : this._(
          key: key,
          create: create,
          dispose: (_, bloc) => bloc?.close(),
          child: child,
          lazy: lazy,
        );


  @override
  Widget buildWithChild(BuildContext context, Widget child) {
    return InheritedProvider(
      create: _create,
      dispose: _dispose,
      child: child,
      lazy: lazy,
    );
  }

首先这个BlocProvider 在 初始化的时候会调用_()的这个方法,什么也没有干,目的是为了封装这个dispose()的方法,并在buildWithChild 方法中又调用了Provider 包中 InheritedProvider,将它初始化的变量传递了过去,

  InheritedProvider({
    Key key,
    Create create,
    T update(BuildContext context, T value),
    UpdateShouldNotify updateShouldNotify,
    void Function(T value) debugCheckInvalidValueType,
    StartListening startListening,
    Dispose dispose,
    this.builder,
    bool lazy,
    Widget child,
  })  : _lazy = lazy,
        _delegate = _CreateInheritedProvider(
          create: create,
          update: update,
          updateShouldNotify: updateShouldNotify,
          debugCheckInvalidValueType: debugCheckInvalidValueType,
          startListening: startListening,
          dispose: dispose,
        ),
        super(key: key, child: child);

  @override
  Widget buildWithChild(BuildContext context, Widget child) {
    assert(
      builder != null || child != null,
      '$runtimeType used outside of MultiProvider must specify a child',
    );
    return _InheritedProviderScope(
      owner: this,
      child: builder != null
          ? Builder(
              builder: (context) => builder(context, child),
            )
          : child,
    );
  }



///这个  Delegate 类似StatefulWidget 
class _CreateInheritedProvider extends _Delegate {
  _CreateInheritedProvider({
    this.create,
    this.update,
    UpdateShouldNotify updateShouldNotify,
    this.debugCheckInvalidValueType,
    this.startListening,
    this.dispose,
  })  : assert(create != null || update != null),
        _updateShouldNotify = updateShouldNotify;

  final Create create;
  final T Function(BuildContext context, T value) update;
  final UpdateShouldNotify _updateShouldNotify;
  final void Function(T value) debugCheckInvalidValueType;
  final StartListening startListening;
  final Dispose dispose;

  @override
  _CreateInheritedProviderState createState() =>
      _CreateInheritedProviderState();
}

在这个InheritedProvider 方法中其实干的事情也不过,初始化了一个类似StatefulWidget的 delegate ,并在buildWithChild方法中创建了真正的保存数据的主角 _InheritedProviderScope

class _InheritedProviderScope extends InheritedWidget {
  _InheritedProviderScope({
    this.owner,
    @required Widget child,
  }) : super(child: child);

  final InheritedProvider owner;

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    return false;
  }

  @override
  _InheritedProviderScopeElement createElement() {
    return _InheritedProviderScopeElement(this);
  }
}

class _InheritedProviderScopeElement extends InheritedElement
    implements InheritedContext {
  _InheritedProviderScopeElement(_InheritedProviderScope widget)
      : super(widget);

  @override
  void unmount() {
    _delegateState.dispose();
    super.unmount();
  }
}

最后发现在_InheritedProviderScopeElement 的unmount() 调用了最初由BLoCProvider 提供的这个dispose方法,

State 中的dispose 的实质就是element 的unmount 方法的调用,只要这个unmount 方法执行了,有没有这个state并不重要,这也就是为什么要说这个dispose 方法的原因

看完了上面的代码,再来看第一个问题,就非常简单了,我们再来看一下provider.of()的这个方法,

  static T of(BuildContext context, {bool listen = true}) {
    /// 获取element 
    final inheritedElement = _inheritedElementOf(context);
    if (listen) {
    ///如果listen为true , 则简历这个context与inheritedWidget 之间的联系
      context.dependOnInheritedElement(inheritedElement);
    }
    return inheritedElement.value;
  }

最后获取的是inheritedElement.value , 也就是_InheritedProviderScopeElement 的value ,这么这个value怎么来的呢,
看看下面_InheritedProviderScopeElement 的 performRebuild()这个方法

class _InheritedProviderScopeElement extends InheritedElement
    implements InheritedContext {
  _InheritedProviderScopeElement(_InheritedProviderScope widget)
      : super(widget);

  ///共享的数据
  @override
  T get value => _delegateState.value;
  
  _DelegateState> _delegateState;

  @override
  void performRebuild() {
    if (_firstBuild) {
      _firstBuild = false;
      _delegateState = widget.owner._delegate.createState()..element = this;
    }
    super.performRebuild();
  }
}

从上面看这个delegatestate.value 是由_InheritedProviderScope.createState()后提供的,我们再来看一下这个createState()方法

@override
  T get value {
    bool _debugPreviousIsInInheritedProviderCreate;
    bool _debugPreviousIsInInheritedProviderUpdate;
    if (!_didInitValue) {
      _didInitValue = true;
      if (delegate.create != null) {
        try {
          _value = delegate.create(element);
        } finally {
        }
      }
      if (delegate.update != null) {
        try {
          _value = delegate.update(element, _value);
        } finally {
        }
      }
    }

    element._isNotifyDependentsEnabled = false;
    _removeListener ??= delegate.startListening?.call(element, _value);
    element._isNotifyDependentsEnabled = true;
    assert(delegate.startListening == null || _removeListener != null);
    return _value;
  }

这个value的类型就是我们最开始由BLoCProvicer 传递的这个create的方法创建的,但是数据是否是我们最开始的数据,就看是否对他做了修改,

至此,flutter_bloc的基础功能就完事了,但是flutter_bloc 的精髓并不是这些,大家可以看到,基础功能大多数都是使用了provider的这个包来完成的,他只是做了少量的封装,如果业务不是很复杂,到这里已经够用了,剩下的我会在后续文章中继续说

我学习flutter的整个过程都记录在里面了
https://www.jianshu.com/c/36554cb4c804

最后附上demo 地址

https://github.com/tsm19911014/tsm_flutter

你可能感兴趣的:(Flutter 学习之旅(四十五) Flutter 状态 flutter_bloc学习(一))