Bootstrap

【源码篇】Flutter Bloc背后的思想,一篇纠结的文章

前言

看了Bloc源码后,心情有点复杂呀。。。

说点积极的...

用过Bloc的靓仔们,肯定能感受到,Bloc框架对开发页面,做了很清晰划分,框架强行定了俩种开发模式

  • Bloc模式:该模式划分四层结构

  • bloc:逻辑层

  • state:数据层

  • event:所有的交互事件

  • view:页面

  • Cubit模式:该模式划分了三层结构

  • cubit:逻辑层

  • state:数据层

  • view:页面

作者在层次的划分上还是很老道的,state层是直接写死在框架内部,这层必须要单独分出来;我感觉如果不是被大型项目的克苏鲁代码山坑过,应该不会有这么深的执念

这个state层加的,我觉得相当有必要,因为某个页面一旦维护的状态很多,将状态变量和逻辑方法混在一起,后期维护会非常头痛。

说点批判的...

  • 大家可能在群里,经常看到一些老哥说:Bloc是将Provider封装了一层。

  • 这里我证实下:这是真的,Bloc确实将Provider封了一层

  • 但是仅仅只用到Provider中子节点查询最近父节点InheritedElement数据和顶层Widget并列布局功能,Provider最经典的刷新机制,完全没用到!

  • 我觉得Bloc作者,对Provider的刷新机制可能有点迷糊

  • 哪怕bloc框架在build widget里用到了一行: Provider.of(context, listen: true) 或者去掉e.markNeedsNotifyDependents() ,我都不会说这话。。。

  • Bloc框架做了一些让我非常疑惑的操作,_startListening方法中的回调中调用了 e.markNeedsNotifyDependents()完全没用!因为没使用Provider.of(context, listen: true) 向 InheritedElement 添加子Element,所以是刷新了个寂寞!为了验证我的想法,我debug了 framework层的notifyClients方法,调用emit或yield刷新的时候, _dependents的map一直为空,哎。。。

  • 我上面吐槽了很多,并非我对bloc有什么意见

  • Bloc我也用了较长的时间,深度使用过程,对其用法做了一些优化,还为其写了一个代码生成插件,为它也算付出了一些时间和精力

  • 但是:代码是不会说谎的,所有好的或不好的都在其中,用心体悟就能感受到。

为啥说心情复杂呢?

之前在看Provider源码的时候,看的有些头痛,内部逻辑确实有些复杂,但是总流程理通,刷新逻辑清晰之后,那是一种酣畅淋漓的感觉!痛苦之后便是一种巨大的满足感,并对Provider熟练运用Framework层各种api,然后实现了精彩的刷新机制,感到赞叹!

然后,上面也讲了,我在Bloc上面确实花了一些精力,优化它的使用,然后看了他的源码,再想想之前看的Provider源码,突然有种巨大的落差感。

在我看来,这样大名鼎鼎的开源库,上面这点疙瘩完全可以避免;也许是这种莫名的高期待,让我产生了这种落差。。。

对了,说不定是Bloc作者,故意留了一个Provider刷新机制在Bloc中,把这个作为一个彩蛋!

突然感觉这点疙瘩没了!

使用

这边介绍下使用,对官方的用法做了一些调整

调整心路的历程,可参照:

下面就直接写出调整后写法了

插件

因为官方插件生成的写法,和调整后写法差距有点大,而且官方插件不支持生成view层和相关设置,此处我就撸了一个插件,完善了相关功能

请注意,Wrap代码和提示代码片段,参靠了官方插件规则

Wrap Widget 规则来着:

快捷代码生成规则来着:

  • 在Android Studio里面搜索 flutter bloc

  • 生成模板代码

  • 支持修改后缀

  • Wrap Widget (alt + enter):RepositoryProvider,BlocConsumer,BlocBuilder,BlocProvider,BlocListener

  • 输入 bloc 可生成快捷代码片段

用法

插件可生成俩种模式代码:Bloc和Cubit;来看下

Cubit模式

  • view

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (BuildContext context) => CounterCubit(),
      child: Builder(builder: (context) => _buildPage(context)),
    );
  }

  Widget _buildPage(BuildContext context) {
    final cubit = BlocProvider.of(context);

    return Container();
  }
}

  • cubit

class CounterCubit extends Cubit {
  CounterCubit() : super(CounterState().init());
}

  • state

class CounterState {
  CounterState init() {
    return CounterState();
  }

  CounterState clone() {
    return CounterState();
  }
}

Bloc模式

  • view:默认添加了一个初始化事件

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (BuildContext context) => CounterBloc()..add(InitEvent()),
      child: Builder(builder: (context) => _buildPage(context)),
    );
  }

  Widget _buildPage(BuildContext context) {
    final bloc = BlocProvider.of(context);

    return Container();
  }
}

  • bloc

class CounterBloc extends Bloc {
  CounterBloc() : super(CounterState().init());

  @override
  Stream mapEventToState(CounterEvent event) async* {
    if (event is InitEvent) {
      yield await init();
    }
  }

  Future init() async {
    return state.clone();
  }
}

  • event

abstract class CounterEvent {}

class InitEvent extends CounterEvent {}

  • state

class CounterState {
  CounterState init() {
    return CounterState();
  }

  CounterState clone() {
    return CounterState();
  }
}

总结

Bloc和Cubit模式对于结构,划分的很清楚,因为有多层结构划分,务必会有相应的模板代码和文件,没有插件的帮助,每次都写这些模板代码,会非常难受;这边为大家写了这个插件,如果有什么BUG,麻烦及时反馈哈。。。

这里就不重复写怎么使用了,使用明细可参照:

前置知识

想弄懂Bloc原理,需要先了解下Stream的相关知识

StreamController、StreamBuilder:这俩者的搭配也可以轻松的实现刷新局部Widget,来看下使用

  • view:Stream流必须要有关闭的操作,此处就需要使用StatefulWidget,需要它的dispose回调

class StreamPage extends StatefulWidget {
  const StreamPage({Key? key}) : super(key: key);

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

class _StreamPageState extends State {
  final logic = StreamLogic();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Bloc-Bloc范例')),
      body: Center(
        child: StreamBuilder(
          initialData: logic.state,
          stream: logic.stream,
          builder: (context, snapshot) {
            return Text(
              '点击了 ${snapshot.data!.count} 次',
              style: TextStyle(fontSize: 30.0),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => logic.increment(),
        child: Icon(Icons.add),
      ),
    );
  }

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

  • logic:Stream数据源是泛型,可以直接使用基础类型,此处使用实体,是为了后期可扩展更多数据

class StreamLogic {
  final state = StreamState();

  // 实例化流控制器
  final _controller = StreamController.broadcast();

  Stream get stream => _controller.stream;

  void increment() {
    _controller.add(state..count = ++state.count);
  }

  void dispose() {
    // 关闭流控制器,释放资源
    _controller.close();
  }
}

  • state

class StreamState {
  int count = 0;
}

  • 效果图

实际上,看了上述的使用,会发现有几个很麻烦的地方

  • 需要创建Stream的一系列对象

  • Stream流必须要有关闭操作,所以要使用StatefulWidget

  • StreamBuilder需要写三个参数,很麻烦

Bloc作者借住Provider的InheritedProvider控件,将上面的痛点都解决了

刷新机制

Bloc的刷新机制很简单,上面的Stream操作,基本阐明了其核心的刷新机制,但是Bloc作者做了一些封装,我们来看看

BlocProvider的魅力

BlocProvider是一个非常重要的控件,刷新参数的精简和Stream流的关闭都和其有关,因为该封装了一个Provider里面InheritedProvider;但是,但是在我看来,他依旧是一个很有魅力的控件

  • BlocProvider:BlocProvider的源码很简单,下面就是这个类的源码

class BlocProvider>
    extends SingleChildStatelessWidget with BlocProviderSingleChildWidget {
  /// {@macro bloc_provider}
  BlocProvider({
    Key? key,
    required Create create,
    this.child,
    this.lazy,
  })  : _create = create,
        _value = null,
        super(key: key, child: child);

  BlocProvider.value({
    Key? key,
    required T value,
    this.child,
  })  : _value = value,
        _create = null,
        lazy = null,
        super(key: key, child: child);

  /// Widget which will have access to the [Bloc] or [Cubit].
  final Widget? child;
    
  final bool? lazy;

  final Create? _create;

  final T? _value;

  static T of>(
    BuildContext context, {
    bool listen = false,
  }) {
    try {
      return Provider.of(context, listen: listen);
    } on ProviderNotFoundException catch (e) {
      if (e.valueType != T) rethrow;
      throw FlutterError(
        '''
        BlocProvider.of() called with a context that does not contain a $T.
        No ancestor could be found starting from the context that was passed to BlocProvider.of<$T>().

        This can happen if the context you used comes from a widget above the BlocProvider.

        The context used was: $context
        ''',
      );
    }
  }

  @override
  Widget buildWithChild(BuildContext context, Widget? child) {
    final value = _value;
    return value != null
        ? InheritedProvider.value(
            value: value,
            startListening: _startListening,
            lazy: lazy,
            child: child,
          )
        : InheritedProvider(
            create: _create,
            dispose: (_, bloc) => bloc.close(),
            startListening: _startListening,
            child: child,
            lazy: lazy,
          );
  }

  static VoidCallback _startListening(
    InheritedContext e,
    BlocBase value,
  ) {
    final subscription = value.stream.listen(
      (dynamic _) => e.markNeedsNotifyDependents(),
    );
    return subscription.cancel;
  }
}

  • BlocProvider和BlocProvider.value的区别

  • 看上面源码可知:BlocProvider.value没有做Stream自动关闭操作

  • 所以BlocProvider.value不应该在普通的单页面使用,可用于全局Bloc实例

  • 单页面Bloc请使用BlocProvider去创建Bloc或Cubit

  • create是外部实例化的XxxBloc,最终传入了InheritedProvider中

  • create就是外部传入的XxxBloc实例

  • 该实例直接传入了InheritedProvider中,这就是涉及到Provider中,最终是储存在 _InheritedProviderScopeElement中, _startListening也是Provider的内容

  • 这内部的原理是比较复杂且很重要的,感兴趣请查看:

  • 说真的 _startListening里面的逻辑没什么卵用

  • markNeedsNotifyDependents这个api是Provider作者专门为Provider子Element刷新做的,必须配套 Provider.of(context, listen: true) 去注册Widget控件才行

  • 涉及逻辑太多,都在上面Provider源码剖析文章中,感兴趣的可以去看看

  • BlocProvider.of

  • 作用:可以在BlocProvider包裹的子控件中,获取到BlocProvider Create传入的XxxBloc

  • 请注意:如果使用BlocProvider父布局context是拿不到XxxBloc的,必须是BlocProvider的子布局

  • 原理:,还是在这篇文章里

  • 我真的不是推广这文章啊,BlocProvider这部分,Bloc用了太多Provider特性

  • Provider文章,我花了九牛二虎之力将原理剖析完,在此处,就没必要再做复读机了

总结:来归纳下BlocProvider这个类的作用

  • 图示

基石BlocBase

毋庸置疑,BlocBase是很重要的一个抽象类

  • BlocBase

abstract class BlocBase {
  BlocBase(this._state) {
    Bloc.observer.onCreate(this);
  }

  StreamController? __stateController;
  StreamController get _stateController {
    return __stateController ??= StreamController.broadcast();
  }

  State _state;

  bool _emitted = false;

  State get state => _state;

  Stream get stream => _stateController.stream;

  @Deprecated(
    'Use stream.listen instead. Will be removed in v8.0.0',
  )
  StreamSubscription listen(
    void Function(State)? onData, {
    Function? onError,
    void Function()? onDone,
    bool? cancelOnError,
  }) {
    return stream.listen(
      onData,
      onError: onError,
      onDone: onDone,
      cancelOnError: cancelOnError,
    );
  }

  void emit(State state) {
    if (_stateController.isClosed) return;
    if (state == _state && _emitted) return;
    onChange(Change(currentState: this.state, nextState: state));
    _state = state;
    _stateController.add(_state);
    _emitted = true;
  }

  @mustCallSuper
  void onChange(Change change) {
    Bloc.observer.onChange(this, change);
  }

  @mustCallSuper
  void addError(Object error, [StackTrace? stackTrace]) {
    onError(error, stackTrace ?? StackTrace.current);
  }

  @protected
  @mustCallSuper
  void onError(Object error, StackTrace stackTrace) {
    Bloc.observer.onError(this, error, stackTrace);
    assert(() {
      throw BlocUnhandledErrorException(this, error, stackTrace);
    }());
  }

  @mustCallSuper
  Future close() async {
    Bloc.observer.onClose(this);
    await _stateController.close();
  }
}

上面的BlocBase做了几件比较重要的事,来梳理下

Bloc.observer这个不重要,这是框架内部定义的一个类,这边可以忽略掉,不太重要

  • 将上面的代码精简下

abstract class BlocBase {
  BlocBase(this.state) : _stateController = StreamController.broadcast();

  final StreamController _stateController;

  T state;

  bool _emitted = false;

  Stream get stream => _stateController.stream;

  void emit(T newState) {
    if (_stateController.isClosed) return;
    if (state == newState && _emitted) return;
    state = newState;
    _stateController.add(state);
    _emitted = true;
  }

  @mustCallSuper
  Future close() async {
    await _stateController.close();
  }
}

BlocBuilder

BlocBuilder对StreamBuilder的用法做了很多精简,来看下内部实现

  • BlocBuilder

  • 此处需要关注下builder参数; buildWhen是个判断是否需要更新的参数

  • build方法里面调用了builder,需要看下父类BlocBuilderBase

typedef BlocWidgetBuilder = Widget Function(BuildContext context, S state);

class BlocBuilder, S> extends BlocBuilderBase {
  const BlocBuilder({
    Key? key,
    required this.builder,
    B? bloc,
    BlocBuilderCondition? buildWhen,
  }) : super(key: key, bloc: bloc, buildWhen: buildWhen);

  final BlocWidgetBuilder builder;

  @override
  Widget build(BuildContext context, S state) => builder(context, state);
}

  • BlocBuilderBase

  • context.read< B>() 和 Provider.of(this, listen: false)效果是一样的,就是对后者的一个封装

  • 此处通过context.read< B>() 拿到了 我们在 BlocProvider中传入的XxxBloc对象,赋值给了_BlocBuilderBaseState中的 _bloc变量

  • BlocBuilderBase抽象了一个build方法,在 _BlocBuilderBaseState中赋值给了 BlocListener

  • BlocBuilderBase还没法看出刷新逻辑,几个重要的参数:_bloc,listener,widget.build都传给了BlocListener;需要看下BlocListener的实现

abstract class BlocBuilderBase, S>
    extends StatefulWidget {
  const BlocBuilderBase({Key? key, this.bloc, this.buildWhen})
      : super(key: key);

  final B? bloc;

  final BlocBuilderCondition? buildWhen;

  Widget build(BuildContext context, S state);

  @override
  State> createState() => _BlocBuilderBaseState();
}

class _BlocBuilderBaseState, S>
    extends State> {
  late B _bloc;
  late S _state;

  @override
  void initState() {
    super.initState();
    _bloc = widget.bloc ?? context.read();
    _state = _bloc.state;
  }

  ...

  @override
  Widget build(BuildContext context) {
    ...
    return BlocListener(
      bloc: _bloc,
      listenWhen: widget.buildWhen,
      listener: (context, state) => setState(() => _state = state),
      child: widget.build(context, _state),
    );
  }
}

  • BlocListener:参数传给父类的构造函数了,需要看下父类BlocListenerBase的实现

class BlocListener, S> extends BlocListenerBase
  const BlocListener({
    Key? key,
    required BlocWidgetListener listener,
    B? bloc,
    BlocListenerCondition? listenWhen,
    Widget? child,
  }) : super(
          key: key,
          child: child,
          listener: listener,
          bloc: bloc,
          listenWhen: listenWhen,
        );
}

  • BlocListenerBase:精简了一些逻辑代码

abstract class BlocListenerBase, S>
    extends SingleChildStatefulWidget {
  const BlocListenerBase({
    Key? key,
    required this.listener,
    this.bloc,
    this.child,
    this.listenWhen,
  }) : super(key: key, child: child);

  final Widget? child;

  final B? bloc;

  final BlocWidgetListener listener;

  final BlocListenerCondition? listenWhen;

  @override
  SingleChildState> createState() =>
      _BlocListenerBaseState();
}

class _BlocListenerBaseState, S>
    extends SingleChildState> {
  StreamSubscription? _subscription;
  late B _bloc;
  late S _previousState;

  @override
  void initState() {
    super.initState();
    _bloc = widget.bloc ?? context.read();
    _previousState = _bloc.state;
    _subscribe();
  }

  ...

  @override
  Widget buildWithChild(BuildContext context, Widget? child) {
    return child!;
  }

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

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

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

终于找了关键的代码了!

可以发现Bloc是通过 StreamController 和 listen配合实现刷新的

调用的 widget.listener(context, state),这个实现的方法是个setState,大家可以看看 _BlocBuilderBaseState这个类

_bloc.stream.listen(
  (state) {
    if (widget.listenWhen?.call(_previousState, state) ?? true) {
      widget.listener(context, state);
    }
    _previousState = state;
  },
);

精简BlocBuild

上面的BlocBuild的实现逻辑还是太绕,封装层级太多,下面写个精简版的BlocBuild

当然了,肯定会保留BlocBuild刷新的核心逻辑

class BlocEasyBuilder, V> extends StatefulWidget {
  const BlocEasyBuilder({
    Key? key,
    required this.builder,
  }) : super(key: key);

  final Function(BuildContext context, V state) builder;

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

class _BlocEasyBuilderState, V>
    extends State> {
  late T _bloc;
  late V _state;
  StreamSubscription? _listen;

  @override
  void initState() {
    _bloc = BlocProvider.of(context);
    _state = _bloc.state;

    //数据改变刷新Widget
    _listen = _bloc.stream.listen((event) {
      setState(() {});
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return widget.builder(context, _state);
  }

  @override
  void dispose() {
    _listen?.cancel();
    super.dispose();
  }
}

  • 来看下效果图:详细的使用代码,请查看:

Event机制

如果使用Bloc模式开发,会多出一个Event层,该层是定义所有的事件交互

这边提一下

  • Bloc:省略了一些代码

abstract class Bloc extends BlocBase {
  /// {@macro bloc}
  Bloc(State initialState) : super(initialState) {
    _bindEventsToStates();
  }

  StreamSubscription>? _transitionSubscription;

  StreamController? __eventController;
  StreamController get _eventController {
    return __eventController ??= StreamController.broadcast();
  }

  void add(Event event) {
    if (_eventController.isClosed) return;
    try {
      onEvent(event);
      _eventController.add(event);
    } catch (error, stackTrace) {
      onError(error, stackTrace);
    }
  }

  Stream> transformEvents(
    Stream events,
    TransitionFunction transitionFn,
  ) {
    return events.asyncExpand(transitionFn);
  }

  @protected
  @visibleForTesting
  @override
  void emit(State state) => super.emit(state);

  Stream mapEventToState(Event event);

  Stream> transformTransitions(
    Stream> transitions,
  ) {
    return transitions;
  }

  @override
  @mustCallSuper
  Future close() async {
    await _eventController.close();
    await _transitionSubscription?.cancel();
    return super.close();
  }

  void _bindEventsToStates() {
    _transitionSubscription = transformTransitions(
      transformEvents(
        _eventController.stream,
        (event) => mapEventToState(event).map(
          (nextState) => Transition(
            currentState: state,
            event: event,
            nextState: nextState,
          ),
        ),
      ),
    ).listen(
      (transition) {
        if (transition.nextState == state && _emitted) return;
        try {
          emit(transition.nextState);
        } catch (error, stackTrace) {
          onError(error, stackTrace);
        }
      },
      onError: onError,
    );
  }
}

整体逻辑比较清晰,来理一下

总结

上面几个关键的类分析完,整个Bloc的运行机制,一下子就明朗了

BlocProvider

  • 负责储存 传入XxxBloc加以储存

  • 提供的of方法,可以在BlocProvider或其子节点位置,获取到储存的XxxBloc

  • 提供回收资源的回调(回收Stream流)

BlocBase

  • 储存了传入的state对象

  • 初始化了Stream一系列对象

  • 封装了关闭Stream流的操作

BlocBuilder

  • 本质是StatefulWidget

  • 通过BlocProvider获取到XxxBloc,再通过其listener方法监听数据改变

  • 数据改变后,通过setState重建StatefulWidget,以达到局部刷新的效果

手搓一个状态管理框架

Bloc的原理相对Provider而言,要简单很多。。。

模仿Bloc的刷新机制,来手搓一个状态管理框架!用EasyC来命名吧!

手搓

  • EasyC:首先需要写一个基类,处理Stream一系列的操作

abstract class EasyC {
  EasyC(this.state) : _controller = StreamController.broadcast();

  final StreamController _controller;

  T state;

  bool _emitted = false;

  Stream get stream => _controller.stream;

  void emit(T newState) {
    if (_controller.isClosed) return;
    if (state == newState && _emitted) return;
    state = newState;
    _controller.add(state);
    _emitted = true;
  }

  @mustCallSuper
  Future close() async {
    await _controller.close();
  }
}

  • EasyCProvider

  • 这里就不使用Provider框架提供的InheritedProvider了

  • 这边我用InheritedWidget手搓了一个

  • of方法和stream流的关闭都搞定了;不用手动关流,也不用写StatefulWidget了!

class EasyCProvider extends InheritedWidget {
  EasyCProvider({
    Key? key,
    Widget? child,
    required this.create,
  }) : super(key: key, child: child ?? Container());

  final T Function(BuildContext context) create;

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) => false;

  @override
  InheritedElement createElement() => EasyCInheritedElement(this);

  static T of(BuildContext context) {
    var inheritedElement =
        context.getElementForInheritedWidgetOfExactType>()
            as EasyCInheritedElement?;

    if (inheritedElement == null) {
      throw 'not found';
    }

    return inheritedElement.value;
  }
}

class EasyCInheritedElement extends InheritedElement {
  EasyCInheritedElement(EasyCProvider widget) : super(widget);

  bool _firstBuild = true;

  late T _value;

  T get value => _value;

  @override
  void performRebuild() {
    if (_firstBuild) {
      _firstBuild = false;
      _value = (widget as EasyCProvider).create(this);
    }

    super.performRebuild();
  }

  @override
  void unmount() {
    _value.close();
    super.unmount();
  }
}

  • EasyCBuilder:最后整一个定点刷新Widget

class EasyCBuilder, V> extends StatefulWidget {
  const EasyCBuilder({
    Key? key,
    required this.builder,
  }) : super(key: key);

  final Function(BuildContext context, V state) builder;

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

class _EasyCBuilderState, V>
    extends State> {
  late T _easyC;
  late V _state;
  StreamSubscription? _listen;

  @override
  void initState() {
    _easyC = EasyCProvider.of(context);
    _state = _easyC.state;

    //数据改变刷新Widget
    _listen = _easyC.stream.listen((event) {
      setState(() {});
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return widget.builder(context, _state);
  }

  @override
  void dispose() {
    _listen?.cancel();
    super.dispose();
  }
}

上面这三个文件,基本就把Bloc的刷新机制再现了

同时,也去掉了我心中的一个疙瘩,Bloc源码对 Provider的 _startListening方法,莫名其妙的使用。。。

使用

使用基本和Bloc一摸一样

我本来想把emit俩个新旧state对象对比的判断去掉,但是想想Bloc作者对这个理念好像有很深的执念,在很多地方都做了处理;所以,这边我也就保留了,也可以保留Bloc原汁原味的用法

  • view

class CounterEasyCPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return EasyCProvider(
      create: (BuildContext context) => CounterEasyC(),
      child: Builder(builder: (context) => _buildPage(context)),
    );
  }

  Widget _buildPage(BuildContext context) {
    final easyC = EasyCProvider.of(context);

    return Scaffold(
      appBar: AppBar(title: Text('自定义状态管理框架-EasyC范例')),
      body: Center(
        child: EasyCBuilder(
          builder: (context, state) {
            return Text(
              '点击了 ${easyC.state.count} 次',
              style: TextStyle(fontSize: 30.0),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => easyC.increment(),
        child: Icon(Icons.add),
      ),
    );
  }
}

  • logic

class CounterEasyC extends EasyC {
  CounterEasyC() : super(CounterEasyCState().init());

  ///自增
  void increment() => emit(state.clone()..count = ++state.count);
}

  • state

class CounterEasyCState {
  late int count;

  CounterEasyCState init() {
    return CounterEasyCState()..count = 0;
  }

  CounterEasyCState clone() {
    return CounterEasyCState()..count = count;
  }
}

  • 效果图

全局也是可以的,和Provider没什么不一样,我这边就不重复写了

总结

这手搓的EasyC框架,保留Bloc刷新机制的精髓,同时,也做了大量的精简

相信有缘人只要用心看看,一定能够理解的

Bloc的源码并不复杂,他是对Stream的使用,做了一个大大的精简,基本使用痛点,全都封装起来,内部处理了

最后

留言板

Provider和Bloc的源码解析终于写完了,就差最后一篇GetX了。。。

为了证明我写的分析源码是有作用且有效果的,在末尾,我都根据其状态管理框架的刷新机制,手搓了一个全新的状态管理框架

选择状态管理框架,应该是一件比较慎重的事;事先可以先看看其原理,理解了他的内部运转机制,就完全可以去按需选择了,因为你明白了它的内部运转机制,就算使用过程中出现什么问题,你也能从容应对了;如果你怕作者弃坑或不满意其功能,选择你自己想要的刷新机制,自己去手搓一个!

Provider,Bloc,GetX这三个框架,我都写了相应插件,如果你选择的状态管理框架是这个三者中任意一个,相信这些插件,都能帮你完成一些重复的工作量

相关地址

  • 文章中Demo的Github地址:

  • Web效果:https://cnad666.github.io/flutter_use/web/index.html

  • 如果相关功能按钮没看到,可能需要你清下浏览器缓存

  • Windows:

  • 密码:xdd666

系列文章