flutter_bloc使用將從下圖的三個(gè)維度說(shuō)明
前言
首先,有很多的文章在說(shuō)flutter bloc模式的應(yīng)用钥平,但是百分之八九十的文章都是在說(shuō)实撒,使用StreamController+StreamBuilder搭建bloc姊途,提升性能的會(huì)加上InheritedWidget,這些文章看了很多知态,真正寫(xiě)使用bloc作者開(kāi)發(fā)的flutter_bloc卻少之又少捷兰。沒(méi)辦法,只能去bloc的github上去找使用方式负敏,最后去bloc官網(wǎng)翻文檔贡茅。
蛋痛,各位叼毛其做,就不能好好說(shuō)說(shuō)flutter_bloc的使用嗎顶考?非要各種抄bloc模式提出作者的那倆篇文章。現(xiàn)在妖泄,搞的雜家這個(gè)伸手黨要自己去翻文檔總結(jié)(手動(dòng)滑稽)驹沿。
項(xiàng)目效果(建議PC瀏覽器打開(kāi))
問(wèn)題
初次使用flutter_bloc框架,可能會(huì)有幾個(gè)疑問(wèn)
- state里面定義了太多變量蹈胡,某個(gè)事件只需要更新其中一個(gè)變量渊季,其它的變量賦相同值麻煩
- 進(jìn)入某個(gè)模塊,進(jìn)行初始化操作:復(fù)雜的邏輯運(yùn)算罚渐,網(wǎng)絡(luò)請(qǐng)求等却汉,入口在哪定義
準(zhǔn)備工作
說(shuō)明
- 這里說(shuō)明下,文章里把
BlocBuilder
放在頂層荷并,因?yàn)楸旧眄?yè)面非常簡(jiǎn)單合砂,也是為了更好呈現(xiàn)頁(yè)面結(jié)構(gòu),所以才放在頂層源织,如果需要更加顆良饶拢化控件更新區(qū)域,請(qǐng)將BlocBuilder
包裹你需要更新的控件區(qū)域即可
引用
- 我覺(jué)得學(xué)習(xí)一個(gè)模式或者框架的時(shí)候雀鹃,最主要的是把主流程跑通,起碼可以符合標(biāo)準(zhǔn)的堆頁(yè)面励两,這樣的話黎茎,就可以把這玩意用起來(lái),再遇到想要的什么細(xì)節(jié)当悔,就可以自己去翻文檔傅瞻,畢竟大體上已經(jīng)懂了,寫(xiě)過(guò)了幾個(gè)頁(yè)面盲憎,也有些體會(huì)嗅骄,再去翻文檔就很快能理解了
- 實(shí)際上Bloc給的API也不多,就幾個(gè)API饼疙,相關(guān)API使用說(shuō)明都寫(xiě)在文章最后
庫(kù)
flutter_bloc: ^6.1.1 #狀態(tài)管理框架
equatable: ^1.2.3 #增強(qiáng)組件相等性判斷
- 看看flutter_bloc都推到6.0了溺森,別再用StreamController手搭Bloc了!
插件
在Android Studio設(shè)置的Plugins里,搜索:Bloc
安裝重啟下屏积,就OK了
- 右擊相應(yīng)的文件夾医窿,選擇“Bloc Class”,我在main文件夾新建的炊林,填入的名字:main姥卢,就自動(dòng)生成下面三個(gè)文件;:main_bloc渣聚,main_event独榴,main_state;main_view是我自己新建奕枝,用來(lái)寫(xiě)頁(yè)面的棺榔。
- 是不是覺(jué)得,還在手動(dòng)新建這些bloc文件low爆了倍权;就好像fish_redux掷豺,不用插件,讓我手動(dòng)去創(chuàng)建那六個(gè)文件薄声,寫(xiě)那些模板代碼当船,真的要原地爆炸。
Bloc范例
效果
- 好了默辨,嗶嗶了一堆德频,看下咱們要用flutter_bloc實(shí)現(xiàn)的效果。
- 直接開(kāi)Chrome演示缩幸,大家在虛擬機(jī)上跑也一樣壹置。
初始化代碼
來(lái)看下這三個(gè)生成的bloc文件:main_bloc,main_event表谊,main_state
- main_bloc:這里就是咱們主要寫(xiě)邏輯的頁(yè)面了
- mapEventToState方法只有一個(gè)參數(shù)钞护,后面自動(dòng)帶了一個(gè)逗號(hào),格式化代碼就分三行了爆办,建議刪掉逗號(hào)难咕,格式化代碼。
class MainBloc extends Bloc<MainEvent, MainState> {
MainBloc() : super(MainInitial());
@override
Stream<MainState> mapEventToState(
MainEvent event,
) async* {
// TODO: implement mapEventToState
}
}
- main_event:這里是執(zhí)行的各類事件距辆,有點(diǎn)類似fish_redux的action層
@immutable
abstract class MainEvent {}
- main_state:狀態(tài)數(shù)據(jù)放在這里保存余佃,中轉(zhuǎn)
@immutable
abstract class MainState {}
class MainInitial extends MainState {}
實(shí)現(xiàn)
- 主入口
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MainPage(),
);
}
}
- 說(shuō)明
- 這里對(duì)于簡(jiǎn)單的頁(yè)面,state的使用抽象狀態(tài)繼承實(shí)現(xiàn)的方式跨算,未免有點(diǎn)麻煩爆土,這里我進(jìn)行一點(diǎn)小改動(dòng),state的實(shí)現(xiàn)類別有很多诸蚕,官網(wǎng)寫(xiě)demo也有不用抽象類步势,直接class氧猬,類似實(shí)體類的方式開(kāi)搞的。
- 相關(guān)代碼的注釋寫(xiě)的比較多立润,大家可以著重看看
- main_bloc
- state變量是框架內(nèi)部定義的狂窑,會(huì)默認(rèn)保存上一次同步的MainSate對(duì)象的值
class MainBloc extends Bloc<MainEvent, MainState> {
MainBloc() : super(MainState(selectedIndex: 0, isExtended: false));
@override
Stream<MainState> mapEventToState(MainEvent event) async* {
///main_view中添加的事件,會(huì)在此處回調(diào)桑腮,此處處理完數(shù)據(jù)泉哈,將數(shù)據(jù)yield,BlocBuilder就會(huì)刷新組件
if (event is SwitchTabEvent) {
///獲取到event事件傳遞過(guò)來(lái)的值,咱們拿到這值塞進(jìn)MainState中
///直接在state上改變內(nèi)部的值,然后yield,只能觸發(fā)一次BlocBuilder,它內(nèi)部會(huì)比較上次MainState對(duì)象,如果相同,就不build
yield MainState()
..selectedIndex = event.selectedIndex
..isExtended = state.isExtended;
} else if (event is IsExtendEvent) {
yield MainState()
..selectedIndex = state.selectedIndex
..isExtended = !state.isExtended;
}
}
}
- main_event:在這里就能看見(jiàn)破讨,view觸發(fā)了那些事件了丛晦;維護(hù)起來(lái)也很爽,看看這里提陶,也很快能懂頁(yè)面在干嘛了
@immutable
abstract class MainEvent extends Equatable{
const MainEvent();
}
///切換NavigationRail的tab
class SwitchTabEvent extends MainEvent{
final int selectedIndex;
const SwitchTabEvent({@required this.selectedIndex});
@override
List<Object> get props => [selectedIndex];
}
///展開(kāi)NavigationRail,這個(gè)邏輯比較簡(jiǎn)單,就不用傳參數(shù)了
class IsExtendEvent extends MainEvent{
const IsExtendEvent();
@override
List<Object> get props => [];
}
-
main_state:state有很多種寫(xiě)法烫沙,在bloc官方文檔上,不同項(xiàng)目state的寫(xiě)法也很多
這邊變量名可以設(shè)置為私用隙笆,用get和set可選擇性的設(shè)置讀寫(xiě)權(quán)限锌蓄,因?yàn)槲疫@邊設(shè)置的倆個(gè)變量全是必用的,讀寫(xiě)均要撑柔,就設(shè)置公有類型瘸爽,不用下劃線“_”去標(biāo)記私有了。
對(duì)于生成的模板代碼铅忿,我們?cè)谶@:去掉@immutable注解剪决,去掉abstract;
這里說(shuō)下加上@immutable和abstract的作用檀训,這邊是為了標(biāo)定不同狀態(tài)柑潦,這種寫(xiě)法,會(huì)使得代碼變得更加麻煩峻凫,用state不同狀態(tài)去標(biāo)定業(yè)務(wù)事件渗鬼,代價(jià)太大,這邊用一個(gè)變量去標(biāo)定荧琼,很容易輕松代替
class MainState{
int selectedIndex;
bool isExtended;
MainState({this.selectedIndex, this.isExtended});
}
- main_view
- 這邊就是咱們的界面層了乍钻,很簡(jiǎn)單,將需要刷新的組件铭腕,用BlocBuilder包裹起來(lái),使用BlocBuilder:提供的state去賦值就ok了多糠,context去添加執(zhí)行的事件累舷,context用StatelessWidget中提供的或者BlocBuilder提供的都行
class MainPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return _buildBg(children: [
//側(cè)邊欄
_buildLeftNavigation(),
//右邊主體內(nèi)容
Expanded(child: Center(
child: BlocBuilder<MainBloc, MainState>(builder: (context, state) {
return Text(
"選擇Index:" + state.selectedIndex.toString(),
style: TextStyle(fontSize: 30.0),
);
}),
))
]);
}
Widget _buildBg({List<Widget> children}) {
///創(chuàng)建BlocProvider的,表明該P(yáng)age夹孔,我們是用MainBloc被盈,MainBloc是屬于該頁(yè)面的Bloc了
return BlocProvider(
create: (BuildContext context) => MainBloc(),
child: Scaffold(
appBar: AppBar(title: Text('Bloc')),
body: Row(children: children),
),
);
}
//增加NavigationRail組件為側(cè)邊欄
Widget _buildLeftNavigation() {
return BlocBuilder<MainBloc, MainState>(builder: (context, state) {
return NavigationRail(
backgroundColor: Colors.white,
elevation: 3,
extended: state.isExtended,
labelType: state.isExtended
? NavigationRailLabelType.none
: NavigationRailLabelType.selected,
//側(cè)邊欄中的item
destinations: [
NavigationRailDestination(
icon: Icon(Icons.add_to_queue),
selectedIcon: Icon(Icons.add_to_photos),
label: Text("測(cè)試一"),
),
NavigationRailDestination(
icon: Icon(Icons.add_circle_outline),
selectedIcon: Icon(Icons.add_circle),
label: Text("測(cè)試二"),
),
NavigationRailDestination(
icon: Icon(Icons.bubble_chart),
selectedIcon: Icon(Icons.broken_image),
label: Text("測(cè)試三"),
),
],
//頂部widget
leading: _buildNavigationTop(),
//底部widget
trailing: _buildNavigationBottom(),
selectedIndex: state.selectedIndex,
onDestinationSelected: (int index) {
///添加切換tab事件
BlocProvider.of<MainBloc>(context)
.add(SwitchTabEvent(selectedIndex: index));
},
);
});
}
Widget _buildNavigationTop() {
return Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage(
"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3383029432,2292503864&fm=26&gp=0.jpg",
),
fit: BoxFit.fill,
),
),
),
),
);
}
Widget _buildNavigationBottom() {
return Container(
child: BlocBuilder<MainBloc, MainState>(
builder: (context, state) {
return FloatingActionButton(
onPressed: () {
///添加NavigationRail展開(kāi),收縮事件
BlocProvider.of<MainBloc>(context).add(IsExtendEvent());
},
child: Icon(state.isExtended ? Icons.send : Icons.navigation),
);
},
),
);
}
}
Bloc范例優(yōu)化
反思
從上面的代碼來(lái)看析孽,實(shí)際存在幾個(gè)隱式問(wèn)題,這些問(wèn)題只怎,剛開(kāi)始使用時(shí)候袜瞬,沒(méi)異常的感覺(jué),但是使用bloc久了后身堡,感覺(jué)肯定越來(lái)越強(qiáng)烈
- state問(wèn)題
- 初始化問(wèn)題:這邊初始化是在bloc里邓尤,直接在構(gòu)造方法里面賦初值的,state中一旦變量多了贴谎,還是這么寫(xiě)汞扎,會(huì)感覺(jué)極其難受,不好管理擅这。需要優(yōu)化
- 可以看見(jiàn)這邊我們只改動(dòng)selectedIndex或者isExtended澈魄;另一個(gè)變量不需要變動(dòng),需要保持上一次的數(shù)據(jù)仲翎,進(jìn)行了此類:state.selectedIndex或者state.isExtended賦值痹扇,一旦變量達(dá)到十幾個(gè)乃至幾十個(gè),還是如此寫(xiě)溯香,是讓人極其崩潰的鲫构。需要優(yōu)化
- bloc問(wèn)題
- 如果進(jìn)行一個(gè)頁(yè)面,需要進(jìn)行復(fù)雜的運(yùn)算或者請(qǐng)求接口后逐哈,才能知曉數(shù)據(jù)芬迄,進(jìn)行賦值,這里肯定需要一個(gè)初始化入口昂秃,初始化入口需要怎樣去定義呢禀梳?
插件
因?yàn)楣俜讲寮傻膶?xiě)法,和調(diào)整后寫(xiě)法差距有點(diǎn)大肠骆,而且官方插件不支持生成view層和相關(guān)設(shè)置算途,此處我就擼了一個(gè)插件,完善了相關(guān)功能
請(qǐng)注意蚀腿,wrap代碼和提示代碼片段嘴瓤,參靠了官方插件規(guī)則
Wrap Widget 規(guī)則來(lái)著:intellij_generator_plugin
快捷代碼生成規(guī)則來(lái)著: intellij_generator_plugin
- 在Android Studio里面搜索 flutter bloc
- 生成模板代碼
- 支持修改后綴
- Wrap Widget (alt + enter):RepositoryProvider,BlocConsumer莉钙,BlocBuilder廓脆,BlocProvider,BlocListener
- 輸入 bloc 可生成快捷代碼片段
優(yōu)化實(shí)現(xiàn)
這邊完整走一下流程磁玉,讓大家能有個(gè)完整的思路
- state:首先來(lái)看看我們對(duì)state中的優(yōu)化停忿,這邊進(jìn)行了倆個(gè)很重要優(yōu)化,增加倆個(gè)方法:init()和clone()
- init():這里初始化統(tǒng)一用init()方法去管理
- clone():這邊克隆方法蚊伞,是非常重要的席赂,一旦變量達(dá)到倆位數(shù)以上吮铭,就能深刻體會(huì)該方法是多么的重要
class MainState {
int selectedIndex;
bool isExtended;
///初始化方法,基礎(chǔ)變量也需要賦初值,不然會(huì)報(bào)空異常
MainState init() {
return MainState()
..selectedIndex = 0
..isExtended = false;
}
///clone方法,此方法實(shí)現(xiàn)參考fish_redux的clone方法
///也是對(duì)官方Flutter Login Tutorial這個(gè)demo中copyWith方法的一個(gè)優(yōu)化
///Flutter Login Tutorial(https://bloclibrary.dev/#/flutterlogintutorial)
MainState clone() {
return MainState()
..selectedIndex = selectedIndex
..isExtended = isExtended;
}
}
- event
- 這邊定義一個(gè)MainInit()初始化方法,同時(shí)去掉Equatable繼承颅停,在我目前的使用中谓晌,感覺(jué)它用處不大。癞揉。纸肉。
@immutable
abstract class MainEvent {}
///初始化事件,這邊目前不需要傳什么值
class MainInitEvent extends MainEvent {}
///切換NavigationRail的tab
class SwitchTabEvent extends MainEvent {
final int selectedIndex;
SwitchTabEvent({@required this.selectedIndex});
}
///展開(kāi)NavigationRail,這個(gè)邏輯比較簡(jiǎn)單,就不用傳參數(shù)了
class IsExtendEvent extends MainEvent {}
- bloc
- 這增加了初始化方法,請(qǐng)注意烧董,如果需要進(jìn)行異步請(qǐng)求毁靶,同時(shí)需要將相關(guān)邏輯提煉一個(gè)方法,咱們?cè)谶@里配套Future和await就能解決在異步場(chǎng)景下同步數(shù)據(jù)問(wèn)題
- 這里使用了克隆方法逊移,可以發(fā)現(xiàn)预吆,我們只要關(guān)注自己需要改變的變量就行了,其它的變量都在內(nèi)部賦值好了胳泉,我們不需要去關(guān)注拐叉;這就大大的便捷了頁(yè)面中有很多變量,只需要變動(dòng)一倆個(gè)變量的場(chǎng)景
- 注意:如果變量的數(shù)據(jù)未改變扇商,界面相關(guān)的widget是不會(huì)重繪的凤瘦;只會(huì)重繪變量被改變的widget
class MainBloc extends Bloc<MainEvent, MainState> {
MainBloc() : super(MainState().init());
@override
Stream<MainState> mapEventToState(MainEvent event) async* {
///main_view中添加的事件,會(huì)在此處回調(diào)案铺,此處處理完數(shù)據(jù)蔬芥,將數(shù)據(jù)yield,BlocBuilder就會(huì)刷新組件
if (event is MainInitEvent) {
yield await init();
} else if (event is SwitchTabEvent) {
///獲取到event事件傳遞過(guò)來(lái)的值,咱們拿到這值塞進(jìn)MainState中
///直接在state上改變內(nèi)部的值,然后yield,只能觸發(fā)一次BlocBuilder,它內(nèi)部會(huì)比較上次MainState對(duì)象,如果相同,就不build
yield switchTap(event);
} else if (event is IsExtendEvent) {
yield isExtend();
}
}
///初始化操作,在網(wǎng)絡(luò)請(qǐng)求的情況下,需要使用如此方法同步數(shù)據(jù)
Future<MainState> init() async {
return state.clone();
}
///切換tab
MainState switchTap(SwitchTabEvent event) {
return state.clone()..selectedIndex = event.selectedIndex;
}
///是否展開(kāi)
MainState isExtend() {
return state.clone()..isExtended = !state.isExtended;
}
}
- view
- view層代碼太多控汉,這邊只增加了個(gè)初始化事件笔诵,就不重新把全部代碼貼出來(lái)了,初始化操作直接在創(chuàng)建的時(shí)候姑子,在XxxBloc上使用add()方法就行了乎婿,就能起到進(jìn)入頁(yè)面,初始化一次的效果街佑;add()方法也是Bloc類中提供的谢翎,遍歷事件的時(shí)候,就特地檢查了add()這個(gè)方法是否添加了事件沐旨;說(shuō)明森逮,這是框架特地提供了一個(gè)初始化的方法
- 這個(gè)初始化方式是在官方示例找到的
- 項(xiàng)目名:Flutter Infinite List Tutorial
- 項(xiàng)目地址:flutter-infinite-list-tutorial
class MainPage extends StatelessWidget {
...
Widget _buildBg({List<Widget> children}) {
///創(chuàng)建BlocProvider的,表明該P(yáng)age磁携,我們是用MainBloc吊宋,MainBloc是屬于該頁(yè)面的Bloc了
return BlocProvider(
create: (BuildContext context) => MainBloc()..add(MainInitEvent()),
child: Scaffold(
appBar: AppBar(title: Text('Bloc')),
body: Row(children: children),
),
);
}
///下方其余代碼省略...........
}
搞定
- OK,經(jīng)過(guò)這樣的優(yōu)化,解決了幾個(gè)痛點(diǎn)璃搜。實(shí)際在view中反復(fù)是要用BlocBuilder去更新view,寫(xiě)起來(lái)有點(diǎn)麻煩鳞上,這里我們可以寫(xiě)一個(gè)这吻,將其中state和context變量,往提出來(lái)的Widget方法傳值篙议,也是蠻不錯(cuò)的
- 大家保持觀察者模式的思想就行了唾糯;觀察者(回調(diào)刷新控件)和被觀察者(產(chǎn)生相應(yīng)事件,添加事件鬼贱,去通知觀察者)移怯,bloc層是處于觀察者和被觀察者中間的一層,我們可以在bloc里面搞業(yè)務(wù)这难,搞邏輯舟误,搞網(wǎng)絡(luò)請(qǐng)求,不能搞基姻乓;拿到Event事件傳遞過(guò)來(lái)的數(shù)據(jù)嵌溢,把處理好的、符合要求的數(shù)據(jù)返回給view層的觀察者就行了蹋岩。
- 使用框架赖草,不拘泥框架,在觀察者模式的思想上剪个,靈活的去使用flutter_bloc提供Api秧骑,這樣可以大大的縮短我們的開(kāi)發(fā)時(shí)間!
Bloc 8.0+新寫(xiě)法
破壞式改變
bloc8.0+的版本扣囊,對(duì)比之前的寫(xiě)法簡(jiǎn)直是破壞式的改變乎折,你如果升級(jí)到bloc 8.0及其以上的版本,之前寫(xiě)的bloc模式寫(xiě)法已經(jīng)完全不兼容了如暖,mapEventToState
方法直接被移除了笆檀,一運(yùn)行項(xiàng)目,bloc內(nèi)部也會(huì)給出報(bào)錯(cuò)盒至,需要你手動(dòng)去注冊(cè)處理器
有一說(shuō)一酗洒,雖然是破壞式的改變寫(xiě)法,但是新寫(xiě)法是非常的優(yōu)雅枷遂,徹底改變了以前的mapEventToState
方法中的各種判斷Event
這種徹底不兼容的做法樱衷,確實(shí)非常的激進(jìn),但是為了優(yōu)化的點(diǎn)酒唉,亦有可圈可點(diǎn)之處
新寫(xiě)法
- 插件已經(jīng)支持了bloc8.0+的寫(xiě)法
來(lái)看下版本生成代碼
- view
class TestPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => TestBloc()..add(InitEvent()),
child: Builder(builder: (context) => _buildPage(context)),
);
}
Widget _buildPage(BuildContext context) {
final bloc = BlocProvider.of<TestBloc>(context);
return Container();
}
}
- bloc
class TestBloc extends Bloc<TestEvent, TestState> {
TestBloc() : super(TestState().init()) {
on<InitEvent>(_init);
}
void _init(InitEvent event, Emitter<TestState> emit) async {
emit(state.clone());
}
}
- state
class TestState {
TestState init() {
return TestState();
}
TestState clone() {
return TestState();
}
}
- event
abstract class TestEvent {}
class InitEvent extends TestEvent {}
可以發(fā)現(xiàn)Bloc層矩桂,完全不用寫(xiě)判斷了
計(jì)數(shù)器實(shí)例
寫(xiě)個(gè)計(jì)數(shù)器demo,大家來(lái)感受下
- view
class BlBlocCounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => BlBlocCounterBloc()..add(InitEvent()),
child: Builder(builder: (context) => _buildPage(context)),
);
}
Widget _buildPage(BuildContext context) {
final bloc = BlocProvider.of<BlBlocCounterBloc>(context);
return Scaffold(
appBar: AppBar(title: Text('Bloc-Bloc范例')),
body: Center(
child: BlocBuilder<BlBlocCounterBloc, BlBlocCounterState>(
builder: (context, state) {
return Text(
'點(diǎn)擊了 ${bloc.state.count} 次',
style: TextStyle(fontSize: 30.0),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => bloc.add(CounterIncrementEvent()),
child: Icon(Icons.add),
),
);
}
}
- bloc:所有事件入口痪伦,在頂部一目了然
class BlBlocCounterBloc extends Bloc<BlBlocCounterEvent, BlBlocCounterState> {
BlBlocCounterBloc() : super(BlBlocCounterState().init()) {
//頁(yè)面初始化時(shí)刻
on<InitEvent>(_init);
//計(jì)數(shù)器自增
on<CounterIncrementEvent>(_increment);
}
void _init(InitEvent event, Emitter<BlBlocCounterState> emit) async {
//處理一些初始化操作,然后刷新界面
emit(state.clone());
}
///自增
void _increment(
CounterIncrementEvent event,
Emitter<BlBlocCounterState> emit,
) {
state.count++;
emit(state.clone());
}
}
- state
class BlBlocCounterState {
late int count;
BlBlocCounterState init() {
return BlBlocCounterState()..count = 0;
}
BlBlocCounterState clone() {
return BlBlocCounterState()..count = count;
}
}
- event
abstract class BlBlocCounterEvent {}
class InitEvent extends BlBlocCounterEvent {}
class CounterIncrementEvent extends BlBlocCounterEvent {}
總結(jié)
新寫(xiě)法侄榴,對(duì)Bloc層改動(dòng)是巨大的
可以發(fā)現(xiàn)雹锣,主要改變的就是對(duì)事件的處理;改動(dòng)后寫(xiě)法對(duì)比以前的寫(xiě)法癞蚕,優(yōu)雅了N倍
- 所有事件入口全部歸納在一起
- 可以輕松的從歸納事件入口蕊爵,跳轉(zhuǎn)到相應(yīng)的業(yè)務(wù)邏輯
- 對(duì)事件的處理,不用寫(xiě)一堆判斷了h肷健(這是破壞式改變攒射,優(yōu)化的點(diǎn))
bloc層的新寫(xiě)法確實(shí)不錯(cuò),新項(xiàng)目如果用bloc恒水,可以無(wú)腦升級(jí)bloc 8.0会放,使用這種新寫(xiě)法;老項(xiàng)目頁(yè)面多的話钉凌,改動(dòng)起來(lái)咧最,成本確實(shí)非常的大,大家自己抉擇嘍
Cubit范例
- Cubit是Bloc模式的一種簡(jiǎn)化版甩骏,去掉了event這一層窗市,對(duì)于簡(jiǎn)單的頁(yè)面,用Cubit來(lái)實(shí)現(xiàn)饮笛,開(kāi)發(fā)體驗(yàn)是大大的好啊咨察,下面介紹下該種模式的寫(xiě)法
創(chuàng)建
- 首先創(chuàng)建Cubit一組文件,選擇“Cubit”福青,新建名稱填寫(xiě):Counter
新建好后摄狱,他會(huì)生成三個(gè)文件:cubit,state无午,view媒役;來(lái)看下生成的代碼
模板代碼
- counter_cubit
class CounterCubit extends Cubit<CounterState> {
CounterCubit() : super(CounterState().init());
}
- state
class CounterState {
CounterState init() {
return CounterState();
}
CounterState clone() {
return CounterState();
}
}
- view
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => CounterCubit(),
child: Builder(builder: (context) => _buildPage(context)),
);
}
Widget _buildPage(BuildContext context) {
final cubit = BlocProvider.of<CounterCubit>(context);
return Container();
}
}
實(shí)現(xiàn)計(jì)時(shí)器
- 來(lái)實(shí)現(xiàn)下一個(gè)灰常簡(jiǎn)單的計(jì)數(shù)器
效果
- 來(lái)看下實(shí)現(xiàn)效果吧,這邊不上圖了宪迟,大家點(diǎn)擊下面的鏈接酣衷,可以直接體驗(yàn)Cubit模式寫(xiě)的計(jì)時(shí)器
- 實(shí)現(xiàn)效果:點(diǎn)我體驗(yàn)實(shí)際效果
實(shí)現(xiàn)
實(shí)現(xiàn)很簡(jiǎn)單,三個(gè)文件就搞定次泽,看下流程:state -> cubit -> view
- state:這個(gè)很簡(jiǎn)單穿仪,加個(gè)計(jì)時(shí)變量
class BlCubitCounterState {
late int count;
BlCubitCounterState init() {
return BlCubitCounterState()..count = 0;
}
BlCubitCounterState clone() {
return BlCubitCounterState()..count = count;
}
}
- cubit
- 這邊加了個(gè)自增方法:increase()
- event層實(shí)際是所有行為的一種整合,方便對(duì)邏輯過(guò)于復(fù)雜的頁(yè)面意荤,所有行為的一種維護(hù)啊片;但是過(guò)于簡(jiǎn)單的頁(yè)面,就那么幾個(gè)事件玖像,還單獨(dú)維護(hù)紫谷,就沒(méi)什么必要了
- 在cubit層寫(xiě)的公共方法,在view里面能直接調(diào)用,更新數(shù)據(jù)使用:emit()
- cubit層應(yīng)該可以算是:bloc層和event層一種結(jié)合后的簡(jiǎn)寫(xiě)
class BlCubitCounterCubit extends Cubit<BlCubitCounterState> {
BlCubitCounterCubit() : super(BlCubitCounterState().init());
///自增
void increment() => emit(state.clone()..count = ++state.count);
}
- view
- view層的代碼就非常簡(jiǎn)單了笤昨,點(diǎn)擊方法里面調(diào)用cubit層的自增方法就ok了
class BlCubitCounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => BlCubitCounterCubit(),
child: Builder(builder: (context) => _buildPage(context)),
);
}
Widget _buildPage(BuildContext context) {
final cubit = BlocProvider.of<BlCubitCounterCubit>(context);
return Scaffold(
appBar: AppBar(title: Text('Bloc-Cubit范例')),
body: Center(
child: BlocBuilder<BlCubitCounterCubit, BlCubitCounterState>(
builder: (context, state) {
return Text(
'點(diǎn)擊了 ${cubit.state.count} 次',
style: TextStyle(fontSize: 30.0),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => cubit.increment(),
child: Icon(Icons.add),
),
);
}
}
總結(jié)
在Bloc模式里面祖驱,如果頁(yè)面不是過(guò)于復(fù)雜,使用Cubit去寫(xiě)瞒窒,基本完全夠用了羹膳;但是如果業(yè)務(wù)過(guò)于復(fù)雜,還是需要用Bloc去寫(xiě)根竿,需要將所有的事件行為管理起來(lái),便于后期維護(hù)
OK就珠,Bloc的簡(jiǎn)化模塊寇壳,Cubit模式就這樣講完了,對(duì)于自己業(yè)務(wù)寫(xiě)的小項(xiàng)目妻怎,我就經(jīng)常用這個(gè)Cubit去寫(xiě)
全局Bloc
說(shuō)明
什么是全局Bloc壳炎?
- BlocProvider介紹里面有這樣的形容:BlocProvider should be used to create new blocs which will be made available to the rest of the subtree(BlocProvider應(yīng)該被用于創(chuàng)建新的Bloc,這些Bloc將可用于其子樹(shù))
- 這樣的話逼侦,我們只需要在主入口地方使用BlocProvider創(chuàng)建Bloc匿辩,就能使用全局的XxxBloc了,這里的全局XxxBloc榛丢,state狀態(tài)都會(huì)被保存的铲球,除非關(guān)閉app,否則state里面的數(shù)據(jù)都不會(huì)被還原晰赞!
- 注意:在主入口創(chuàng)建的XxxBloc稼病,在主入口處創(chuàng)建了一次,在其它頁(yè)面均不需要再次創(chuàng)建掖鱼,在任何頁(yè)面只需要使用BlocBuilder然走,便可以定點(diǎn)刷新及其獲取全局XxxBloc的state數(shù)據(jù)
使用場(chǎng)景
- 全局的主題色,字體樣式和大小等等全局配置更改戏挡;這種情況芍瑞,在需要全局屬性的地方,使用BlocBuilder對(duì)應(yīng)的全局XxxBloc泛型去刷新數(shù)據(jù)就行了
- 跨頁(yè)面去調(diào)用事件褐墅,既然是全局的XxxBloc拆檬,這就說(shuō)明,我們可以在任何頁(yè)面掌栅,使用
BlocProvider.of<XxxBloc>(context)
調(diào)用全局XxxBloc中事件秩仆,這就起到了一種跨頁(yè)面調(diào)用事件的效果- 使用全局Bloc做跨頁(yè)面事件時(shí),應(yīng)該明白猾封,當(dāng)你關(guān)閉Bloc對(duì)應(yīng)的頁(yè)面澄耍,對(duì)應(yīng)全局Bloc中的并不會(huì)被回收,下次進(jìn)入頁(yè)面,頁(yè)面的數(shù)據(jù)還是上次退出頁(yè)面修改的數(shù)據(jù)齐莲,這里應(yīng)該使用StatefulWidget痢站,在initState生命周期處,初始化數(shù)據(jù)选酗;或者在dispose生命周期處阵难,還原數(shù)據(jù)源
- 思考下:全局Bloc對(duì)象存在周期是在整個(gè)App存活周期,必然不能創(chuàng)建過(guò)多的全局Bloc芒填,跨頁(yè)面?zhèn)鬟f事件使用全局Bloc應(yīng)當(dāng)只能做折中方案
效果圖
使用
來(lái)看下怎么創(chuàng)建和使用全局Bloc吧呜叫!
- 主入口配置
- 全局的Bloc創(chuàng)建還是蠻簡(jiǎn)單的,這邊把
MultiBlocProvider
在Builder里面套在child上面就行了殿衰;當(dāng)然了朱庆,把MultiBlocProvider
套在MaterialApp上也是可以的 - 這樣我們就獲得一個(gè)全局的
SpanOneCubit
- 全局的Bloc創(chuàng)建還是蠻簡(jiǎn)單的,這邊把
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MainPage(),
builder: (BuildContext context, Widget child) {
return MultiBlocProvider(
providers: [
///此處通過(guò)BlocProvider創(chuàng)建的Bloc或者Cubit是全局的
BlocProvider<SpanOneCubit>(
create: (BuildContext context) => SpanOneCubit(),
),
],
child: child,
);
},
);
}
}
需要用倆個(gè)Bloc模塊來(lái)演示,這里分別用SpanOneCubit
和SpanTwoCubit
來(lái)演示闷祥,其中SpanOneCubit
是全局的
SpanOneCubit
- state
- 先來(lái)看看State模塊的代碼娱颊,這里很簡(jiǎn)單,只定義了count變量
class SpanOneState {
int count;
///初始化方法
SpanOneState init() {
return SpanOneState()..count = 0;
}
///克隆方法,針對(duì)于刷新界面數(shù)據(jù)
SpanOneState clone() {
return SpanOneState()..count = count;
}
}
- view
- 這個(gè)頁(yè)面僅僅是展示計(jì)數(shù)變量的變化凯砍,因?yàn)樵谥魅肟谑褂昧?code>BlocProvider創(chuàng)建了
SpanOneCubit
箱硕,所以在這個(gè)頁(yè)面不需要再次創(chuàng)建层玲,直接使用BlocBuilder
便可以獲取其state - 可以發(fā)現(xiàn)勾栗,這個(gè)頁(yè)面使用了StatefulWidget,在initState周期中一膨,初始化了數(shù)據(jù)源局待;這樣斑响,每次進(jìn)入頁(yè)面,數(shù)據(jù)源就不會(huì)保存為上一次改動(dòng)的來(lái)钳榨,都會(huì)被初始化為我們想要的值舰罚;這個(gè)頁(yè)面能接受到任何頁(yè)面調(diào)用其事件,這樣就實(shí)現(xiàn)類似于廣播的一種效果(
偽
)
- 這個(gè)頁(yè)面僅僅是展示計(jì)數(shù)變量的變化凯砍,因?yàn)樵谥魅肟谑褂昧?code>BlocProvider創(chuàng)建了
class CubitSpanOnePage extends StatefulWidget {
@override
_SpanOnePageState createState() => _SpanOnePageState();
}
class _SpanOnePageState extends State<CubitSpanOnePage> {
@override
void initState() {
BlocProvider.of<BlocSpanOneCubit>(context).init();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(title: Text('跨頁(yè)面-One')),
floatingActionButton: FloatingActionButton(
onPressed: () => BlocProvider.of<BlocSpanOneCubit>(context).toSpanTwo(context),
child: const Icon(Icons.arrow_forward_outlined),
),
body: Center(
child: BlocBuilder<BlocSpanOneCubit, BlocSpanOneState>(
builder: (context, state) {
return Text(
'SpanTwoPage點(diǎn)擊了 ${state.count} 次',
style: TextStyle(fontSize: 30.0),
);
},
),
),
);
}
}
- cubit
- cubit里面有三個(gè)事件薛耻,初始化营罢,跳轉(zhuǎn)頁(yè)面,計(jì)數(shù)自增
class SpanOneCubit extends Cubit<SpanOneState> {
SpanOneCubit() : super(SpanOneState().init());
void init() {
emit(state.init());
}
///跳轉(zhuǎn)到跨頁(yè)面
void toSpanTwo(BuildContext context) {
Navigator.push(context, MaterialPageRoute(builder: (context) => SpanTwoPage()));
}
///自增
void increase() {
state..count = ++state.count;
emit(state.clone());
}
}
SpanTwoCubit
- state
- 使用count饼齿,記錄下我們點(diǎn)擊自增的次數(shù)
class SpanTwoState {
int count;
///初始化方法
SpanTwoState init() {
return SpanTwoState()..count = 0;
}
///克隆方法,針對(duì)于刷新界面數(shù)據(jù)
SpanTwoState clone() {
return SpanTwoState()..count = count;
}
}
- view
- 這地方我們需要?jiǎng)?chuàng)建使用BlocProvider一個(gè)SpanTwoCubit饲漾,這是使用Bloc的常規(guī)流程
- 在自增的點(diǎn)擊事件里,我們調(diào)用本模塊和
SpanOneCubit
中的自增方法缕溉,OK考传,這里我們就能同步的改變SpanOneCubit
模塊的數(shù)據(jù)了!
class CubitSpanTwoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => BlocSpanTwoCubit()..init(context),
child: Builder(builder: (context) => _buildPage(context)),
);
}
Widget _buildPage(BuildContext context) {
final cubit = BlocProvider.of<BlocSpanTwoCubit>(context);
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(title: Text('跨頁(yè)面-Two')),
floatingActionButton: FloatingActionButton(
onPressed: () {
//改變SpanOneCubit模塊數(shù)據(jù)
BlocProvider.of<BlocSpanOneCubit>(context).increase();
//改變當(dāng)前頁(yè)面數(shù)據(jù)
cubit.increase();
},
child: const Icon(Icons.add),
),
body: Center(
child: BlocBuilder<BlocSpanTwoCubit, BlocSpanTwoState>(
builder: (context, state) {
return Text('當(dāng)前點(diǎn)擊了 ${state.count} 次',
style: TextStyle(fontSize: 30.0));
},
),
),
);
}
}
- cubit
- 平平無(wú)奇的業(yè)務(wù)代碼
class SpanTwoCubit extends Cubit<SpanTwoState> {
SpanTwoCubit() : super(SpanTwoState().init());
void init(BuildContext context){
emit(state.init());
}
///自增
void increase() => emit(state.clone()..count = ++state.count);
}
總結(jié)
OK证鸥,這樣便用全局Bloc實(shí)現(xiàn)了類似廣播的一種效果
- 使用全局去刷新:主題僚楞,字體樣式和大小之類勤晚,每個(gè)頁(yè)面都要使用BlocBuilder對(duì)應(yīng)的全局bloc去刷新對(duì)應(yīng)的全局view模塊
Bloc API說(shuō)明
BlocBuilder
BlocBuilder是Flutter窗口小部件,需要Bloc
和builder
函數(shù)泉褐。BlocBuilder
處理構(gòu)建小部件以響應(yīng)新?tīng)顟B(tài)赐写。BlocBuilder
與非常相似,StreamBuilder
但具有更簡(jiǎn)單的API膜赃,可以減少所需的樣板代碼量挺邀。該builder
函數(shù)可能會(huì)被多次調(diào)用,并且應(yīng)該是一個(gè)純函數(shù)跳座,它會(huì)根據(jù)狀態(tài)返回小部件端铛。
看看BlocListener
是否要響應(yīng)狀態(tài)更改“執(zhí)行”任何操作,例如導(dǎo)航疲眷,顯示對(duì)話框等沦补。
如果省略cubit參數(shù),BlocBuilder
將使用BlocProvider
和當(dāng)前函數(shù)自動(dòng)執(zhí)行查找BuildContext
咪橙。
BlocBuilder<BlocA, BlocAState>(
builder: (context, state) {
// return widget here based on BlocA's state
}
)
僅當(dāng)您希望提供一個(gè)范圍僅限于單個(gè)窗口小部件且無(wú)法通過(guò)父級(jí)BlocProvider
和當(dāng)前類訪問(wèn)的bloc時(shí),才指定該bloc BuildContext
虚倒。
BlocBuilder<BlocA, BlocAState>(
cubit: blocA, // provide the local cubit instance
builder: (context, state) {
// return widget here based on BlocA's state
}
)
為了對(duì)何時(shí)builder
調(diào)用該函數(shù)進(jìn)行細(xì)粒度的控制美侦,buildWhen
可以提供一個(gè)可選的選項(xiàng)。buildWhen
獲取先前的塊狀態(tài)和當(dāng)前的塊狀態(tài)并返回一個(gè)布爾值魂奥。如果buildWhen
返回true菠剩,builder
將使用進(jìn)行調(diào)用,state
并且小部件將重新生成耻煤。如果buildWhen
返回false具壮,builder
則不會(huì)調(diào)用state
且不會(huì)進(jìn)行重建。
BlocBuilder<BlocA, BlocAState>(
buildWhen: (previousState, state) {
// return true/false to determine whether or not
// to rebuild the widget with state
},
builder: (context, state) {
// return widget here based on BlocA's state
}
)
BlocProvider
BlocProvider是Flutter小部件哈蝇,可通過(guò)為其子元素提供塊BlocProvider.of<T>(context)
棺妓。它用作依賴項(xiàng)注入(DI)小部件,以便可以將一個(gè)塊的單個(gè)實(shí)例提供給子樹(shù)中的多個(gè)小部件炮赦。
在大多數(shù)情況下怜跑,BlocProvider
應(yīng)使用它來(lái)創(chuàng)建新的bloc,這些bloc將可用于其余子樹(shù)吠勘。在這種情況下性芬,由于BlocProvider
負(fù)責(zé)創(chuàng)建塊,它將自動(dòng)處理關(guān)閉bloc剧防。
BlocProvider(
create: (BuildContext context) => BlocA(),
child: ChildA(),
);
默認(rèn)情況下植锉,BlocProvider
將懶惰地創(chuàng)建bloc,這意味著create
當(dāng)通過(guò)查找塊時(shí)將執(zhí)行該bloc BlocProvider.of<BlocA>(context)
峭拘。
要覆蓋此行為并強(qiáng)制create
立即運(yùn)行俊庇,lazy
可以將其設(shè)置為false
狮暑。
BlocProvider(
lazy: false,
create: (BuildContext context) => BlocA(),
child: ChildA(),
);
在某些情況下,BlocProvider
可用于向小部件樹(shù)的新部分提供現(xiàn)有的bloc暇赤。當(dāng)需要將現(xiàn)有bloc用于新路線時(shí)心例,這將是最常用的。在這種情況下鞋囊,BlocProvider
由于不會(huì)創(chuàng)建bloc止后,因此不會(huì)自動(dòng)關(guān)閉該bloc。
BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: ScreenA(),
);
然后從ChildA
或ScreenA
中檢索BlocA
:
// with extensions
context.read<BlocA>();
// without extensions
BlocProvider.of<BlocA>(context)復(fù)制到剪貼板錯(cuò)誤復(fù)制的
MultiBlocProvider
MultiBlocProvider是Flutter小部件溜腐,可將多個(gè)BlocProvider
小部件合并為一個(gè)译株。 MultiBlocProvider
提高了可讀性,消除了嵌套多個(gè)元素的需求BlocProviders
挺益。通過(guò)使用歉糜,MultiBlocProvider
我們可以從:
BlocProvider<BlocA>(
create: (BuildContext context) => BlocA(),
child: BlocProvider<BlocB>(
create: (BuildContext context) => BlocB(),
child: BlocProvider<BlocC>(
create: (BuildContext context) => BlocC(),
child: ChildA(),
)
)
)
至:
MultiBlocProvider(
providers: [
BlocProvider<BlocA>(
create: (BuildContext context) => BlocA(),
),
BlocProvider<BlocB>(
create: (BuildContext context) => BlocB(),
),
BlocProvider<BlocC>(
create: (BuildContext context) => BlocC(),
),
],
child: ChildA(),
)
BlocListener
BlocListener是Flutter小部件,它帶有BlocWidgetListener
和一個(gè)可選Bloc
望众,listener
以響應(yīng)bloc中的狀態(tài)變化匪补。它應(yīng)用于需要在每次狀態(tài)更改時(shí)發(fā)生一次的功能,例如導(dǎo)航烂翰,顯示a SnackBar
夯缺,顯示aDialog
等。
listener`與in和函數(shù)不同甘耿,每次狀態(tài)更改(**不**包括初始狀態(tài))僅被調(diào)用一次踊兜。`builder``BlocBuilder``void
如果省略cubit參數(shù),BlocListener
將使用BlocProvider
和當(dāng)前函數(shù)自動(dòng)執(zhí)行查找BuildContext
佳恬。
BlocListener<BlocA, BlocAState>(
listener: (context, state) {
// do stuff here based on BlocA's state
},
child: Container(),
)
僅當(dāng)您希望提供無(wú)法通過(guò)BlocProvider
和當(dāng)前訪問(wèn)的bloc時(shí)捏境,才指定該bloc BuildContext
。
BlocListener<BlocA, BlocAState>(
cubit: blocA,
listener: (context, state) {
// do stuff here based on BlocA's state
},
child: Container()
)
為了對(duì)何時(shí)listener
調(diào)用該函數(shù)進(jìn)行細(xì)粒度的控制毁葱,listenWhen
可以提供一個(gè)可選的選項(xiàng)垫言。listenWhen
獲取先前的bloc狀態(tài)和當(dāng)前的bloc狀態(tài)并返回一個(gè)布爾值。如果listenWhen
返回true倾剿,listener
將使用調(diào)用state
骏掀。如果listenWhen
返回false,listener
則不會(huì)調(diào)用state
柱告。
BlocListener<BlocA, BlocAState>(
listenWhen: (previousState, state) {
// return true/false to determine whether or not
// to call listener with state
},
listener: (context, state) {
// do stuff here based on BlocA's state
},
child: Container(),
)
MultiBlocListener
MultiBlocListener是Flutter小部件截驮,可將多個(gè)BlocListener
小部件合并為一個(gè)。 MultiBlocListener
提高了可讀性际度,消除了嵌套多個(gè)元素的需求BlocListeners
葵袭。通過(guò)使用,MultiBlocListener
我們可以從:
BlocListener<BlocA, BlocAState>(
listener: (context, state) {},
child: BlocListener<BlocB, BlocBState>(
listener: (context, state) {},
child: BlocListener<BlocC, BlocCState>(
listener: (context, state) {},
child: ChildA(),
),
),
)
至:
MultiBlocListener(
listeners: [
BlocListener<BlocA, BlocAState>(
listener: (context, state) {},
),
BlocListener<BlocB, BlocBState>(
listener: (context, state) {},
),
BlocListener<BlocC, BlocCState>(
listener: (context, state) {},
),
],
child: ChildA(),
)
BlocConsumer
BlocConsumer公開(kāi)builder
和listener
以便對(duì)新?tīng)顟B(tài)做出反應(yīng)乖菱。BlocConsumer
與嵌套類似BlocListener
坡锡,BlocBuilder
但減少了所需的樣板數(shù)量蓬网。BlocConsumer
僅應(yīng)在需要重建UI和執(zhí)行其他對(duì)狀態(tài)更改進(jìn)行響應(yīng)的情況下使用cubit
。BlocConsumer
取需要BlocWidgetBuilder
和BlocWidgetListener
和任選的cubit
鹉勒,BlocBuilderCondition
和BlocListenerCondition
帆锋。
如果cubit
省略該參數(shù),BlocConsumer
將使用BlocProvider
和當(dāng)前函數(shù)自動(dòng)執(zhí)行查找 BuildContext
禽额。
BlocConsumer<BlocA, BlocAState>(
listener: (context, state) {
// do stuff here based on BlocA's state
},
builder: (context, state) {
// return widget here based on BlocA's state
}
)
可選的listenWhen
锯厢,buildWhen
可以實(shí)現(xiàn),以更精細(xì)地控制何時(shí)listener
和builder
被調(diào)用脯倒。在listenWhen
和buildWhen
將在每個(gè)被調(diào)用cubit
state
的變化实辑。它們各自采用先前的state
和當(dāng)前的,state
并且必須返回a bool
藻丢,以確定是否將調(diào)用builder
and / orlistener
函數(shù)剪撬。以前state
會(huì)被初始化為state
的cubit
的時(shí)候BlocConsumer
被初始化。listenWhen
并且buildWhen
是可選的悠反,如果未實(shí)現(xiàn)残黑,則默認(rèn)為true
。
BlocConsumer<BlocA, BlocAState>(
listenWhen: (previous, current) {
// return true/false to determine whether or not
// to invoke listener with state
},
listener: (context, state) {
// do stuff here based on BlocA's state
},
buildWhen: (previous, current) {
// return true/false to determine whether or not
// to rebuild the widget with state
},
builder: (context, state) {
// return widget here based on BlocA's state
}
)
RepositoryProvider
RepositoryProvider是Flutter小部件斋否,它通過(guò)為其子節(jié)點(diǎn)提供存儲(chǔ)庫(kù)RepositoryProvider.of<T>(context)
萍摊。它用作依賴項(xiàng)注入(DI)小部件,以便可以將存儲(chǔ)庫(kù)的單個(gè)實(shí)例提供給子樹(shù)中的多個(gè)小部件如叼。BlocProvider
應(yīng)該用于提供塊,而RepositoryProvider
只能用于存儲(chǔ)庫(kù)穷劈。
RepositoryProvider(
create: (context) => RepositoryA(),
child: ChildA(),
);
然后ChildA
我們可以通過(guò)以下方式檢索Repository
實(shí)例:
// with extensions
context.read<RepositoryA>();
// without extensions
RepositoryProvider.of<RepositoryA>(context)
MultiRepositoryProvider
MultiRepositoryProvider是Flutter小部件笼恰,將多個(gè)RepositoryProvider
小部件合并為一個(gè)。 MultiRepositoryProvider
提高了可讀性歇终,消除了嵌套多個(gè)元素的需求RepositoryProvider
社证。通過(guò)使用,MultiRepositoryProvider
我們可以從:
RepositoryProvider<RepositoryA>(
create: (context) => RepositoryA(),
child: RepositoryProvider<RepositoryB>(
create: (context) => RepositoryB(),
child: RepositoryProvider<RepositoryC>(
create: (context) => RepositoryC(),
child: ChildA(),
)
)
)
至:
MultiRepositoryProvider(
providers: [
RepositoryProvider<RepositoryA>(
create: (context) => RepositoryA(),
),
RepositoryProvider<RepositoryB>(
create: (context) => RepositoryB(),
),
RepositoryProvider<RepositoryC>(
create: (context) => RepositoryC(),
),
],
child: ChildA(),
)
最后
相關(guān)地址
- demo地址: flutter_use
- flutter_bloc相關(guān)Api白嫖地址:flutter_bloc相關(guān)Api
- flutter_bloc
系列文章
- 告別克蘇魯代碼山:Flutter 改善套娃地獄問(wèn)題(仿喜馬拉雅PC頁(yè)面舉例)
- 讓Dialog擁有更多可能:這一次评凝,解決Flutter Dialog的各種痛點(diǎn)追葡!