Flutter狀態(tài)管理學(xué)習(xí)手冊(三)——Bloc

一、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)行交互。

image

由此可見,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)到列表,一個頁面用于顯示列表。

image
  1. 引入 Redux 的第三方庫

pubspec.yaml 文件中引入 flutter_bloc 第三方庫支持 bloc 功能。

  # 引入 bloc 第三方庫
  flutter_bloc: ^0.9.0
  1. 使用 Bloc 插件

這一步可有可無,但使用插件會方便開發(fā),不使用的話也沒什么問題。

Bloc 官方提供了 VSCode 和 Android studio 的插件,方便生成 Bloc 框架用到的相關(guān)類。
下文以 Android studio 的插件為例。

比如 list 頁面,該插件會生成相應(yīng)的類

image

從生成的五個文件中也可以看到,list_bloc 負(fù)責(zé)承載業(yè)務(wù)邏輯,list_page 負(fù)責(zé)編寫 UI 界面,list_eventlist_state 分別是事件和狀態(tài),其中 list.dart 文件是用于導(dǎo)出前面四個文件的。

具體使用可見

Android studio 的 Bloc 插件

VSCode 的 Bloc 插件

  1. 使用 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()));
  }
  1. 展示頁面 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("");
    }),
  1. 列表頁面 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)會相對比較友好。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容