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<int>{
  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<StatefulWidget> createState() =>_TsmFlutterBLoCState();
}

class _TsmFlutterBLoCState extends State<TsmFlutterBLoCPage>{
  @override
  Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text('Flutter BLoC 学习'),
          centerTitle: true,
        ),
        body: BlocBuilder<TsmCountCubit,int>(
          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<TsmCountCubit>().increment(),
        ),
      );
  }
}

使用flutter_bloc 框架的基础流程

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

1>创建Privoider

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

2>BlocBuilder<Event,State>();接收数据

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

bloc 发送数据

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

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

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

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

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

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

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

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

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

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

我们再来看看第2个问题 StreamController 的 close 是何时调用的,怎么调用的呢,
说道这里我们就不得不重新提一个Element 的生命周期的问题,在面前Element章节有说过传送门 ( //www.greatytc.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<T> 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<T>(
      create: _create,
      dispose: _dispose,
      child: child,
      lazy: lazy,
    );
  }

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

  InheritedProvider({
    Key key,
    Create<T> create,
    T update(BuildContext context, T value),
    UpdateShouldNotify<T> updateShouldNotify,
    void Function(T value) debugCheckInvalidValueType,
    StartListening<T> startListening,
    Dispose<T> 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<T>(
      owner: this,
      child: builder != null
          ? Builder(
              builder: (context) => builder(context, child),
            )
          : child,
    );
  }



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

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

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

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

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

  final InheritedProvider<T> owner;

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

  @override
  _InheritedProviderScopeElement<T> createElement() {
    return _InheritedProviderScopeElement<T>(this);
  }
}

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

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

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

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

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

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

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

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

  ///共享的数据
  @override
  T get value => _delegateState.value;
  
  _DelegateState<T, _Delegate<T>> _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的整个过程都记录在里面了
//www.greatytc.com/c/36554cb4c804

最后附上demo 地址

https://github.com/tsm19911014/tsm_flutter

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339