Flutter的不少靈感來自於React,它的設計思想是數據與視圖分離,由數據映射渲染視圖。因此在Flutter中,它的Widget是immutable的,而它的動態部分所有放到了狀態(State)中。react
在以前的文章中,咱們已經介紹了scoped model與redux兩種狀態管理方案在flutter中的應用。他們彷佛都還不錯,但都仍是美中不足。今天我將介紹Google提出的一種全新的解決方案——BLoC。git
在正式開始介紹前,我但願您已經閱讀並理解了stream的相關知識,後面的內容都基於此。若是您還未了解過dart:stream 的話,我建議您先閱讀這篇文章:Dart:什麼是Stream。github
咱們一直在找尋強大的狀態管理方式。也許你並無想過,flutter自身已經爲咱們提供了狀態管理,並且你常常都在用到。編程
沒錯,它就是 Stateful widget。當咱們接觸到flutter的時候,首先須要瞭解的就是有些小部件是有狀態的,有些則是無狀態的。stateless widget 與 stateful widget。redux
在stateful widget中,咱們widget的描述信息被放進了State,而stateful widget只是持有一些immutable的數據以及建立它的狀態而已。它的全部成員變量都應該是final的,當狀態發生變化的時候,咱們須要通知視圖從新繪製,這個過程就是setState。網絡
這看上去很不錯,咱們改變狀態的時候setState一下就能夠了。 在咱們一開始構建應用的時候,也許很簡單,咱們這時候可能並不須要狀態管理。app
可是隨着功能的增長,你的應用程序將會有幾十個甚至上百個狀態。這個時候你的應用應該會是這樣。less
一旦當app的交互變得複雜,setState出現的次數便會顯著增長,每次setState都會從新調用build方法,這勢必對於性能以及代碼的可閱讀性帶來必定的影響。能不能不使用setState就能刷新頁面呢?如何在多個頁面中共享狀態?咱們但願有一種更增強大的方式,來管理咱們的狀態。異步
BLoC是一種利用reactive programming方式構建應用的方法,這是一個由流構成的徹底異步的世界。 async
BLoC可以容許咱們完美的分離業務邏輯!不再用考慮何時須要刷新屏幕了,一切交給StreamBuilder和BLoC!和StatefulWidget說拜拜!!
BLoC表明業務邏輯組件(Business Logic Component),由來自Google的兩位工程師 Paolo Soares和Cong Hui設計,並在2018年DartConf期間(2018年1月23日至24日)首次展現。點擊觀看Youtube視頻。。
這裏咱們以一個最簡單的CountApp舉例。簡單介紹BLoC的用法。該項目完整代碼已上傳Github。
這是一個在不一樣頁面使用BLoC共享狀態信息的app。這兩個頁面都依賴於一個數字,這個數字會隨着咱們按下按鈕的次數而增長。
咱們這裏的要求很簡單,僅僅只是輸出一個數字而已,而後有一個方法可以讓數字加一。因此咱們須要建立一條可以經過int類型數據的流。
import 'dart:async';
class CountBLoC {
int _count;
StreamController<int> _countController;
CountBLoC() {
_count = 0;
_countController = StreamController<int>();
}
Stream<int> get value => _countController.stream;
increment() {
_countController.sink.add(++_count);
}
dispose() {
_countController.close();
}
}
複製代碼
一個應用須要大量開發人員參與,你寫的代碼也許在幾個月以後被另一個開發看到了,這時候假如你的變量沒有被保護的話,也許一樣是讓count++,他會用countController.sink.add(++_count)這種方法,而不是調用 increment方法。
雖然兩種方式的效果徹底同樣,可是第二種方式將會讓咱們的business logic零散的混入其餘代碼中,提升了代碼耦合程度,很是不利於代碼的維護以及閱讀,因此爲了讓BLoC徹底分離咱們的業務邏輯,請務必使用私有變量。
這裏有三種方式建立bloc
因爲咱們須要在兩個屏幕中訪問同一個bloc,因此咱們只能選擇全局單例模式或者scoped模式。
全局單例咱們只須要在bloc類的文件中建立一個bloc實例便可。不過我並不推薦這種作法,由於不須要用這個bloc的時候,咱們應該釋放它。
可是爲了讓我解釋的儘可能簡單,後面我將會基於全局單例模式來介紹。
建立一個bloc provider類,這裏咱們須要藉助InheritWidget,實現of方法並讓updateShouldNotify返回true。
class BlocProvider extends InheritedWidget {
CountBLoC bLoC = CountBLoC();
BlocProvider({Key key, Widget child}) : super(key: key, child: child);
@override
bool updateShouldNotify(_) => true;
static CountBLoC of(BuildContext context) =>
(context.inheritFromWidgetOfExactType(BlocProvider) as BlocProvider).bLoC;
}
複製代碼
小提示: 這裏updateShouldNotify須要傳入一個InheritedWidget oldWidget,可是咱們強制返回true,因此傳一個「_」佔位。
這裏以第一個頁面爲例,僅僅顯示文字+數字。
StreamBuilder<int>(
stream: bloc.value,
initialData: 0,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
return Text(
'You hit me: ${snapshot.data} times',
style: Theme.of(context).textTheme.display1,
);
})
複製代碼
floatingActionButton: FloatingActionButton(
onPressed: ()=> bloc.increment(),
child: Icon(Icons.add),
)
複製代碼
因爲這裏並不涉及widget的重構,咱們只須要調用bloc的功能便可。
咱們構建好ui後,運行程序將會發現這件奇怪的事。
第二個頁面的數字沒法顯示,並且控制檯拋出了這個異常。flutter: Bad state: Stream has already been listened to.
複製代碼
這是因爲流被重複監聽致使的。 兩個頁面中都須要顯示這個數字,那麼就使用了兩個StreamBuilder。而StreamBuilder都監聽的同一個流,因此致使了流被重複監聽了。
還記得咱們在Dart|什麼是Stream中說的兩種流嗎。沒錯,咱們建立StreamController的時候,默認是建立的單訂閱流。因此咱們須要將流改爲廣播流。
_countController = StreamController.broadcast<int>();
複製代碼
只須要在建立StreamController的時候調用broadcast方法便可。
這是因爲咱們在第一次pop UnderPage的時候,這個頁面已經被銷燬了。當咱們再push進去的時候,StreamBuilder沒法收聽到最後一次事件(已經流過去了),只能顯示initiaData。而再次點擊時,正確的數字被add進了流,StreamController收聽到了它,因此又能顯示正確的數據了。
這個問題可以解決嗎?
答案是確定的,使用rxdart!rxdart極大的加強了流的功能,解決方法將會在後續rxdart篇介紹。
大型應用程序須要多個BLoC。一個好的模式是爲每一個屏幕使用一個頂級組件,併爲每一個複雜足夠的小部件使用一個。可是,太多的BLoC會變得很麻煩。此外,若是您的應用中有數百個可觀察量(流),則會對性能產生負面影響。換句話說:不要過分設計你的應用程序。
——Filip Hracek
下面有一些優秀的文章可以給您更多參考
本次所用到的代碼已經上傳Github:github.com/OpenFlutter…
bloc是一個優秀的狀態管理方式,它可以幫助咱們更好的構建複雜的大型應用。可是他還不是完美的(至少目前不是)。它在處理大量異步事件以及分離業務邏輯上表現很優秀,可是在共享狀態上還有一些缺陷。
有人嘗試將redux與bloc結合使用,試圖找到突破口。這裏有一個專門爲它編寫的庫:rebloc。感興趣的朋友能夠自行了解一下。
若是你在使用bloc進行狀態管理的時候有任何好的點子,或者是疑問,歡迎在下方評論區以及個人郵箱1652219550a@gmail.com留言,我會在24小時內與您聯繫!
下一篇文章將會爲你們介紹Reactive Programming的最佳庫RxDart在BLoC上的實踐,敬請期待。