[Android] Coroutines Hot Flow:希望我能早點知道的小事(3/3)

Connie Lin
7 min readMay 17, 2022

這是 Coroutines Hot Flow 系列文的最後一篇,記錄了我希望自己一開始在使用 Hot Flow 時,就能知道的事 — 也就是當時碰到的盲點與地雷們。先前已經寫了兩篇文章,第一篇介紹了 cold flow 與 hot flow 的區別,以及 hot flow 老大 — SharedFlow 的基本特性;第二篇介紹 hot flow 小弟 — StateFlow,以及兩種 hot flow 的基本實作方式。如果你還沒看過,可以先去瞧瞧。

當初在學習 hot flow,正也是用於新功能實作的時候。看完網路上的介紹文件,眼睛說懂了,寫下去卻不是那麼一回事。因為有時間上的壓力,學習起來並不是很全面,也是因為這些盲點而遇到了許多問題。得在有限的時間內見招拆招實在是很痛苦,雖然我的雷不一定是你的雷,但如果能有所幫助,我所浪費的那些時間也能夠有所欣慰了吧(?

那些我希望能早點知道的事 🥺

Cold flow 適合動態產生,hot flow 適合靜態宣告

第一篇文章分別為兩種流給了比喻:cold flow 是私人管線,hot flow 是公共財。cold flow 透過 .collect() 來驅動,flow { } block 內的事情做完即結束,單一 cold flow 只能被 .collect() 一次 — 因此需要動態產生,讓每個需要資料的人各自去產生 instance 並做訂閱。

Hot flow 則是被創建出來之後,只要還是 active 狀態並且還有 reference 就會保存在記憶體中。為了讓多方可以取得同一個對象並作訂閱,很自然地就會用 reference 來管理。不過因為 cold flow 也能透過 Flow.sharedIn() 以及 Flow.stateIn() 來轉成 hot flow,那就要注意不能從 function call 中做轉型,而產生一堆 hot flow 了。最好是透過下列三種方式產生:

val hotFlow1 = MutableSharedFlow<T>()
val hotFlow2 = coldFlow.sharedIn(viewModelScope)
fun coldFlow: Flow<T>() = flowOf {}

當然,以上是理想世界的情境,人生就是有很多 BUT。真的需要動態產生 hot flow 的可能性不是沒有(例如不同 user 需要傳入不同的 id?),那千萬要記得管理 reference 呀。

不能在同一個 CoroutineScope 中 collect 多個 hot flow

應該要建立新的 coroutine scope 來 collect 多個 hot flow。collect() 是個 suspend function,直到 flow 結束後才會 resume。還記得第一篇文章中有提到 “Hot flow never ends” 嗎? HotFlow.collect() 是不會結束的,因此它所在的 coroutine scope 會被 suspended,不會向下再執行下去。在下面的例子中, functionA() 是無法被執行的,hotFlowB 在另一個 scope 做 collect,就不會被影響到。

viewModelScope.launch {
hotFlowA.collect { // do something }
functionA() // 不會執行到
}
viewModelScope.launch {
hotFlowB.collect { // do something }
}

要建立新的 data instance 來更新 StateFlow

第二篇文章提到,StateFlow 預設做了 distinctUntilChanged(),來比對舊值與新值的差異。如果新舊資料是同一個實體,即便有某些參數改變了,也會被過濾掉而不會通知訂閱者。因此要建立新的 instance 來更新 StateFlow。

data class Message(val text: String)
_stateFlow.value = Message("Hello")
// 可以用下面這兩種方式更新資料
_stateFlow.value = Message("Bye")
_stateFlow.update { it.copy("Bye") } // 好處是可以拿到舊的 cache 作修改

stateFlow 有 conflated 的特性(容量為 0 且會拋棄舊值),所以直接同步更新資料即可(上面兩種做法都是同步的),不需要使用異步的 emit 。

到底什麼時候要用 cold flow、什麼時候要用 hot flow 啊啊啊?

收尾前,來做個最後的總結吧。Cold flow 的每個訂閱者都會獨自完整走過一次取得資料的流程,且能提供什麼資料的行為已經定義在 block 內了,無法外部改變。像是 API request 的回傳值通常就是使用 cold flow 來包裝,需要資料的人去 request 即可。

Hot flow 則是共用的,而能拿到 mutable reference 的人還能把資料丟進去,hot flow 就是通知事件、狀態的容器,生產方在某個時機點把資料丟進去,提供訂閱方使用。用 Observer pattern 實作的 listener 機制,就很適合用 hot flow 來改寫。

如果一定需要一筆資料,且只關心最新的資料,那就選用 StateFlow,簡單明瞭。但如果你的情境比較多元,就用 SharedFlow 來定義資料分享的情境,例如要不要為新的訂閱者留 cache?容量機制如何運作?容量爆掉如何處理等。

因應不同的資料邏輯、商業邏輯,選用最適合的 Flow 吧。

Reference

在學習時,主要是透過這兩份文件:Android Developer: StateFlow and SharedFlowReactive Streams on Kotlin: SharedFlow and StateFlow 建立比較全貌的認識。後者還蠻推薦有時間的話好好看一下的,還有一些 GIF 動畫幫助消化理解。

前面在講針對 hot flow 作 reference 管理的段落,我提到了可以透過 shareInstateIn來將 cold flow 轉為 hot flow,不過鑒於篇幅與主軸,沒有好好介紹他們。如果有興趣,除了上述文章之外,也可以透過這篇文章來好好認識他們:Things to know about Flow’s shareIn and stateIn operators

Murmuring..

在草稿匣躺了半年,終於完成這篇文章了。算是第一次寫比較長篇的技術分享文,回過頭已經修了不少盲點、語病,發現好像還是摻雜了許多自己的意識在裡面,希望讀起來不會太辛苦 😂

任何不足之處與錯誤都歡迎指正,也可以留下意見或是想法,感謝你的閱讀。

--

--