在實際項目中我覺得大家一般不會直接使用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章節有說過傳送門 ( http://www.lxweimin.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的整個過程都記錄在里面了
http://www.lxweimin.com/c/36554cb4c804
最后附上demo 地址