flutter_bloc使用將從下圖的三個維度說明
前言
-
首先,有很多的文章在說flutter bloc模式的應用,但是百分之八九十的文章都是在說,使用StreamController+StreamBuilder搭建bloc,提升性能的會加上InheritedWidget,這些文章看了很多,真正寫使用bloc作者開發的flutter_bloc卻少之又少。沒辦法,只能去bloc的github上去找使用方式,最后去bloc官網翻文檔。
-
蛋痛,各位叼毛,就不能好好說說flutter_bloc的使用嗎?非要各種抄bloc模式提出作者的那倆篇文章。現在,搞的雜家這個伸手黨要自己去翻文檔總結(手動滑稽)。
項目效果(建議PC瀏覽器打開)
問題
初次使用flutter_bloc框架,可能會有幾個疑問
- state里面定義了太多變量,某個事件只需要更新其中一個變量,其它的變量賦相同值麻煩
- 進入某個模塊,進行初始化操作:復雜的邏輯運算,網絡請求等,入口在哪定義
准備工作
說明
- 這里說明下,文章里把
BlocBuilder
放在頂層,因為本身頁面非常簡單,也是為了更好呈現頁面結構,所以才放在頂層,如果需要更加顆粒化控件更新區域,請將BlocBuilder
包裹你需要更新的控件區域即可
引用
- 我覺得學習一個模式或者框架的時候,最主要的是把主流程跑通,起碼可以符合標准的堆頁面,這樣的話,就可以把這玩意用起來,再遇到想要的什么細節,就可以自己去翻文檔,畢竟大體上已經懂了,寫過了幾個頁面,也有些體會,再去翻文檔就很快能理解了
- 實際上Bloc給的API也不多,就幾個API,相關API使用說明都寫在文章最后
庫
flutter_bloc: ^6.1.1 #狀態管理框架
equatable: ^1.2.3 #增強組件相等性判斷
- 看看flutter_bloc都推到6.0了,別再用StreamController手搭Bloc了!
插件
在Android Studio設置的Plugins里,搜索:Bloc
安裝重啟下,就OK了
- 右擊相應的文件夾,選擇“Bloc Class”,我在main文件夾新建的,填入的名字:main,就自動生成下面三個文件;:main_bloc,main_event,main_state;main_view是我自己新建,用來寫頁面的。
- 是不是覺得,還在手動新建這些bloc文件low爆了;就好像fish_redux,不用插件,讓我手動去創建那六個文件,寫那些模板代碼,真的要原地爆炸。
Bloc范例
效果
- 好了,嗶嗶了一堆,看下咱們要用flutter_bloc實現的效果。
- 直接開Chrome演示,大家在虛擬機上跑也一樣。
初始化代碼
來看下這三個生成的bloc文件:main_bloc,main_event,main_state
- main_bloc:這里就是咱們主要寫邏輯的頁面了
- mapEventToState方法只有一個參數,后面自動帶了一個逗號,格式化代碼就分三行了,建議刪掉逗號,格式化代碼。
class MainBloc extends Bloc<MainEvent, MainState> {
MainBloc() : super(MainInitial());
@override
Stream<MainState> mapEventToState(
MainEvent event,
) async* {
// TODO: implement mapEventToState
}
}
- main_event:這里是執行的各類事件,有點類似fish_redux的action層
@immutable
abstract class MainEvent {}
- main_state:狀態數據放在這里保存,中轉
@immutable
abstract class MainState {}
class MainInitial extends MainState {}
實現
- 主入口
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MainPage(),
);
}
}
- 說明
- 這里對於簡單的頁面,state的使用抽象狀態繼承實現的方式,未免有點麻煩,這里我進行一點小改動,state的實現類別有很多,官網寫demo也有不用抽象類,直接class,類似實體類的方式開搞的。
- 相關代碼的注釋寫的比較多,大家可以着重看看
- main_bloc
- state變量是框架內部定義的,會默認保存上一次同步的MainSate對象的值
class MainBloc extends Bloc<MainEvent, MainState> {
MainBloc() : super(MainState(selectedIndex: 0, isExtended: false));
@override
Stream<MainState> mapEventToState(MainEvent event) async* {
///main_view中添加的事件,會在此處回調,此處處理完數據,將數據yield,BlocBuilder就會刷新組件
if (event is SwitchTabEvent) {
///獲取到event事件傳遞過來的值,咱們拿到這值塞進MainState中
///直接在state上改變內部的值,然后yield,只能觸發一次BlocBuilder,它內部會比較上次MainState對象,如果相同,就不build
yield MainState()
..selectedIndex = event.selectedIndex
..isExtended = state.isExtended;
} else if (event is IsExtendEvent) {
yield MainState()
..selectedIndex = state.selectedIndex
..isExtended = !state.isExtended;
}
}
}
- main_event:在這里就能看見,view觸發了那些事件了;維護起來也很爽,看看這里,也很快能懂頁面在干嘛了
@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];
}
///展開NavigationRail,這個邏輯比較簡單,就不用傳參數了
class IsExtendEvent extends MainEvent{
const IsExtendEvent();
@override
List<Object> get props => [];
}
-
main_state:state有很多種寫法,在bloc官方文檔上,不同項目state的寫法也很多
-
這邊變量名可以設置為私用,用get和set可選擇性的設置讀寫權限,因為我這邊設置的倆個變量全是必用的,讀寫均要,就設置公有類型,不用下划線“_”去標記私有了。
-
對於生成的模板代碼,我們在這:去掉@immutable注解,去掉abstract;
-
這里說下加上@immutable和abstract的作用,這邊是為了標定不同狀態,這種寫法,會使得代碼變得更加麻煩,用state不同狀態去標定業務事件,代價太大,這邊用一個變量去標定,很容易輕松代替
-
class MainState{
int selectedIndex;
bool isExtended;
MainState({this.selectedIndex, this.isExtended});
}
- main_view
- 這邊就是咱們的界面層了,很簡單,將需要刷新的組件,用BlocBuilder包裹起來,使用BlocBuilder:提供的state去賦值就ok了,context去添加執行的事件,context用StatelessWidget中提供的或者BlocBuilder提供的都行
class MainPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return _buildBg(children: [
//側邊欄
_buildLeftNavigation(),
//右邊主體內容
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}) {
///創建BlocProvider的,表明該Page,我們是用MainBloc,MainBloc是屬於該頁面的Bloc了
return BlocProvider(
create: (BuildContext context) => MainBloc(),
child: Scaffold(
appBar: AppBar(title: Text('Bloc')),
body: Row(children: children),
),
);
}
//增加NavigationRail組件為側邊欄
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,
//側邊欄中的item
destinations: [
NavigationRailDestination(
icon: Icon(Icons.add_to_queue),
selectedIcon: Icon(Icons.add_to_photos),
label: Text("測試一"),
),
NavigationRailDestination(
icon: Icon(Icons.add_circle_outline),
selectedIcon: Icon(Icons.add_circle),
label: Text("測試二"),
),
NavigationRailDestination(
icon: Icon(Icons.bubble_chart),
selectedIcon: Icon(Icons.broken_image),
label: Text("測試三"),
),
],
//頂部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展開,收縮事件
BlocProvider.of<MainBloc>(context).add(IsExtendEvent());
},
child: Icon(state.isExtended ? Icons.send : Icons.navigation),
);
},
),
);
}
}
Bloc范例優化
反思
從上面的代碼來看,實際存在幾個隱式問題,這些問題,剛開始使用時候,沒異常的感覺,但是使用bloc久了后,感覺肯定越來越強烈
- state問題
- 初始化問題:這邊初始化是在bloc里,直接在構造方法里面賦初值的,state中一旦變量多了,還是這么寫,會感覺極其難受,不好管理。需要優化
- 可以看見這邊我們只改動selectedIndex或者isExtended;另一個變量不需要變動,需要保持上一次的數據,進行了此類:state.selectedIndex或者state.isExtended賦值,一旦變量達到十幾個乃至幾十個,還是如此寫,是讓人極其崩潰的。需要優化
- bloc問題
- 如果進行一個頁面,需要進行復雜的運算或者請求接口后,才能知曉數據,進行賦值,這里肯定需要一個初始化入口,初始化入口需要怎樣去定義呢?
插件
因為官方插件生成的寫法,和調整后寫法差距有點大,而且官方插件不支持生成view層和相關設置,此處我就擼了一個插件,完善了相關功能
請注意,wrap代碼和提示代碼片段,參靠了官方插件規則
Wrap Widget 規則來着:intellij_generator_plugin
快捷代碼生成規則來着: intellij_generator_plugin
- 在Android Studio里面搜索 flutter bloc
- 生成模板代碼
- 支持修改后綴
- Wrap Widget (alt + enter):RepositoryProvider,BlocConsumer,BlocBuilder,BlocProvider,BlocListener
- 輸入 bloc 可生成快捷代碼片段
優化實現
這邊完整走一下流程,讓大家能有個完整的思路
- state:首先來看看我們對state中的優化,這邊進行了倆個很重要優化,增加倆個方法:init()和clone()
- init():這里初始化統一用init()方法去管理
- clone():這邊克隆方法,是非常重要的,一旦變量達到倆位數以上,就能深刻體會該方法是多么的重要
class MainState {
int selectedIndex;
bool isExtended;
///初始化方法,基礎變量也需要賦初值,不然會報空異常
MainState init() {
return MainState()
..selectedIndex = 0
..isExtended = false;
}
///clone方法,此方法實現參考fish_redux的clone方法
///也是對官方Flutter Login Tutorial這個demo中copyWith方法的一個優化
///Flutter Login Tutorial(https://bloclibrary.dev/#/flutterlogintutorial)
MainState clone() {
return MainState()
..selectedIndex = selectedIndex
..isExtended = isExtended;
}
}
- event
- 這邊定義一個MainInit()初始化方法,同時去掉Equatable繼承,在我目前的使用中,感覺它用處不大。。。
@immutable
abstract class MainEvent {}
///初始化事件,這邊目前不需要傳什么值
class MainInitEvent extends MainEvent {}
///切換NavigationRail的tab
class SwitchTabEvent extends MainEvent {
final int selectedIndex;
SwitchTabEvent({@required this.selectedIndex});
}
///展開NavigationRail,這個邏輯比較簡單,就不用傳參數了
class IsExtendEvent extends MainEvent {}
- bloc
- 這增加了初始化方法,請注意,如果需要進行異步請求,同時需要將相關邏輯提煉一個方法,咱們在這里配套Future和await就能解決在異步場景下同步數據問題
- 這里使用了克隆方法,可以發現,我們只要關注自己需要改變的變量就行了,其它的變量都在內部賦值好了,我們不需要去關注;這就大大的便捷了頁面中有很多變量,只需要變動一倆個變量的場景
- 注意:如果變量的數據未改變,界面相關的widget是不會重繪的;只會重繪變量被改變的widget
class MainBloc extends Bloc<MainEvent, MainState> {
MainBloc() : super(MainState().init());
@override
Stream<MainState> mapEventToState(MainEvent event) async* {
///main_view中添加的事件,會在此處回調,此處處理完數據,將數據yield,BlocBuilder就會刷新組件
if (event is MainInitEvent) {
yield await init();
} else if (event is SwitchTabEvent) {
///獲取到event事件傳遞過來的值,咱們拿到這值塞進MainState中
///直接在state上改變內部的值,然后yield,只能觸發一次BlocBuilder,它內部會比較上次MainState對象,如果相同,就不build
yield switchTap(event);
} else if (event is IsExtendEvent) {
yield isExtend();
}
}
///初始化操作,在網絡請求的情況下,需要使用如此方法同步數據
Future<MainState> init() async {
return state.clone();
}
///切換tab
MainState switchTap(SwitchTabEvent event) {
return state.clone()..selectedIndex = event.selectedIndex;
}
///是否展開
MainState isExtend() {
return state.clone()..isExtended = !state.isExtended;
}
}
- view
- view層代碼太多,這邊只增加了個初始化事件,就不重新把全部代碼貼出來了,初始化操作直接在創建的時候,在XxxBloc上使用add()方法就行了,就能起到進入頁面,初始化一次的效果;add()方法也是Bloc類中提供的,遍歷事件的時候,就特地檢查了add()這個方法是否添加了事件;說明,這是框架特地提供了一個初始化的方法
- 這個初始化方式是在官方示例找到的
- 項目名:Flutter Infinite List Tutorial
- 項目地址:flutter-infinite-list-tutorial
class MainPage extends StatelessWidget {
...
Widget _buildBg({List<Widget> children}) {
///創建BlocProvider的,表明該Page,我們是用MainBloc,MainBloc是屬於該頁面的Bloc了
return BlocProvider(
create: (BuildContext context) => MainBloc()..add(MainInitEvent()),
child: Scaffold(
appBar: AppBar(title: Text('Bloc')),
body: Row(children: children),
),
);
}
///下方其余代碼省略...........
}
搞定
- OK,經過這樣的優化,解決了幾個痛點。實際在view中反復是要用BlocBuilder去更新view,寫起來有點麻煩,這里我們可以寫一個,將其中state和context變量,往提出來的Widget方法傳值,也是蠻不錯的
- 大家保持觀察者模式的思想就行了;觀察者(回調刷新控件)和被觀察者(產生相應事件,添加事件,去通知觀察者),bloc層是處於觀察者和被觀察者中間的一層,我們可以在bloc里面搞業務,搞邏輯,搞網絡請求,不能搞基;拿到Event事件傳遞過來的數據,把處理好的、符合要求的數據返回給view層的觀察者就行了。
- 使用框架,不拘泥框架,在觀察者模式的思想上,靈活的去使用flutter_bloc提供Api,這樣可以大大的縮短我們的開發時間!
Cubit范例
- Cubit是Bloc模式的一種簡化版,去掉了event這一層,對於簡單的頁面,用Cubit來實現,開發體驗是大大的好啊,下面介紹下該種模式的寫法
創建
- 首先創建Cubit一組文件,選擇“Cubit”,新建名稱填寫:Counter
新建好后,他會生成三個文件:cubit,state,view;來看下生成的代碼
模板代碼
- 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();
}
}
實現計時器
- 來實現下一個灰常簡單的計數器
效果
- 來看下實現效果吧,這邊不上圖了,大家點擊下面的鏈接,可以直接體驗Cubit模式寫的計時器
- 實現效果:點我體驗實際效果
實現
實現很簡單,三個文件就搞定,看下流程:state -> cubit -> view
- state:這個很簡單,加個計時變量
class BlCubitCounterState {
late int count;
BlCubitCounterState init() {
return BlCubitCounterState()..count = 0;
}
BlCubitCounterState clone() {
return BlCubitCounterState()..count = count;
}
}
- cubit
- 這邊加了個自增方法:increase()
- event層實際是所有行為的一種整合,方便對邏輯過於復雜的頁面,所有行為的一種維護;但是過於簡單的頁面,就那么幾個事件,還單獨維護,就沒什么必要了
- 在cubit層寫的公共方法,在view里面能直接調用,更新數據使用:emit()
- cubit層應該可以算是:bloc層和event層一種結合后的簡寫
class BlCubitCounterCubit extends Cubit<BlCubitCounterState> {
BlCubitCounterCubit() : super(BlCubitCounterState().init());
///自增
void increment() => emit(state.clone()..count = ++state.count);
}
- view
- view層的代碼就非常簡單了,點擊方法里面調用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(
'點擊了 ${cubit.state.count} 次',
style: TextStyle(fontSize: 30.0),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => cubit.increment(),
child: Icon(Icons.add),
),
);
}
}
總結
在Bloc模式里面,如果頁面不是過於復雜,使用Cubit去寫,基本完全夠用了;但是如果業務過於復雜,還是需要用Bloc去寫,需要將所有的事件行為管理起來,便於后期維護
OK,Bloc的簡化模塊,Cubit模式就這樣講完了,對於自己業務寫的小項目,我就經常用這個Cubit去寫
全局Bloc
說明
什么是全局Bloc?
- BlocProvider介紹里面有這樣的形容:BlocProvider should be used to create new blocs which will be made available to the rest of the subtree(BlocProvider應該被用於創建新的Bloc,這些Bloc將可用於其子樹)
- 這樣的話,我們只需要在主入口地方使用BlocProvider創建Bloc,就能使用全局的XxxBloc了,這里的全局XxxBloc,state狀態都會被保存的,除非關閉app,否則state里面的數據都不會被還原!
- 注意:在主入口創建的XxxBloc,在主入口處創建了一次,在其它頁面均不需要再次創建,在任何頁面只需要使用BlocBuilder,便可以定點刷新及其獲取全局XxxBloc的state數據
使用場景
- 全局的主題色,字體樣式和大小等等全局配置更改;這種情況,在需要全局屬性的地方,使用BlocBuilder對應的全局XxxBloc泛型去刷新數據就行了
- 跨頁面去調用事件,既然是全局的XxxBloc,這就說明,我們可以在任何頁面,使用
BlocProvider.of<XxxBloc>(context)
調用全局XxxBloc中事件,這就起到了一種跨頁面調用事件的效果- 使用全局Bloc做跨頁面事件時,應該明白,當你關閉Bloc對應的頁面,對應全局Bloc中的並不會被回收,下次進入頁面,頁面的數據還是上次退出頁面修改的數據,這里應該使用StatefulWidget,在initState生命周期處,初始化數據;或者在dispose生命周期處,還原數據源
- 思考下:全局Bloc對象存在周期是在整個App存活周期,必然不能創建過多的全局Bloc,跨頁面傳遞事件使用全局Bloc應當只能做折中方案
效果圖
使用
來看下怎么創建和使用全局Bloc吧!
- 主入口配置
- 全局的Bloc創建還是蠻簡單的,這邊把
MultiBlocProvider
在Builder里面套在child上面就行了;當然了,把MultiBlocProvider
套在MaterialApp上也是可以的 - 這樣我們就獲得一個全局的
SpanOneCubit
- 全局的Bloc創建還是蠻簡單的,這邊把
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: [
///此處通過BlocProvider創建的Bloc或者Cubit是全局的
BlocProvider<SpanOneCubit>(
create: (BuildContext context) => SpanOneCubit(),
),
],
child: child,
);
},
);
}
}
需要用倆個Bloc模塊來演示,這里分別用SpanOneCubit
和SpanTwoCubit
來演示,其中SpanOneCubit
是全局的
SpanOneCubit
- state
- 先來看看State模塊的代碼,這里很簡單,只定義了count變量
class SpanOneState {
int count;
///初始化方法
SpanOneState init() {
return SpanOneState()..count = 0;
}
///克隆方法,針對於刷新界面數據
SpanOneState clone() {
return SpanOneState()..count = count;
}
}
- view
- 這個頁面僅僅是展示計數變量的變化,因為在主入口使用了
BlocProvider
創建了SpanOneCubit
,所以在這個頁面不需要再次創建,直接使用BlocBuilder
便可以獲取其state - 可以發現,這個頁面使用了StatefulWidget,在initState周期中,初始化了數據源;這樣,每次進入頁面,數據源就不會保存為上一次改動的來,都會被初始化為我們想要的值;這個頁面能接受到任何頁面調用其事件,這樣就實現類似於廣播的一種效果(
偽
)
- 這個頁面僅僅是展示計數變量的變化,因為在主入口使用了
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('跨頁面-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點擊了 ${state.count} 次',
style: TextStyle(fontSize: 30.0),
);
},
),
),
);
}
}
- cubit
- cubit里面有三個事件,初始化,跳轉頁面,計數自增
class SpanOneCubit extends Cubit<SpanOneState> {
SpanOneCubit() : super(SpanOneState().init());
void init() {
emit(state.init());
}
///跳轉到跨頁面
void toSpanTwo(BuildContext context) {
Navigator.push(context, MaterialPageRoute(builder: (context) => SpanTwoPage()));
}
///自增
void increase() {
state..count = ++state.count;
emit(state.clone());
}
}
SpanTwoCubit
- state
- 使用count,記錄下我們點擊自增的次數
class SpanTwoState {
int count;
///初始化方法
SpanTwoState init() {
return SpanTwoState()..count = 0;
}
///克隆方法,針對於刷新界面數據
SpanTwoState clone() {
return SpanTwoState()..count = count;
}
}
- view
- 這地方我們需要創建使用BlocProvider一個SpanTwoCubit,這是使用Bloc的常規流程
- 在自增的點擊事件里,我們調用本模塊和
SpanOneCubit
中的自增方法,OK,這里我們就能同步的改變SpanOneCubit
模塊的數據了!
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('跨頁面-Two')),
floatingActionButton: FloatingActionButton(
onPressed: () {
//改變SpanOneCubit模塊數據
BlocProvider.of<BlocSpanOneCubit>(context).increase();
//改變當前頁面數據
cubit.increase();
},
child: const Icon(Icons.add),
),
body: Center(
child: BlocBuilder<BlocSpanTwoCubit, BlocSpanTwoState>(
builder: (context, state) {
return Text('當前點擊了 ${state.count} 次',
style: TextStyle(fontSize: 30.0));
},
),
),
);
}
}
- cubit
- 平平無奇的業務代碼
class SpanTwoCubit extends Cubit<SpanTwoState> {
SpanTwoCubit() : super(SpanTwoState().init());
void init(BuildContext context){
emit(state.init());
}
///自增
void increase() => emit(state.clone()..count = ++state.count);
}
總結
OK,這樣便用全局Bloc實現了類似廣播的一種效果
- 使用全局去刷新:主題,字體樣式和大小之類,每個頁面都要使用BlocBuilder對應的全局bloc去刷新對應的全局view模塊
Bloc API說明
BlocBuilder
BlocBuilder是Flutter窗口小部件,需要Bloc
和builder
函數。BlocBuilder
處理構建小部件以響應新狀態。BlocBuilder
與非常相似,StreamBuilder
但具有更簡單的API,可以減少所需的樣板代碼量。該builder
函數可能會被多次調用,並且應該是一個純函數,它會根據狀態返回小部件。
看看BlocListener
是否要響應狀態更改“執行”任何操作,例如導航,顯示對話框等。
如果省略cubit參數,BlocBuilder
將使用BlocProvider
和當前函數自動執行查找BuildContext
。
BlocBuilder<BlocA, BlocAState>(
builder: (context, state) {
// return widget here based on BlocA's state
}
)
僅當您希望提供一個范圍僅限於單個窗口小部件且無法通過父級BlocProvider
和當前類訪問的bloc時,才指定該bloc BuildContext
。
BlocBuilder<BlocA, BlocAState>(
cubit: blocA, // provide the local cubit instance
builder: (context, state) {
// return widget here based on BlocA's state
}
)
為了對何時builder
調用該函數進行細粒度的控制,buildWhen
可以提供一個可選的選項。buildWhen
獲取先前的塊狀態和當前的塊狀態並返回一個布爾值。如果buildWhen
返回true,builder
將使用進行調用,state
並且小部件將重新生成。如果buildWhen
返回false,builder
則不會調用state
且不會進行重建。
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小部件,可通過為其子元素提供塊BlocProvider.of<T>(context)
。它用作依賴項注入(DI)小部件,以便可以將一個塊的單個實例提供給子樹中的多個小部件。
在大多數情況下,BlocProvider
應使用它來創建新的bloc,這些bloc將可用於其余子樹。在這種情況下,由於BlocProvider
負責創建塊,它將自動處理關閉bloc。
BlocProvider(
create: (BuildContext context) => BlocA(),
child: ChildA(),
);
默認情況下,BlocProvider
將懶惰地創建bloc,這意味着create
當通過查找塊時將執行該bloc BlocProvider.of<BlocA>(context)
。
要覆蓋此行為並強制create
立即運行,lazy
可以將其設置為false
。
BlocProvider(
lazy: false,
create: (BuildContext context) => BlocA(),
child: ChildA(),
);
在某些情況下,BlocProvider
可用於向小部件樹的新部分提供現有的bloc。當需要將現有bloc用於新路線時,這將是最常用的。在這種情況下,BlocProvider
由於不會創建bloc,因此不會自動關閉該bloc。
BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: ScreenA(),
);
然后從ChildA
或ScreenA
中檢索BlocA
:
// with extensions
context.read<BlocA>();
// without extensions
BlocProvider.of<BlocA>(context)復制到剪貼板錯誤復制的
MultiBlocProvider
MultiBlocProvider是Flutter小部件,可將多個BlocProvider
小部件合並為一個。 MultiBlocProvider
提高了可讀性,消除了嵌套多個元素的需求BlocProviders
。通過使用,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
和一個可選Bloc
,listener
以響應bloc中的狀態變化。它應用於需要在每次狀態更改時發生一次的功能,例如導航,顯示a SnackBar
,顯示aDialog
等。
listener`與in和函數不同,每次狀態更改(**不**包括初始狀態)僅被調用一次。`builder``BlocBuilder``void
如果省略cubit參數,BlocListener
將使用BlocProvider
和當前函數自動執行查找BuildContext
。
BlocListener<BlocA, BlocAState>(
listener: (context, state) {
// do stuff here based on BlocA's state
},
child: Container(),
)
僅當您希望提供無法通過BlocProvider
和當前訪問的bloc時,才指定該bloc BuildContext
。
BlocListener<BlocA, BlocAState>(
cubit: blocA,
listener: (context, state) {
// do stuff here based on BlocA's state
},
child: Container()
)
為了對何時listener
調用該函數進行細粒度的控制,listenWhen
可以提供一個可選的選項。listenWhen
獲取先前的bloc狀態和當前的bloc狀態並返回一個布爾值。如果listenWhen
返回true,listener
將使用調用state
。如果listenWhen
返回false,listener
則不會調用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小部件,可將多個BlocListener
小部件合並為一個。 MultiBlocListener
提高了可讀性,消除了嵌套多個元素的需求BlocListeners
。通過使用,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公開builder
和listener
以便對新狀態做出反應。BlocConsumer
與嵌套類似BlocListener
,BlocBuilder
但減少了所需的樣板數量。BlocConsumer
僅應在需要重建UI和執行其他對狀態更改進行響應的情況下使用cubit
。BlocConsumer
取需要BlocWidgetBuilder
和BlocWidgetListener
和任選的cubit
,BlocBuilderCondition
和BlocListenerCondition
。
如果cubit
省略該參數,BlocConsumer
將使用BlocProvider
和當前函數自動執行查找 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
可以實現,以更精細地控制何時listener
和builder
被調用。在listenWhen
和buildWhen
將在每個被調用cubit
state
的變化。它們各自采用先前的state
和當前的,state
並且必須返回a bool
,以確定是否將調用builder
and / orlistener
函數。以前state
會被初始化為state
的cubit
的時候BlocConsumer
被初始化。listenWhen
並且buildWhen
是可選的,如果未實現,則默認為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小部件,它通過為其子節點提供存儲庫RepositoryProvider.of<T>(context)
。它用作依賴項注入(DI)小部件,以便可以將存儲庫的單個實例提供給子樹中的多個小部件。BlocProvider
應該用於提供塊,而RepositoryProvider
只能用於存儲庫。
RepositoryProvider(
create: (context) => RepositoryA(),
child: ChildA(),
);
然后ChildA
我們可以通過以下方式檢索Repository
實例:
// with extensions
context.read<RepositoryA>();
// without extensions
RepositoryProvider.of<RepositoryA>(context)
MultiRepositoryProvider
MultiRepositoryProvider是Flutter小部件,將多個RepositoryProvider
小部件合並為一個。 MultiRepositoryProvider
提高了可讀性,消除了嵌套多個元素的需求RepositoryProvider
。通過使用,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(),
)
最后
相關地址
- demo地址: flutter_use
- flutter_bloc相關Api白嫖地址:flutter_bloc相關Api
- flutter_bloc
系列文章
- 解決方案
- Dialog解決方案,牆裂推薦:一種更優雅的Flutter Dialog解決方案
- 狀態管理
- 中小項目牆裂推薦:Flutter GetX使用---簡潔的魅力!
- 大型項目推薦:fish_redux使用詳解---看完就會用!