iT邦幫忙

2021 iThome 鐵人賽

DAY 17
0
Mobile Development

解鎖kotlin coroutine的各種姿勢-新手篇系列 第 17

day17 不懂kotlin flow資料流? 那喝杯進口奶茶吧

  • 分享至 

  • xImage
  •  

用過Rx或reactive stream的大大,應該會很好理解flow,從設計概念來講,flow也屬於react stream,如果有從那邊轉過來的人,可以先看這篇由jetBrain Project Lead寫的文章,談到了Reactive Streams and Kotlin Flows

對新手而言,要講flow就得先講講英文,去看劍橋字典flow大概是持續、不斷的流動的意思,再看看文檔
A suspending function asynchronously returns a single value, but how can we return multiple asynchronously computed values? This is where Kotlin Flows come in.
對,開頭就講到suspend只會返回一個值。但為了能夠返回多的非同步的result,就要用flow

另一句文檔的描述是,透過flow建構器 建構 帶 全面操作符 的冷的非同步流
Flow — cold asynchronous stream with flow builder and comprehensive operator set (filter, map, etc);

因為我中文不好,這邊拆一下,

  1. 他必須透過flow builder建構
  2. 有很多操作符可以用
  3. 被定義為冷的非同步流

至於為什麼分冷熱?之後會再開一篇講flow、stateFlow、sharedFlow和channel的差異,這裡一樣先專注講flow是什麼,那做為一個正常的新手,對flow概念應該還是,我聽你講了很多,但我還是沒聽懂

這其實很正常,當初我直接看文檔,也是看不懂,有種被忽悠的感覺,一個字一個字都懂,放在一起就不懂了,在看圖之前,我想用一個例子解釋一下flow,進口奶茶

原本的連結,如果有校稿者,這邊以base64的方式崁入圖片是可以的嗎?

我也沒想到我想了好幾周的最好例子會是這個
以這個範例來說,男方是consumer, 女方是intermediary,奶茶是producer,奶茶並不關心是誰喝他,也不在意最後男方喝到什麼東西,奶茶只負責給奶茶

換個角度,男方其實也不在意奶茶給了甚麼,看他的眼神和女方深情對望就知道,女方給甚麼他就喝什麼

最後來到女方,作為intermediary,他可以決定要讓男方就是喝到奶茶,還是混她的口水進去,或是把吸到嘴裡的奶茶只分一半給男方,當然也有可能把剛剛吃飯的渣渣給(等等有點噁心了)

接著上圖
flow
現在看這張圖,兩個flow就是吸管,producer的圓形就是飲料杯口,intermediary的方形比喻成女方的嘴巴,consumer的倒D型,當成男方的嘴巴,還有飲料不要吐回去,太噁心了,看到這裡,你已經有基本概念了

接著來認真介紹,Flow可分為三個部分,producer、intermediary、consumer,而更重要的是,作為數據流,他可以按順序發出多個值,他是透過異步處理的數據序列,ex: Flow<dataType>

給個範例,換一個例子

viewModelScope.launch {
    (0..5).asFlow()//producer,奶茶
        .map{//intermediary,女方
            it * 2
        }
        .collect {//consumer,男方
            Timber.d(it.toString())
        }
}

要注意flow是cold stream,意即使用到collect時才會被執行

補充一個重要的點,使用 flow 构建器时,提供方不能提供来自不同 CoroutineContext 的 emit 值。因此,请勿通过创建新协程或使用 withContext 代码块,在不同 CoroutineContext 中调用 emit。在这些情况下,您可使用其他数据流构建器,例如 callbackFlow ----------by.官方文件

疑等等,前面講過在viewModel用viewModelScope開啟coroutine,再調用repository的suspend,透過withContext去切換會更好維護,現在你告訴我flow不能這樣做
meme

對,就是這樣,第一次看到時有些許混淆視正常的,但請先記住基本原則,flow不僅有自己的操作符可以用來控制diapatcher,他也不需要suspend修式符,最重要的是emit被限制在只有flow builder可以用,所以上面講的問題,其實都被處理好了

之後會再寫一篇範例,留個地方放連結,到時轉這邊看就會懂了

那實際執行上,到底有甚麼不同

這邊都用kotlinConf 2019的範例,因為他們連圖都做好了,我可以直接講

執行

這是一個最基本的flow,實際上的執行是先建立Flow,接著每emit一次,都能立刻拿到一個值

intermediary

記得我們上面的flow流程圖嗎?他的中間層可以對資料進行操作

以list來說,必須一次處理完整個list,再把整個處理好的list交出去
但flow就不同,她是分別對每個值做處理,所以A處理好,就先給A',B處理好,就先給B'

那現在,我們來更深入了解一下flow的執行,在producer是由上而下執行的,delay只會trigger一次,而在consumer,則是每收到一個值,就會執行block裡面的函數一次,所以她收到兩個值,就delay兩次

恩這很flow

why call cold


以這兩個例子來看,list的function會立刻執行,但flow的function卻不會執行,除非你呼叫collect() 或是toList()(男方說我要喝奶茶,或我要喝淡奶茶),producer和intermediary才會運作

kotlinConf 2019
flow
flow

用了withContext會怎樣

然而,长时间运行的消耗 CPU 的代码也许需要在 Dispatchers.Default 上下文中执行,并且更新 UI 的代码也许需要在 Dispatchers.Main 中执行。通常,withContext 用于在 Kotlin 协程中改变代码的上下文,但是 flow {...} 构建器中的代码必须遵循上下文保存属性,并且不允许从其他上下文中发射(emit)。

如果用了就會~

Exception in thread "main" java.lang.IllegalStateException: Flow invariant is violated:
        Flow was collected in [CoroutineId(1), "coroutine#1":BlockingCoroutine{Active}@5511c7f8, BlockingEventLoop@2eac3323],
        but emission happened in [CoroutineId(1), "coroutine#1":DispatchedCoroutine{Active}@2dae0000, Dispatchers.Default].
        Please refer to 'flow' documentation or use 'flowOn' instead
    at ...

對,他會報錯,這種情況要怎麼半?可以用flow on,我明天連我覺得重要的flow操作符一起講

handle Backpressure

熟悉react stream的開發者,會注意到flow她並沒有顯式的back pressure處理機制,難道flow沒有這個問題?
back pressure - 數據流消費者consumer跟不上數據流生產者producer的速度,所以發一個訊號要求減緩

詳細介紹看連結
react stream and flow
node.js backpressure

其實問題還是有,只是kotlin coroutine的開發者,用更優雅的方式解決了,還記得suspend 的功用吧,suspend和resume,當consumer工作量太多,造成back pressure時,可以先把producer掛起,之後再resume

jetbrain - Simple design of Kotlin Flow

連結

flow操作符
pagin3 with flow
mvvm with flow

必看

jetbrain


影片
影片
video

選看

jetBrain Project Lead寫的文章


上一篇
day16 coroutine job 的那些狀態,job state
下一篇
day18 kotlin - flow基本操作
系列文
解鎖kotlin coroutine的各種姿勢-新手篇30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言