前言
本篇記錄了 Flutter 眾多概念中較為複雜的一個領域,那就是 狀態管理 State Management 的部分。然而之所以複雜的原因就是它並沒有一套實作的標準。例如 Google 自行發表的 BLoC Pattern 、 Rx 系列以及 Flutter 原生的狀態管理,都會在這篇一一做出差異比較與使用時機。
Flutter 完整開發架構
Stream 初識
比喻
巧克力工廠的特色
- 工廠收到一個 ‘order’, 做一些處理後, 將蛋糕從工廠的另外一端吐出
- 當有第一個訂單以前工廠是尚未建立的
- 工廠花大多數的時間在等訂單進來
- 必須有人(order taker :) 等待新的訂單. 這是進入工廠的入口
- 當蛋糕做好的時候必須有人來拿
來一段程式碼 :
1 | import 'dart:async'; |
I cant bake your type!!!
什麼是 StreamController
StreamController
有其中兩個屬性sink
和stream
當
StreamController
被創建時, 會自動生成該兩個物件並指派sink
有能力將data傳入stream
裡面不能直接將資料傳給
stream
, 需要透過sink
(order taker)
總覽
- Order Taker :
StreamController
的sink.add
函數- 功能 : 將加入的 event 提供給 stream 去處理
- Order Inspector :
stream
的map函數- 功能 : 檢視每一個進來
stream
的 event , 並加以處理(抽取或轉換)然後返回
- 功能 : 檢視每一個進來
Baker :
StreamTransformer
- 功能 : 處理每一個進來的處理過的資料, 然後塞回
stream
- 功能 : 處理每一個進來的處理過的資料, 然後塞回
Pickup Office :
listen
函式
map function 和 StreamTransformer 的差別
相同 :
- 都是用來處理 stream 裡面的 event
不同 :
map function
: 一對一的data進出StreamTransformer
: 一對多的資料進出(輸出多個資料)- 更加彈性
- e.g. 可以呼叫多次
sink.add
, 將 event data 加入到 stream
Why Stream ?
緣起
Stream
的處理方式看似複雜, 但在 flutter 或 dart 裡面經常會提供觸發的事件資料(透過 sink 放入 stream 裡面), 而無需自己製作. 我們只需創建另一個 stream
用
來看一段程式碼 :
1 | import 'dart:html'; |
Stream ‘take’ and ‘where’ function ?
- take(int) - 當事件進入到
stream
的次數高達設定的數字時,stream
會被終止且發出結束事件(onDone) - where((){})
- 事實上他就是過濾器, 專門過濾不符合匿名韓式裡寫的條件.
- true : 保留該 event
- false : 忽略該 event
- 創建新的
stream
並將 event 塞入
- 事實上他就是過濾器, 專門過濾不符合匿名韓式裡寫的條件.
猜字遊戲 : 當猜到第四次仍沒有猜中就不可以再猜了XXD
1 | import 'dart:html'; |
另外一個例子更可以展現 :
1 | import 'dart:html'; |
BLOC’s vs Stateful widgets
前情提要
場景 : BLOC 對於資訊共享於不同 widget 之間做出很好的處理
- 整個 app 的 widget 結構
- 當 LoginScreen 和 UserPreferences 都要共享下圖紅色圈起來的資料, 如果單純使用 stateful widgets 來寫會很複雜. 但是 bloc 可以輕易的解決 widget 和 widget 之間共享資料的難度
BLOC : Business logic component
用來儲存所有資料(data)和狀態(state)在一個實體當中
bloc component
並不需要app裡頭哪一個特定的widget做綁定與
widget
是分離的, 並不會在widget tree
的結構當中
streams 在 bloc 裡頭的意義
下圖是bloc和stream一起運作的概念 :
bloc
可以看作是物流中心, 用來管理 app 所有數據和狀態的stream
可以看作是各式各樣需要物流的產品, 在哪裡得到資訊? 將資訊送往哪裡?
下圖是app的結構 :
Bloc 的應用
TextField
和 bloc
互相作用 :
bloc
擁有兩個屬性 :
- Email StreamController
- Password StreamController
對於如何應用 bloc
到 app 裏頭, 在 state management
尚未有一個統一的說法 :
1. Single Global Instance : 適合用於在較小的項目當中
(中間)
app
的widget
結構(右邊)
bloc.dart
檔按裡面有一個Bloc
類以及一個bloc
實體在整個 app 中只有一份 (
final
)bloc
實體, 所以裡面所有的變數和狀態是共享的在所有使用到的widget
當中開發上會非常方便且簡易但是會難以控制
2. Scoped Instance : 適合在更大型且更複雜的項目底下開發
- 不把
bloc
的實體放在bloc.dart
檔案下來當作全域變數 - 下面的 CustomWidget1~3 是任意的
- 將 bloc 放在需要的 widget 裡面, 而其子widget都可以存取到該
bloc
- 不需將 bloc 實體暴露給整個 app 做使用
- 這樣開發明顯會複雜許多但會得到更好的控制
StreamBuilder - consuming stream
定義 : 是一個可以將使用者定義的物件的串流轉成widget的widget
兩個參數 :
- stream : 要監測事件的 stream
- builder : 將串流裡的元素轉換成 widget (builder function)
- 觸發條件為當 stream 改變
StreamBuilder 概述
- Flutter 所提供的一個 widget
- 將需要的
Stream
和StreamBuilder
做綁定 - 當
StreamBuiler
偵測到一個新的事件從stream
進來, 就調用builder function
(返回新的widget )並且重新渲染
通常應用來更新(渲染) widget 取代原有使用 statefulWidget 的 setState()
context
概念
每一個widget都有自己獨立的
context
context
是一種標示符, 將該 widget 放置在正確的 widget 階層中(如上圖)每一個 widget 都可以在 widget 階層中向上尋找任意的 widget
每一個 widget 都知道自己的
context
以及以上的context
context
就像鏈結一樣, 該context
知道他的上級而上級的context
又知道他的上級…
來看一段程式碼 :
1 | import 'package:flutter/material.dart'; |
程式碼概述 :
在該 widget(
Provider
) 底下 widget tree 的任意 widget , 都可以藉由該of(BuilderContext)
將自己的context
傳入該
context
有一個inheritFromWidgetOfExactType(Type typeTarget)
方法, 用來向上尋找 widget 階層中類型為typeTarget
的 widget.找到後會返回一個
InheritedWidget
類型的 widget(物件) 並將他向下轉型為typeTarget
How to merge two stream ?
Case : 監聽兩個 stream
當達到一定的條件作出適當的響應
solution to merge stream
有三種解決的辦法 :
下面有對RxDart有做詳細的解說
RxDart
Rx家族介紹
Rx 全名叫做 ReactiveX
有多種不同的版本套件
依據不同的程式語言去量身定做
架構在不同版本間基本上都是一樣
不同的術語在 RxDart
Stream
會變成Observable
- A wrapper class that extends to Stream
StreamController
會變成Subject
- A wrapper class that extends to StreamController
知識補給站BehaviorSubject
是 StreamController
的一種.
默認是 Broadcast controller , 所以他的
stream
是可以被監聽多次的會紀錄最新加入
sink
的 event , 以便之後可以做存去使用.在
Stream
的處理方面加入許多延伸的功能
學習連結
回到正題 => 我們將用RxDart的 combineLatest2()
方法讓兩個 stream
做出合併處理
combineLatest2()
的機制圖解
- 觀念圖示連結
Observale
直到要合併的兩個stream
都各至少發出一個 event 才會發出 event