在实际项目中我觉得大家一般不会直接使用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 地址