用過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);
因為我中文不好,這邊拆一下,
至於為什麼分冷熱?之後會再開一篇講flow、stateFlow、sharedFlow和channel的差異,這裡一樣先專注講flow是什麼,那做為一個正常的新手,對flow概念應該還是,我聽你講了很多,但我還是沒聽懂
這其實很正常,當初我直接看文檔,也是看不懂,有種被忽悠的感覺,一個字一個字都懂,放在一起就不懂了,在看圖之前,我想用一個例子解釋一下flow,進口奶茶
原本的連結,如果有校稿者,這邊以base64的方式崁入圖片是可以的嗎?
我也沒想到我想了好幾周的最好例子會是這個
以這個範例來說,男方是consumer, 女方是intermediary,奶茶是producer,奶茶並不關心是誰喝他,也不在意最後男方喝到什麼東西,奶茶只負責給奶茶
換個角度,男方其實也不在意奶茶給了甚麼,看他的眼神和女方深情對望就知道,女方給甚麼他就喝什麼
最後來到女方,作為intermediary,他可以決定要讓男方就是喝到奶茶,還是混她的口水進去,或是把吸到嘴裡的奶茶只分一半給男方,當然也有可能把剛剛吃飯的渣渣給(等等有點噁心了)
接著上圖
現在看這張圖,兩個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不能這樣做
對,就是這樣,第一次看到時有些許混淆視正常的,但請先記住基本原則,flow不僅有自己的操作符可以用來控制diapatcher,他也不需要suspend修式符,最重要的是emit被限制在只有flow builder可以用,所以上面講的問題,其實都被處理好了
之後會再寫一篇範例,留個地方放連結,到時轉這邊看就會懂了
這邊都用kotlinConf 2019的範例,因為他們連圖都做好了,我可以直接講
這是一個最基本的flow,實際上的執行是先建立Flow,接著每emit一次,都能立刻拿到一個值
記得我們上面的flow流程圖嗎?他的中間層可以對資料進行操作
以list來說,必須一次處理完整個list,再把整個處理好的list交出去
但flow就不同,她是分別對每個值做處理,所以A處理好,就先給A',B處理好,就先給B'
那現在,我們來更深入了解一下flow的執行,在producer是由上而下執行的,delay只會trigger一次,而在consumer,則是每收到一個值,就會執行block裡面的函數一次,所以她收到兩個值,就delay兩次
恩這很flow
以這兩個例子來看,list的function會立刻執行,但flow的function卻不會執行,除非你呼叫collect() 或是toList()(男方說我要喝奶茶,或我要喝淡奶茶),producer和intermediary才會運作
然而,长时间运行的消耗 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操作符一起講
熟悉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