文章結構:web
widget只作UI展現,bloc實現控制邏輯,model作數據封裝。 我在運營線的新需求開發中實際使用了bloc作了一個新的頁面,就是商機敗北頁面。 這個頁面有三級的敗北緣由選擇,敗北緣由是級聯的,還有每次選擇設備後要從新請求庫存,提交敗北等操做。ajax
在同一個頁面中跨widget數據獲取數據和操做比較多,例如提交敗北的時候,須要獲取多個子widget中的數值,若是沒有使用bloc的話,就須要在子widget的構造方法中傳遞model或回調方法。數組
若是頁面層級較多,這種model和回調方法就須要逐級傳遞。 例如在下圖左邊,須要把submiModel或回調方法從最上層一直傳遞到最底部,才能作到收集全部的子widget中的信息,最終集合全部的widget中的參數到submitModel中,提交敗北請求。緩存
下面右邊的代碼就是這樣逐層傳遞參數的方法,若是層級不少的話會很是頭疼,不只要重複的聲明變量,並且若是修改的話要每一個地方都改。bash
而使用了bloc的話,構造函數就特別乾淨,不須要逐層傳遞model或者回調方法。服務器
下面的兩塊代碼都是頁面widget結構中最下層的兩個widget,不須要在構造方法中傳遞任何參數或回調方法,可是一樣能調用拿到model和調用回調方法。怎麼實現的繼續往下看。:)網絡
以前在項目中直接使用setState會產生一些問題,例如當前State是unmounted狀態,這時直接調用setState會拋異常。異步
而使用bloc的話刷新頁面是使用以下方式。關鍵的代碼是_requestController.add ide
咱們現有項目中有一些很是複雜的頁面,可是隻使用了一個文件,全部的業務邏輯和widget組件都在這個頁面裏面。函數
下面是一段這種混雜網絡請求、widget顯示的代碼示例。
這樣相比於多個子widget的方式就不用逐層傳遞數據和回調方法。可是破壞了面向對象開發的基本原則:信息隱藏,好比修改某個widget的一個小功能,由於在一個很大的文件中修改,其中業務邏輯錯綜複雜,改一個小功能可能就會影響其餘邏輯,產生意料不到的後果,這一塊的代碼就變得很是難以維護。
同時直接調用setState刷新整個頁面的性能也有問題,也會產生拋異常的問題。
而使用bloc的方式會更加自由,能夠像第二點中的圖中同樣使用多個子widget,在子widget中進行刷新。
也能夠直接改造上圖的代碼,在一個widget中實現局部刷新。
第四個優勢其實和第三個是一併實現的,都是經過streamBuidler和streamController,這二者是配套使用的。其實bloc就是streamBuilder+streamController+ancestorWidgetOfExactType,和web端的ajax比較相似,幾項現有技術整合出了新的東西。
StreamBuilder要傳入一個stream對象才能實現刷新,而這個stream要從streamController中獲取的。
當在上面的代碼中調用_requestController.add(deviceModel)後,下面的StreamBuilder就會自動調用builder方法,實現局部刷新StreamBuilder,而不是刷新外層的InkWell。
StreamBuilder是一個Widget,能夠在任意地方插入,實現局部刷新很是簡單。
要實現一個BlocProvider,現有項目中已經集成,整個項目只須要一個BlocProvider,能夠放在Utils目錄中。
實現Bloc其實就只用這一個類和flutter自帶的方法就好了,代碼很是簡單,並不須要在yaml文件中引入第三方庫。
全部的bloc都須要繼承BlocBase,一般一個bloc對應一個會刷新頁面的業務邏輯(如網絡請求、切換tab)。
下面是bloc建立和blocProvider的代碼。
bloc觸發刷新的方式就是使用StreamBuilder+StreamController,可是直接使用StreamController有一個很大的缺點就是隻能進行一對一的listen,且模式很是單一。因此須要使用到rxdart,下面會講到。
bloc另外一重要特性就是跨層級獲取bloc,在bloc能夠獲取到model或者調用方法。
其中使用了ancestorWidgetOfExactType,由於flutter widget是樹狀結構的,結構層次保存在BuildContext中,能夠從子節點依次往父節點找符合類型的widget,因此全部使用了BlocProvider包裹的widget均可以經過of方法找到,再返回widget中的bloc。(bloc最終是保存在外一層的state中)。
final type = _typeOf<BlocProvider<T>>();
BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
複製代碼
在下圖中,最下層的widget調用of方法後,能夠直接獲取到頂層頁面的submitBloc,由於flutter widget是樹狀結構的,結構層次保存在BuildContext中,能夠從子節點依次往父節點找符合類型的widget,因此全部使用了BlocProvider包裹的widget均可以經過of方法找到,再返回widget中的bloc。(bloc最終是保存在外一層的state中)。
StreamController實現了觀察者模式,監聽者不是直接被調用,而是處於觀察狀態,當event加入streamController後,監聽者得到異步回調。
var obs = Observable(Stream.fromIterable([1,2,3,4,5]))
.map((item)=>++item);
obs.listen(print);
輸出:2 3 4 5 6
複製代碼
Subject是對StreamController的擴展,經常使用的有如下幾種。
PublishSubject:StreamController廣播版,streamController只能有一個listener,PublishSubject能夠屢次listen。下面的其餘幾種Subject也都是廣播版。
BehaviorSubject: 緩存最近一次的事件。若是先發生event,後listen,也能收到緩存的event。
ReplaySubject: 緩存全部的事件,以前加入的全部event,listen後,都會順序發送過來。
發現這是因爲每次都建立新的bloc致使的,經過使用一個bloc list解決了這個問題。
static ZNImageBLocList _getInstance() {
if (_instance == null) {
_instance = new ZNImageBLocList._internal();
}
return _instance;
}
List<ZNImageBloc> blocList;
ZNImageBloc getBloc(String url,ZNImageConfig config){
for(ZNImageBloc bloc in blocList){
if(bloc.imageUrl==url){
return bloc;
}
}
ZNImageBloc bloc = new ZNImageBloc(url, config);
blocList.add(bloc);
return bloc;
}
複製代碼
class ZnWebImageState extends State<ZnWebImage> {
ZNImageBloc bloc;
@override
void dispose() {
// TODO: implement dispose
bloc.dispose();
super.dispose();
}
@override
void initState() {
// TODO: implement initState
super.initState();
bloc = ZNImageBLocList.instance.getBloc(widget.url, widget.config);
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return BlocProvider<ZNImageBloc>(
bloc: bloc,
child: ZNImageContainer(),
);
}
}
複製代碼
這樣多個zn_web_image的widget就能共用同一個bloc,在下載過程當中同步顯示進度條,下載完成後同時收到通知顯示下載好的圖片。
bloc在徵信項目中也解決了一個很頭疼的問題,就是否經過認證的狀態,這個狀態全局共用,且有多個變動入口,認證狀態變動後要求全部頁面更新顯示。
1.第一個tab中的首頁頂部。
2.確認訂單頁面,能夠從第一個tab首頁進入,也能夠從第二個tab的訂單列表進入。
3.第四個tab的我的中心頁面。
最開始沒有使用bloc的時候,寫了多個網絡請求獲取認證狀態,isAuth屬性也在每一個頁面分別保存。
1.首頁經過requestAuth從服務器端獲取isAuth屬性。
2.確認訂單頁面也須要從requestAuth獲取isAuth屬性。雖然首頁經過請求獲取到了isAuth屬性,從首頁進入確認訂單頁面能夠傳遞isAuth屬性。可是從訂單列表進入時沒有這個屬性,訂單列表是和首頁同時初始化的,仍是須要網絡請求。
3.我的中心頁面也須要從requestAuth獲取isAuth屬性。雖然首頁經過請求獲取到了isAuth屬性,可是我的中心頁面是和首頁同時在tabbarView中初始化的,不能經過構造方法傳遞isAuth屬性。
這樣很是的麻煩,並且工做量大不少。
使用bloc以後,isAuth這樣全局共用的屬性,只須要在MaterialApp外面加一層BlocProvider就好了,任何一個子頁面都能直接獲取isAuth,而且能同步isAuth的狀態更新,刷新UI顯示。
//我的認證bloc
CreditAuthBloc authBloc;
@override
void dispose() {
authBloc.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
authBloc = CreditAuthBloc();
}
@override
Widget build(BuildContext context) {
return BlocProvider<CreditAuthBloc>(bloc: authBloc,child: MaterialApp(
title: '',
initialRoute:'/',
home: Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: TabBarView(physics: NeverScrollableScrollPhysics(),controller: tabController, children: [
ZNMarketPage(),//首頁
ZNOrderList(),//訂單列表
ZNBill(),
ZNMyCenter(),//我的中心
]),
),
],
)),
),);
}
複製代碼
子頁面獲取bloc
//使用bloc中的auth
// int userAuthStatus = 0; //我的認證 0: 未認證, 1: 已認證
CreditAuthBloc authBloc;
@override
void didChangeDependencies() {
// TODO: implement didChangeDependencies
super.didChangeDependencies();
authBloc = BlocProvider.of<CreditAuthBloc>(context);
}
複製代碼
經過streamBuilder使用bloc