一、Bloc 介紹
Bloc 的名字比較新穎,這個狀態(tài)管理框架的目的是將 UI 層和業(yè)務(wù)邏輯進(jìn)行分離。Bloc 的復(fù)雜度處于 ScopedModel 和 Redux 之間,相較于 ScopedModel,Bloc 擁有分明的架構(gòu)處于業(yè)務(wù)邏輯,相較于 Redux,Bloc 著重于業(yè)務(wù)邏輯的分解,使得整個框架對于開發(fā)來講簡單實(shí)用。
二、Bloc 的層次結(jié)構(gòu)
Bloc 分為三層:
- Data Layer(數(shù)據(jù)層),用于提供數(shù)據(jù)。
- Bloc(Business Logic) Layer(業(yè)務(wù)層),通過繼續(xù) Bloc 類實(shí)現(xiàn),用于處理業(yè)務(wù)邏輯。
- Presentation Layer(表現(xiàn)層),用于 UI 構(gòu)建。
Presentation Layer 只與 Bloc Layer 交互,Data Laye 也只與 Bloc Layer 交互。Bloc Layer 作為重要一層,處于表現(xiàn)層和數(shù)據(jù)層之間,使得 UI 和數(shù)據(jù)通過 Bloc Layer 進(jìn)行交互。
由此可見,Bloc 的架構(gòu)和客戶端主流的 MVC 和 MVP 架構(gòu)比較相似,但也存在 Event 和 State 的概念一同構(gòu)成響應(yīng)式框架。
三、Bloc 需要知道的概念
BlocProvider,通常做為 App 的根布局。BlocProvider 可以保存 Bloc,在其它頁面通過BlocProvider.of<Bloc>(context)獲取 Bloc。
Event,用戶操作 UI 后發(fā)出的事件,用于通知 Bloc 層事件發(fā)生。
State,頁面狀態(tài),可用于構(gòu)建 UI。通常是 Bloc 將接收到的 Event 轉(zhuǎn)化為 State。
Bloc 架構(gòu)的核心是 Bloc 類,Bloc 類是一個抽象類,有一個 mapEventToState(event)方法需要實(shí)現(xiàn)。mapEventToState(event)顧名思義,就是將用戶點(diǎn)擊 View 時發(fā)出的 event 轉(zhuǎn)化為構(gòu)建 UI 所用的 State。另外,在 StatefulWidget 中使用 bloc 的話,在 widget dispose 時,要調(diào)用 bloc.dispose()方法進(jìn)行釋放。
四、Bloc 的實(shí)踐
這里以常見的獲取列表選擇列表為例子。一個頁面用于展示選中項和跳轉(zhuǎn)到列表,一個頁面用于顯示列表。
- 引入 Redux 的第三方庫
在 pubspec.yaml 文件中引入 flutter_bloc 第三方庫支持 bloc 功能。
# 引入 bloc 第三方庫
flutter_bloc: ^0.9.0
- 使用 Bloc 插件
這一步可有可無,但使用插件會方便開發(fā),不使用的話也沒什么問題。
Bloc 官方提供了 VSCode 和 Android studio 的插件,方便生成 Bloc 框架用到的相關(guān)類。
下文以 Android studio 的插件為例。
比如 list 頁面,該插件會生成相應(yīng)的類
從生成的五個文件中也可以看到,list_bloc 負(fù)責(zé)承載業(yè)務(wù)邏輯,list_page 負(fù)責(zé)編寫 UI 界面,list_event 和 list_state 分別是事件和狀態(tài),其中 list.dart 文件是用于導(dǎo)出前面四個文件的。
具體使用可見
- 使用 BlocProvider 作為根布局
在 main.dart 中,使用 BlocProvider 作為父布局包裹,用于傳遞需要的 bloc。Demo 中包含兩個頁面,一個是展示頁面 ShowPage,一個是列表頁面 ListPage。
上面講到,Bloc 的核心功能在于 Bloc 類,對于展示頁面 ShowPage,會有一個 ShowBloc 繼續(xù)自 Bloc 類。由于展示頁面 ShowPage 會和列表頁面 ListPage 有數(shù)據(jù)的互動,所以這里將 ShowBloc 保存在 BlocProvider 中進(jìn)行傳遞。
@override
Widget build(BuildContext context) {
return BlocProvider(
bloc: _showBloc,
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ShowPage()));
}
- 展示頁面 ShowPage
① ShowEvent
列表的 item 點(diǎn)擊后,需要發(fā)送一個 event 通知其它頁面列表被選中,這里定義一個 SelectShowEvent 作為這種 event 通知。
class SelectShowEvent extends ShowEvent {
String selected;
SelectShowEvent(this.selected);
}
② ShowState
State 用于表示一種界面狀態(tài),即一個 State 就對應(yīng)一個界面。插件在一開始會生成一個默認(rèn)狀態(tài),InitialShowState。我們可以使用 InitialShowState 來代表初始的界面。另外,我們自己定義一種狀態(tài),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 的主要職責(zé)是接收 Event,然后把 Event 轉(zhuǎn)化為對應(yīng)的 State。這里的 ShowBloc 繼續(xù)自 Bloc,需要重寫實(shí)現(xiàn)抽象方法 mapEventToState(event)。在這個方法中,我們判斷傳過來的 event 是不是 SelectShowEvent,是則拿到 SelectShowEvent 中的 selected 變量去構(gòu)建 SelectedShowState。mapEventToState(event)返回的是一個 Stream,我們通過 yield 關(guān)鍵字去返回一個 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 的界面上,我們需要根據(jù) showBloc 中是否有被選中的列表項目去展于頁面,所以這里我們先使用使用BlocProvider.of<ShowBloc>(context)去拿到 showBloc,接著再用 BlocBuilder 根據(jù) showBloc 構(gòu)建界面。使用 BlocBuilder 的好處就是可以讓頁面自動響應(yīng) showBloc 的變化而變化。
var showBloc = BlocProvider.of<ShowBloc>(context);
...
BlocBuilder(
bloc: showBloc,
builder: (context, state) {
if (state is SelectedShowState) {
return Text(state.selected);
}
return Text("");
}),
- 列表頁面 ListPage
① ListEvent
列表頁面,我們一開始需要從網(wǎng)絡(luò)中拉取列表數(shù)據(jù),所以定義一個 FetchListEvent 事件在進(jìn)入頁面時通知 ListBloc 去獲取列表。
@immutable
abstract class ListEvent extends Equatable {
ListEvent([List props = const []]) : super(props);
}
class FetchListEvent extends ListEvent {}
② ListState
InitialListState 是插件默認(rèn)生成的初始狀態(tài),另外定義一個 FetchListState 代表獲取列表完成的狀態(tài)。
@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 中,進(jìn)行從網(wǎng)絡(luò)獲取列表數(shù)據(jù)的業(yè)務(wù)。這里通過一個延時操作摸擬網(wǎng)絡(luò)請求,最后用 yield 返回列表數(shù)據(jù)。
class ListBloc extends Bloc<ListEvent, ListState> {
@override
ListState get initialState => InitialListState();
@override
Stream<ListState> mapEventToState(
ListEvent event,
) async* {
if (event is FetchListEvent) {
// 模擬網(wǎng)絡(luò)請求
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,一個是發(fā)出列表請求的 Event。
@override
void initState() {
bloc = ListBloc(); // 初始化listBloc
bloc.dispatch(FetchListEvent()); // 發(fā)出列表請求事件
super.initState();
}
接下用,便是用 BlocBuilder 去響應(yīng)狀態(tài)。當(dāng) state 是 InitialListState,說明未獲取列表,則顯示 loading 界面,當(dāng) state 是 FetchListState 時,說明已經(jīng)成功獲取列表,顯示列表界面。
body: BlocBuilder(
bloc: bloc,
builder: (context, state) {
// 根據(jù)狀態(tài)顯示界面
if (state is InitialListState) {
// 顯示 loading 界面
return buildLoad();
} else if (state is FetchListState) {
// 顯示列表界面
var list = state.list;
return buildList(list);
}
}));
最后,記得對 bloc 進(jìn)行 dispose()。
@override
void dispose() {
bloc.dispose();
super.dispose();
}
具體代碼可以到 github 查看。
總結(jié)
在 Bloc 的架構(gòu)中,將一個頁面和一個 Bloc 相結(jié)合,由頁面產(chǎn)生 Event,Bloc 根據(jù)業(yè)務(wù)需要將 Event 轉(zhuǎn)化為 State,再把 State 交給頁面中的 BlocBuilder 構(gòu)建 UI。Demo 中只是給出了簡單的狀態(tài)管理,實(shí)際項目中,比如網(wǎng)絡(luò)請求,有請求中、請求成功、請求失敗的多種狀態(tài),可以做適當(dāng)封裝使 Bloc 更加易用。相比于 Redux,Bloc 不需要將所有狀態(tài)集中管理,這樣對于不同模塊的頁面易于拆分,對于代碼量比較大的客戶端而言,Bloc 的架構(gòu)會相對比較友好。