在上篇文章中,我詳細介紹了 InheritedWidget 及 ScopedModel 實現原理與方法,有同窗說找不到源碼,其實上篇文章包括這篇文章裏的源碼都按步驟放在樣例代碼裏了,有同窗說有點懵,其實上一篇的概念過多並且自己我表達也不是很清晰,英文文檔中我也解釋的沒有徹底語義化,因此還請諒解,結合代碼你會有更好地理解。html
這篇的重點我將放在 BloC 的實現上面,咱們已經知道 Strems 的概念,RXDart 是依賴 Streams 使用的輸入(Sink)和輸出(Stream)封裝而成的響應式庫,BloC 基於此即可以實時偵聽數據的變化而改變數據,而且,BloC 主要解決的問題就是他不會一刀切的更新整個狀態樹,它關注的是數據,通過一系列處理後獲得它而且只改變應用它的 widget。react
咱們先來實踐一下如何在 widget 中使用數據。Flutter 提供了一個名爲 StreamBuilder 的 StatefulWidget。git
StreamBuilder 監聽 Stream,每當一些數據流出 Stream 時,它會自動重建,調用其構建器回調。github
StreamBuilder<T>(
key: ...optional, the unique ID of this Widget...
stream: ...the stream to listen to...
initialData: ...any initial data, in case the stream would initially be empty...
builder: (BuildContext context, AsyncSnapshot<T> snapshot){
if (snapshot.hasData){
return ...the Widget to be built based on snapshot.data
}
return ...the Widget to be built if no data is available
},
)
複製代碼
如下示例使用 Stream 而不是 setState() 模擬默認的「計數器」應用程序:app
import 'dart:async';
import 'package:flutter/material.dart';
class CounterPage extends StatefulWidget {
@override
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _counter = 0;
final StreamController<int> _streamController = StreamController<int>();
@override
void dispose(){
_streamController.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Stream version of the Counter App')),
body: Center(
child: StreamBuilder<int>(
stream: _streamController.stream,
initialData: _counter,
builder: (BuildContext context, AsyncSnapshot<int> snapshot){
return Text('You hit me: ${snapshot.data} times');
}
),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: (){
_streamController.sink.add(++_counter);
},
),
);
}
}
複製代碼
是時候展示真正的計技術了,咱們依然將 BloC 用於默認的計數器應用中:less
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Streams Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: BlocProvider<IncrementBloc>(
bloc: IncrementBloc(),
child: CounterPage(),
),
);
}
}
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final IncrementBloc bloc = BlocProvider.of<IncrementBloc>(context);
return Scaffold(
appBar: AppBar(title: Text('Stream version of the Counter App')),
body: Center(
child: StreamBuilder<int>(
stream: bloc.outCounter,
initialData: 0,
builder: (BuildContext context, AsyncSnapshot<int> snapshot){
return Text('You hit me: ${snapshot.data} times');
}
),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: (){
bloc.incrementCounter.add(null);
},
),
);
}
}
class IncrementBloc implements BlocBase {
int _counter;
//
// Stream to handle the counter
//
StreamController<int> _counterController = StreamController<int>();
StreamSink<int> get _inAdd => _counterController.sink;
Stream<int> get outCounter => _counterController.stream;
//
// Stream to handle the action on the counter
//
StreamController _actionController = StreamController();
StreamSink get incrementCounter => _actionController.sink;
//
// Constructor
//
IncrementBloc(){
_counter = 0;
_actionController.stream
.listen(_handleLogic);
}
void dispose(){
_actionController.close();
_counterController.close();
}
void _handleLogic(data){
_counter = _counter + 1;
_inAdd.add(_counter);
}
}
複製代碼
這是上篇文章的最後給打你們製造懸念的代碼?五臟俱全,基本已經實現了 BloC。async
結合上面的例子來分析 BloC 體現出來的優點:(建議先將這段代碼跑起來!)ide
你能夠看到 CounterPage(第21-45行),其中沒有任何業務邏輯。函數
它承擔的負責僅有:佈局
此外,整個業務邏輯集中在一個單獨的類「IncrementBloc」中。
若是如今,若是咱們須要更改業務邏輯,只需更新方法 _handleLogic(第77-80行)。 也許新的業務邏輯將要求作很是複雜的事情...... CounterPage 永遠與它無關!
如今,測試業務邏輯也變得更加容易。
無需再經過用戶界面測試業務邏輯。 只須要測試 IncrementBloc 類。
因爲使用了 Streams,您如今能夠獨立於業務邏輯組織布局。
你能夠從應用程序中的任何位置用任何操做:只需調用 .incrementCounter 接收器便可。
您能夠在任何頁面的任何位置顯示計數器,只需艦艇監聽 .outCounter 流。
不用 setState()
而是使用 StreamBuilder,從而大大減小了「構建」的數量,只減小了所需的數量。
這是性能上的巨提升!
爲了達到各類目的,BLoC 須要可訪問。
有如下幾種方法能夠訪問它:
經過全局單例的變量
這種方式很容易實現,但不推薦。 此外,因爲 Dart 中沒有類析構函數,所以咱們永遠沒法正確釋放資源。
做爲本地實例
您能夠實例化 BLoC 的本地實例。 在某些狀況下,此解決方案徹底符合需求。 在這種狀況下,您應該始終考慮在 StatefulWidget 中初始化,以便您能夠利用 dispose() 方法來釋放它。
由根組件提供 使其可訪問的最多見方式是經過根 Widget,將其實現爲 StatefulWidget。
如下代碼給出了一個通用 BlocProvider 的示例:(這個例子牛逼!)
// Generic Interface for all BLoCs
abstract class BlocBase {
void dispose();
}
// Generic BLoC provider
class BlocProvider<T extends BlocBase> extends StatefulWidget {
BlocProvider({
Key key,
@required this.child,
@required this.bloc,
}): super(key: key);
final T bloc;
final Widget child;
@override
_BlocProviderState<T> createState() => _BlocProviderState<T>();
static T of<T extends BlocBase>(BuildContext context){
final type = _typeOf<BlocProvider<T>>();
BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
return provider.bloc;
}
static Type _typeOf<T>() => T;
}
class _BlocProviderState<T> extends State<BlocProvider<BlocBase>>{
@override
void dispose(){
widget.bloc.dispose();
super.dispose();
}
@override
Widget build(BuildContext context){
return widget.child;
}
}
複製代碼
關於這段通用的 BlocProvider 仔細回味,你會發現其精妙之處!
通用 BlocProvider 的一些解釋:
首先,如何將其用做數據提供者?
若是你看了上面BloC 計數器的示例代碼示例代碼,您將看到如下代碼行(第12-15行)
home: BlocProvider<IncrementBloc>(
bloc: IncrementBloc(),
child: CounterPage(),
),
複製代碼
使用以上代碼,咱們實例化了一個想要處理 IncrementBloc 的新 BlocProvider,並將 CounterPage 呈現爲子組件。
從 BlocProvider 開始的子組件的任何組件部分都將可以經過如下行訪問 IncrementBloc:
IncrementBloc bloc = BlocProvider.of<IncrementBloc>(context);
複製代碼
BLoC 的基本使用就介紹完了,全部實例代碼在這裏 ,我將每種狀態管理的方法分模塊放在裏面,選擇使用哪一種方式運行代碼便可。
在大型項目中,這是很是可取的。 給如下幾個建議:
如下示例代碼在整個應用程序的頂部使用 ApplicationBloc,而後在 CounterPage 頂部使用 IncrementBloc。該示例還展現瞭如何使用兩個 Bloc:
void main() => runApp(
BlocProvider<ApplicationBloc>(
bloc: ApplicationBloc(),
child: MyApp(),
)
);
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context){
return MaterialApp(
title: 'Streams Demo',
home: BlocProvider<IncrementBloc>(
bloc: IncrementBloc(),
child: CounterPage(),
),
);
}
}
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context){
final IncrementBloc counterBloc = BlocProvider.of<IncrementBloc>(context);
final ApplicationBloc appBloc = BlocProvider.of<ApplicationBloc>(context);
...
}
}
複製代碼
我爲此也整理了一個將 BLoC 結合 InheritedWidget 使用的示例:bloc_inherited(在 Vscode 打開這段代碼是 [close_sinks] 的警告的)
在不少與 BLoC 相關的文章中,您將看到 Provider 的實現實際上是一個 InheritedWidget。
固然, 這是徹底能夠實現的,然而,
這 3 點解釋了我爲什麼將通用 BlocProvider 實現爲 StatefulWidget,這樣我就能夠釋放資源。
Flutter沒法實例化泛型類型
不幸的是,Flutter 沒法實例化泛型類型,咱們必須將 BLoC 的實例傳遞給 BlocProvider。 爲了在每一個BLoC中強制執行 dispose() 方法,全部BLoC都必須實現 BlocBase 接口。
在使用 InheritedWidget 並經過 context.inheritFromWidgetOfExactType(...) 獲取指定類型最近的 Widget 時,每當InheritedWidget 的父級或者子佈局發生變化時,這個方法會自動將當前 「context」(= BuildContext)註冊到要重建的 widget 當中。
請注意,爲了徹底正確,我剛纔解釋的與 InheritedWidget 相關的問題只發生在咱們將 InheritedWidget 與 StatefulWidget 結合使用時。 當您只使用沒有 State 的 InheritedWidget 時,問題就不會發生。
Flutter 狀態管理的這幾種模式一樣能夠適用於不少軟件開發中,而 BloC 模式最初的設想是實現容許獨立於平臺重用相同的代碼!所以多花時間學習這類模式即是軟件開發的根基。
個人建議是將實例代碼運行出來閱讀代碼,依靠文章理解!但願能幫助到你!
這篇內容是我反覆看谷歌大會寫完的。
而且大量借鑑了 Reactive Programming - Streams - BLoC 這篇文章。