Bloc 的名字比較新穎,這個狀態管理框架的目的是將 UI 層和業務邏輯進行分離。Bloc 的複雜度處於 ScopedModel 和 Redux 之間,相較於 ScopedModel,Bloc 擁有分明的架構處於業務邏輯,相較於 Redux,Bloc 着重於業務邏輯的分解,使得整個框架對於開發來說簡單實用。git
Bloc 分爲三層:github
Presentation Layer 只與 Bloc Layer 交互,Data Laye 也只與 Bloc Layer 交互。Bloc Layer 做爲重要一層,處於表現層和數據層之間,使得 UI 和數據經過 Bloc Layer 進行交互。bash
因而可知,Bloc 的架構和客戶端主流的 MVC 和 MVP 架構比較類似,但也存在 Event 和 State 的概念一同構成響應式框架。網絡
BlocProvider,一般作爲 App 的根佈局。BlocProvider 能夠保存 Bloc,在其它頁面經過BlocProvider.of<Bloc>(context)
獲取 Bloc。架構
Event,用戶操做 UI 後發出的事件,用於通知 Bloc 層事件發生。app
State,頁面狀態,可用於構建 UI。一般是 Bloc 將接收到的 Event 轉化爲 State。框架
Bloc 架構的核心是 Bloc 類,Bloc 類是一個抽象類,有一個 mapEventToState(event)
方法須要實現。mapEventToState(event)
顧名思義,就是將用戶點擊 View 時發出的 event 轉化爲構建 UI 所用的 State。另外,在 StatefulWidget 中使用 bloc 的話,在 widget dispose 時,要調用 bloc.dispose()
方法進行釋放。async
這裏以常見的獲取列表選擇列表爲例子。一個頁面用於展現選中項和跳轉到列表,一個頁面用於顯示列表。ide
在 pubspec.yaml
文件中引入 flutter_bloc
第三方庫支持 bloc 功能。佈局
# 引入 bloc 第三方庫
flutter_bloc: ^0.9.0
複製代碼
這一步無關緊要,但使用插件會方便開發,不使用的話也沒什麼問題。
Bloc 官方提供了 VSCode 和 Android studio 的插件,方便生成 Bloc 框架用到的相關類。 下文以 Android studio 的插件爲例。
好比 list 頁面,該插件會生成相應的類
從生成的五個文件中也能夠看到,list_bloc
負責承載業務邏輯,list_page
負責編寫 UI 界面,list_event
和 list_state
分別是事件和狀態,其中 list.dart
文件是用於導出前面四個文件的。
具體使用可見
在 main.dart
中,使用 BlocProvider 做爲父佈局包裹,用於傳遞須要的 bloc。Demo 中包含兩個頁面,一個是展現頁面 ShowPage,一個是列表頁面 ListPage。
上面講到,Bloc 的核心功能在於 Bloc 類,對於展現頁面 ShowPage,會有一個 ShowBloc 繼續自 Bloc 類。因爲展現頁面 ShowPage 會和列表頁面 ListPage 有數據的互動,因此這裏將 ShowBloc 保存在 BlocProvider 中進行傳遞。
@override
Widget build(BuildContext context) {
return BlocProvider(
bloc: _showBloc,
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ShowPage()));
}
複製代碼
① ShowEvent
列表的 item 點擊後,須要發送一個 event 通知其它頁面列表被選中,這裏定義一個 SelectShowEvent 做爲這種 event 通知。
class SelectShowEvent extends ShowEvent {
String selected;
SelectShowEvent(this.selected);
}
複製代碼
② ShowState
State 用於表示一種界面狀態,即一個 State 就對應一個界面。插件在一開始會生成一個默認狀態,InitialShowState。咱們可使用 InitialShowState 來表明初始的界面。另外,咱們本身定義一種狀態,SelectedShowState,表明選中列表後的 State。
@immutable
abstract class ShowState {}
class InitialShowState extends ShowState {}
class SelectedShowState extends ShowState {
String _selectedString = "";
String get selected => _selectedString;
SelectedShowState(this._selectedString);
}
複製代碼
③ ShowBloc
Bloc 的主要職責是接收 Event,而後把 Event 轉化爲對應的 State。這裏的 ShowBloc 繼續自 Bloc,須要重寫實現抽象方法 mapEventToState(event)
。在這個方法中,咱們判斷傳過來的 event 是否是 SelectShowEvent,是則拿到 SelectShowEvent 中的 selected 變量去構建 SelectedShowState。mapEventToState(event)
返回的是一個 Stream,咱們經過 yield 關鍵字去返回一個 SelectedShowState。
class ShowBloc extends Bloc<ShowEvent, ShowState> {
@override
ShowState get initialState => InitialShowState();
@override
Stream<ShowState> mapEventToState(
ShowEvent event,
) async* {
if (event is SelectShowEvent) {
yield SelectedShowState(event.selected);
}
}
}
複製代碼
④ ShowPage
在 ShowPage 的界面上,咱們須要根據 showBloc 中是否有被選中的列表項目去展於頁面,因此這裏咱們先使用使用BlocProvider.of<ShowBloc>(context)
去拿到 showBloc,接着再用 BlocBuilder 根據 showBloc 構建界面。使用 BlocBuilder 的好處就是可讓頁面自動響應 showBloc 的變化而變化。
var showBloc = BlocProvider.of<ShowBloc>(context);
...
BlocBuilder(
bloc: showBloc,
builder: (context, state) {
if (state is SelectedShowState) {
return Text(state.selected);
}
return Text("");
}),
複製代碼
① ListEvent
列表頁面,咱們一開始須要從網絡中拉取列表數據,因此定義一個 FetchListEvent 事件在進入頁面時通知 ListBloc 去獲取列表。
@immutable
abstract class ListEvent extends Equatable {
ListEvent([List props = const []]) : super(props);
}
class FetchListEvent extends ListEvent {}
複製代碼
② ListState
InitialListState 是插件默認生成的初始狀態,另外定義一個 FetchListState 表明獲取列表完成的狀態。
@immutable
abstract class ListState extends Equatable {
ListState([List props = const []]) : super(props);
}
class InitialListState extends ListState {}
class FetchListState extends ListState {
List<String> _list = [];
UnmodifiableListView<String> get list => UnmodifiableListView(_list);
FetchListState(this._list);
}
複製代碼
③ ListBloc
在 ListBloc 中,進行從網絡獲取列表數據的業務。這裏經過一個延時操做摸擬網絡請求,最後用 yield 返回列表數據。
class ListBloc extends Bloc<ListEvent, ListState> {
@override
ListState get initialState => InitialListState();
@override
Stream<ListState> mapEventToState(
ListEvent event,
) async* {
if (event is FetchListEvent) {
// 模擬網絡請求
await Future.delayed(Duration(milliseconds: 2000));
var list = [
"1. Bloc artitechture",
"2. Bloc artitechture",
"3. Bloc artitechture",
"4. Bloc artitechture",
"5. Bloc artitechture",
"6. Bloc artitechture",
"7. Bloc artitechture",
"8. Bloc artitechture",
"9. Bloc artitechture",
"10. Bloc artitechture"
];
yield FetchListState(list);
}
}
}
複製代碼
④ ListPage
在列表頁面初始化時有兩個操做,一個是初始化 listBloc,一個是發出列表請求的 Event。
@override
void initState() {
bloc = ListBloc(); // 初始化listBloc
bloc.dispatch(FetchListEvent()); // 發出列表請求事件
super.initState();
}
複製代碼
接下用,即是用 BlocBuilder 去響應狀態。當 state 是 InitialListState,說明未獲取列表,則顯示 loading 界面,當 state 是 FetchListState 時,說明已經成功獲取列表,顯示列表界面。
body: BlocBuilder(
bloc: bloc,
builder: (context, state) {
// 根據狀態顯示界面
if (state is InitialListState) {
// 顯示 loading 界面
return buildLoad();
} else if (state is FetchListState) {
// 顯示列表界面
var list = state.list;
return buildList(list);
}
}));
複製代碼
最後,記得對 bloc 進行 dispose()
。
@override
void dispose() {
bloc.dispose();
super.dispose();
}
複製代碼
具體代碼能夠到 github 查看。
在 Bloc 的架構中,將一個頁面和一個 Bloc 相結合,由頁面產生 Event,Bloc 根據業務須要將 Event 轉化爲 State,再把 State 交給頁面中的 BlocBuilder 構建 UI。Demo 中只是給出了簡單的狀態管理,實際項目中,好比網絡請求,有請求中、請求成功、請求失敗的多種狀態,能夠作適當封裝使 Bloc 更加易用。相比於 Redux,Bloc 不須要將全部狀態集中管理,這樣對於不一樣模塊的頁面易於拆分,對於代碼量比較大的客戶端而言,Bloc 的架構會相對比較友好。