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í)踐

這里以常見的獲取列表選擇列表為例子叮盘。一個頁面用于展示選中項(xiàng)和跳轉(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 中是否有被選中的列表項(xiàng)目去展于頁面勺卢,所以這里我們先使用使用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í)際項(xiàng)目中亚隙,比如網(wǎng)絡(luò)請求,有請求中违崇、請求成功阿弃、請求失敗的多種狀態(tài),可以做適當(dāng)封裝使 Bloc 更加易用羞延。相比于 Redux渣淳,Bloc 不需要將所有狀態(tài)集中管理,這樣對于不同模塊的頁面易于拆分伴箩,對于代碼量比較大的客戶端而言水由,Bloc 的架構(gòu)會相對比較友好。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赛蔫,一起剝皮案震驚了整個濱河市砂客,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌呵恢,老刑警劉巖鞠值,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異渗钉,居然都是意外死亡彤恶,警方通過查閱死者的電腦和手機(jī)钞钙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來声离,“玉大人芒炼,你說我怎么就攤上這事∈趸玻” “怎么了本刽?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長赠涮。 經(jīng)常有香客問我子寓,道長,這世上最難降的妖魔是什么笋除? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任斜友,我火速辦了婚禮,結(jié)果婚禮上垃它,老公的妹妹穿的比我還像新娘鲜屏。我一直安慰自己,他們只是感情好国拇,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布墙歪。 她就那樣靜靜地躺著,像睡著了一般贝奇。 火紅的嫁衣襯著肌膚如雪虹菲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天掉瞳,我揣著相機(jī)與錄音毕源,去河邊找鬼。 笑死陕习,一個胖子當(dāng)著我的面吹牛霎褐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播该镣,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼冻璃,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了损合?” 一聲冷哼從身側(cè)響起省艳,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嫁审,沒想到半個月后跋炕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡律适,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年辐烂,在試婚紗的時候發(fā)現(xiàn)自己被綠了遏插。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡纠修,死狀恐怖胳嘲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情扣草,我是刑警寧澤了牛,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站德召,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏汽纤。R本人自食惡果不足惜上岗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蕴坪。 院中可真熱鬧肴掷,春花似錦受裹、人聲如沸脯厨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽虐秋。三九已至媒区,卻和暖如春纱控,著一層夾襖步出監(jiān)牢的瞬間期贫,已是汗流浹背梳星。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工赞赖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人冤灾。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓前域,卻偏偏與公主長得像,于是被迫代替她去往敵國和親韵吨。 傳聞我的和親對象是個殘疾皇子匿垄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容