写点什么

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

用户头像
小呆呆666
关注
发布于: 51 分钟前

前言

看了 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<T>(context, listen: true) 或者去掉 e.markNeedsNotifyDependents() ,我都不会说这话。。。

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

  • 抛弃了 Provider 机制极简的 Callback 回调机制,选择了 Stream 流这种。。。

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

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

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


如果我理解有误,恳请大家指出,我真的很想找出点其中所蕴含的深意,改变我上面的想法。。。


为啥说心情复杂呢?


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


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


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

使用

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

调整心路的历程,可参照:flutter_bloc使用解析---骚年,你还在手搭bloc吗!

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

插件

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


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


Wrap Widget 规则来着:intellij_generator_plugin


快捷代码生成规则来着: intellij_generator_plugin


  • 在 Android Studio 里面搜索 flutter bloc



  • 生成模板代码



  • 支持修改后缀



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



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


用法

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


Cubit 模式


  • view


class CounterPage extends StatelessWidget {  final cubit = CounterCubit();
@override Widget build(BuildContext context) { return BlocProvider( create: (BuildContext context) => cubit, child: Container(), ); }}
复制代码


  • cubit


class CounterCubit extends Cubit<CounterState> {  CounterCubit() : super(CounterState().init());}
复制代码


  • state


class CounterState {  CounterState init() {    return CounterState();  }
CounterState clone() { return CounterState(); }}
复制代码


Bloc 模式


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


class CounterPage extends StatelessWidget {  final bloc = CounterBloc();
@override Widget build(BuildContext context) { return BlocProvider( create: (BuildContext context) => bloc..add(InitEvent()), child: Container(), ); }}
复制代码


  • bloc


class CounterBloc extends Bloc<CounterEvent, CounterState> {  CounterBloc() : super(CounterState().init());
@override Stream<CounterState> mapEventToState(CounterEvent event) async* { if (event is InitEvent) { yield await init(); } }
Future<CounterState> 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,麻烦及时反馈哈。。。


这里就不重复写怎么使用了,使用明细可参照:flutter_bloc使用解析---骚年,你还在手搭bloc吗!

前置知识

想弄懂 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<StreamPage> { final logic = StreamLogic();
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Bloc-Bloc范例')), body: Center( child: StreamBuilder<StreamState>( 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<StreamState>.broadcast();
Stream<StreamState> 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<T extends BlocBase<Object?>>    extends SingleChildStatelessWidget with BlocProviderSingleChildWidget {  /// {@macro bloc_provider}  BlocProvider({    Key? key,    required Create<T> 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<T>? _create;
final T? _value;
static T of<T extends BlocBase<Object?>>( BuildContext context, { bool listen = false, }) { try { return Provider.of<T>(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<T>.value( value: value, startListening: _startListening, lazy: lazy, child: child, ) : InheritedProvider<T>( create: _create, dispose: (_, bloc) => bloc.close(), startListening: _startListening, child: child, lazy: lazy, ); }
static VoidCallback _startListening( InheritedContext<BlocBase> 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 的内容

  • 这内部的原理是比较复杂且很重要的,感兴趣请查看:源码篇:Flutter Provider的另一面(万字图文+插件)

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

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

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

  • BlocProvider.of<T>

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

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

  • 原理:源码篇:Flutter Provider的另一面(万字图文+插件),还是在这篇文章里

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

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


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


  1. BlocProvider 或会储存外部传入的 XxxBloc 实例,XxxBloc 类必须继承 BlocBase

  2. BlocProvider 存储的 XxxBloc 实例,可以通过 BlocProvider.of<T>获取到(必须是在 BlocProvider 或其子 Widget)

  3. BlocProvider 获取的实例 XxxBloc 能够自动释放;BlocProvider.value 命名构造函数实例的 XxxBloc 不会自动释放


BlocProvider实现了上面这三个碉堡的功能,基本就可以把Stream使用模式彻底精简了


  • 图示


基石 BlocBase

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


  • BlocBase


abstract class BlocBase<State> {  BlocBase(this._state) {    Bloc.observer.onCreate(this);  }
StreamController<State>? __stateController; StreamController<State> get _stateController { return __stateController ??= StreamController<State>.broadcast(); }
State _state;
bool _emitted = false;
State get state => _state;
Stream<State> get stream => _stateController.stream;
@Deprecated( 'Use stream.listen instead. Will be removed in v8.0.0', ) StreamSubscription<State> 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<State>(currentState: this.state, nextState: state)); _state = state; _stateController.add(_state); _emitted = true; }
@mustCallSuper void onChange(Change<State> 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<void> close() async { Bloc.observer.onClose(this); await _stateController.close(); }}
复制代码


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

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


  1. 储存了传入的 state 对象

  2. 每次使用 emit 刷新的时候,会将传入 state 替换之前存储 state 对象

  3. emit 做了一个判断,如果传入 state 和存储 state 对象相同,将不执行刷新操作(这就是我在 State 类里面,加 clone 方法的原因)

  4. 初始化了 Stream 一系列对象

  5. 封装了关闭 Stream 流的操作


  • 将上面的代码精简下


abstract class BlocBase<T> {  BlocBase(this.state) : _stateController = StreamController<T>.broadcast();
final StreamController<T> _stateController;
T state;
bool _emitted = false;
Stream<T> 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<void> close() async { await _stateController.close(); }}
复制代码

BlocBuilder

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


  • BlocBuilder

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

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


typedef BlocWidgetBuilder<S> = Widget Function(BuildContext context, S state);
class BlocBuilder<B extends BlocBase<S>, S> extends BlocBuilderBase<B, S> { const BlocBuilder({ Key? key, required this.builder, B? bloc, BlocBuilderCondition<S>? buildWhen, }) : super(key: key, bloc: bloc, buildWhen: buildWhen);
final BlocWidgetBuilder<S> builder;
@override Widget build(BuildContext context, S state) => builder(context, state);}
复制代码


  • BlocBuilderBase

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

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

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

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


abstract class BlocBuilderBase<B extends BlocBase<S>, S>    extends StatefulWidget {  const BlocBuilderBase({Key? key, this.bloc, this.buildWhen})      : super(key: key);
final B? bloc;
final BlocBuilderCondition<S>? buildWhen;
Widget build(BuildContext context, S state);
@override State<BlocBuilderBase<B, S>> createState() => _BlocBuilderBaseState<B, S>();}
class _BlocBuilderBaseState<B extends BlocBase<S>, S> extends State<BlocBuilderBase<B, S>> { late B _bloc; late S _state;
@override void initState() { super.initState(); _bloc = widget.bloc ?? context.read<B>(); _state = _bloc.state; }
...
@override Widget build(BuildContext context) { ... return BlocListener<B, S>( bloc: _bloc, listenWhen: widget.buildWhen, listener: (context, state) => setState(() => _state = state), child: widget.build(context, _state), ); }}
复制代码


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


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


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


abstract class BlocListenerBase<B extends BlocBase<S>, 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<S> listener;
final BlocListenerCondition<S>? listenWhen;
@override SingleChildState<BlocListenerBase<B, S>> createState() => _BlocListenerBaseState<B, S>();}
class _BlocListenerBaseState<B extends BlocBase<S>, S> extends SingleChildState<BlocListenerBase<B, S>> { StreamSubscription<S>? _subscription; late B _bloc; late S _previousState;
@override void initState() { super.initState(); _bloc = widget.bloc ?? context.read<B>(); _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<T extends BlocBase<V>, V> extends StatefulWidget {  const BlocEasyBuilder({    Key? key,    required this.builder,  }) : super(key: key);
final Function(BuildContext context, V state) builder;
@override _BlocEasyBuilderState createState() => _BlocEasyBuilderState<T, V>();}
class _BlocEasyBuilderState<T extends BlocBase<V>, V> extends State<BlocEasyBuilder<T, V>> { late T _bloc; late V _state; StreamSubscription<V>? _listen;
@override void initState() { _bloc = BlocProvider.of<T>(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(); }}
复制代码


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


Event 机制

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

这边提一下


  • Bloc:省略了一些代码


abstract class Bloc<Event, State> extends BlocBase<State> {  /// {@macro bloc}  Bloc(State initialState) : super(initialState) {    _bindEventsToStates();  }
StreamSubscription<Transition<Event, State>>? _transitionSubscription;
StreamController<Event>? __eventController; StreamController<Event> get _eventController { return __eventController ??= StreamController<Event>.broadcast(); }
void add(Event event) { if (_eventController.isClosed) return; try { onEvent(event); _eventController.add(event); } catch (error, stackTrace) { onError(error, stackTrace); } }
Stream<Transition<Event, State>> transformEvents( Stream<Event> events, TransitionFunction<Event, State> transitionFn, ) { return events.asyncExpand(transitionFn); }
@protected @visibleForTesting @override void emit(State state) => super.emit(state);
Stream<State> mapEventToState(Event event);
Stream<Transition<Event, State>> transformTransitions( Stream<Transition<Event, State>> transitions, ) { return transitions; }
@override @mustCallSuper Future<void> 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, ); }}
复制代码


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


  1. Bloc 是抽象类

  2. 构造函数里面调用 _bindEventsToStates() 方法

  3. Bloc 抽象了一个 mapEventToState(Event event)方法,继承 Bloc 抽象类,必须实现该方法

  4. Bloc 类中,实例了 Stream 流对象,来做 Event 的事件触发机制

  5. 添加 Event 事件时,会触发 _bindEventsToStates() 方法中的 listener 回调

  6. _bindEventsToStates 里面做了一些操作

  7. 被添加的 Event 事件:events.asyncExpand(transitionFn);先将自身 Event 参数传入 transitionFn 方法中执行

  8. transitionFn 的逻辑是:将 Event 参数传入 mapEventToState 中,然后 mapEventToState 回传 State 对象

  9. 然后触发 listen 回调,listen 中,将 state 传 emit 中,然后触发刷新控件重建

总结

上面几个关键的类分析完,整个 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<T> {  EasyC(this.state) : _controller = StreamController<T>.broadcast();
final StreamController<T> _controller;
T state;
bool _emitted = false;
Stream<T> 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<void> close() async { await _controller.close(); }}
复制代码


  • EasyCProvider

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

  • 这边我用 InheritedWidget 手搓了一个

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


class EasyCProvider<T extends EasyC> 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<T extends EasyC>(BuildContext context) { var inheritedElement = context.getElementForInheritedWidgetOfExactType<EasyCProvider<T>>() as EasyCInheritedElement<T>?;
if (inheritedElement == null) { throw 'not found'; }
return inheritedElement.value; }}
class EasyCInheritedElement<T extends EasyC> extends InheritedElement { EasyCInheritedElement(EasyCProvider<T> widget) : super(widget);
bool _firstBuild = true;
late T _value;
T get value => _value;
@override void performRebuild() { if (_firstBuild) { _firstBuild = false; _value = (widget as EasyCProvider<T>).create(this); }
super.performRebuild(); }
@override void unmount() { _value.close(); super.unmount(); }}
复制代码


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


class EasyCBuilder<T extends EasyC<V>, V> extends StatefulWidget {  const EasyCBuilder({    Key? key,    required this.builder,  }) : super(key: key);
final Function(BuildContext context, V state) builder;
@override _EasyCBuilderState createState() => _EasyCBuilderState<T, V>();}
class _EasyCBuilderState<T extends EasyC<V>, V> extends State<EasyCBuilder<T, V>> { late T _easyC; late V _state; StreamSubscription<V>? _listen;
@override void initState() { _easyC = EasyCProvider.of<T>(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 {  final easyC = CounterEasyC();
@override Widget build(BuildContext context) { return EasyCProvider( create: (BuildContext context) => easyC, child: Scaffold( appBar: AppBar(title: Text('自定义状态管理框架-EasyC范例')), body: Center( child: EasyCBuilder<CounterEasyC, CounterEasyCState>( 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<CounterEasyCState> {  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 地址:flutter_use

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

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

  • Windows:Windows平台安装包

  • 密码:xdd666


系列文章


发布于: 51 分钟前阅读数: 5
用户头像

小呆呆666

关注

2021,葬爱不在低调 2020.08.17 加入

还未添加个人简介

评论

发布
暂无评论
源码篇:Flutter Bloc背后的思想,一篇纠结的文章