誰能取代Android的LiveData- StateFlow or SharedFlow?

點(diǎn)擊上方藍(lán)字關(guān)注我,知識(shí)會(huì)給你力量
這個(gè)系列我做了協(xié)程和Flow開發(fā)者的一系列文章的翻譯,旨在了解當(dāng)前協(xié)程、Flow、LiveData這樣設(shè)計(jì)的原因,從設(shè)計(jì)者的角度,發(fā)現(xiàn)他們的問題,以及如何解決這些問題,pls enjoy it。
Kotlin Coroutines最近引入了兩種Flow類型,即SharedFlow和StateFlow,Android的社區(qū)開始思考用這些新類型中的一種或兩種來替代LiveData的可能性和意義。這方面的兩個(gè)主要原因是:
- LiveData與UI緊密相連
- LiveData與Android平臺(tái)緊密相連
我們可以從這兩個(gè)事實(shí)中得出結(jié)論,從Clean Architecture的角度來看,雖然LiveData在表現(xiàn)層中運(yùn)行良好,但它并不適合領(lǐng)域?qū)樱驗(yàn)轭I(lǐng)域?qū)幼詈檬仟?dú)立于平臺(tái)的(指純Kotlin/Java模塊);而且它也不太適合數(shù)據(jù)層(Repositories實(shí)現(xiàn)和數(shù)據(jù)源),因?yàn)槲覀兺ǔ?yīng)該將數(shù)據(jù)訪問工作交給工作線程。
img
不過,我們不能只是用純Flow來替代LiveData。在所有應(yīng)用層上使用純Flow作為LiveData的替代品的主要問題是:
- Flow是無狀態(tài)的(不能通過.value訪問)
- Flow是聲明性的(冷的):一個(gè)Flow構(gòu)建器僅僅描述了Flow是什么,并且只有在Collect時(shí)才會(huì)被物化。然而,一個(gè)新的Flow是為每個(gè)收集器有效地運(yùn)行(物化)的,這意味著上游(昂貴的)數(shù)據(jù)庫訪問是為每個(gè)收集器重復(fù)地運(yùn)行。
- Flow本身并不了解Android的生命周期,也不提供Android生命周期狀態(tài)變化時(shí)收集器的自動(dòng)暫停和恢復(fù)。
?這些都不能被看作是純粹的Flow的內(nèi)在缺陷:這些只是使它不能很好地替代LiveData的特點(diǎn),但在其他情況下卻可以很強(qiáng)大。對于(3),我們已經(jīng)可以使用LifecycleCoroutineScope的擴(kuò)展,如 launchWhenStarted來啟動(dòng)coroutine來收集我們的Flow--這些收集器將自動(dòng)暫停,并與組件的Lifecycle同步恢復(fù)。
?
注意:在本文中,我們把Collect和觀察作為同義概念。Collect是Kotlin Flow的首選術(shù)語(我們Collect一個(gè)Flow),觀察是Android的LiveData的首選術(shù)語(我們觀察一個(gè)LiveData)。
但是,(1)--獲取當(dāng)前狀態(tài),以及(2)--對于N>=1個(gè)收集器只物化一次,對于0個(gè)收集器不物化,又是怎么回事?
現(xiàn)在,SharedFlow和StateFlow為這兩個(gè)問題提供了一個(gè)解決方案。
A practical example
讓我們用一個(gè)實(shí)際的用例來說明。我們的用例是獲取附近的位置。我們假設(shè)Firebase實(shí)時(shí)數(shù)據(jù)庫和GeoFire庫一起使用,它允許查詢附近的地點(diǎn)。
Using LiveData end-to-end
img
讓我們首先展示一下從數(shù)據(jù)源一直到視圖的LiveData的使用。數(shù)據(jù)源負(fù)責(zé)通過GeoQuery連接到Firebase實(shí)時(shí)數(shù)據(jù)庫。當(dāng)我們收到onGeoQueryReady()或onGeoQueryError()時(shí),我們用自上次onGeoQueryReady()以來進(jìn)入、退出或移動(dòng)的地點(diǎn)的總數(shù)來更新LiveData值。
@Singleton
class NearbyUsersDataSource @Inject constructor() {
// Ideally, those should be constructor-injected.
val geoFire = GeoFire(FirebaseDatabase.getInstance().getReference("geofire"))
val geoLocation = GeoLocation(0.0, 0.0)
val radius = 100.0
val geoQuery = geoFire.queryAtLocation(geoLocation, radius)
// Listener for receiving GeoLocations
val listener: GeoQueryEventListener = object : GeoQueryEventListener {
val map = mutableMapOf<Key, GeoLocation>()
override fun onKeyEntered(key: String, location: GeoLocation) {
map[key] = location
}
override fun onKeyExited(key: String) {
map.remove(key)
}
override fun onKeyMoved(key: String, location: GeoLocation) {
map[key] = location
}
override fun onGeoQueryReady() {
_locations.value = State.Ready(map.toMap())
}
override fun onGeoQueryError(e: DatabaseError) {
_locations.value = State.Error(map.toMap(), e.toException())
}
}
// Listen for changes only while observed
private val _locations = object : MutableLiveData<State>() {
override fun onActive() {
geoQuery.addGeoQueryEventListener(listener)
}
override fun onInactive() {
geoQuery.removeGeoQueryEventListener(listener)
}
}
// Expose read-only LiveData
val locations: LiveData<State> by this::_locations
sealed class State(open val value: Map<Key, GeoLocation>) {
data class Ready(
override val value: Map<Key, GeoLocation>
) : State(value)
data class Error(
override val value: Map<Key, GeoLocation>,
val exception: Exception
) : State(value)
}
}
我們的Repository、ViewModel和Activity就應(yīng)該像這樣簡單。
@Singleton
class NearbyUsersRepository @Inject constructor(
nearbyUsersDataSource: NearbyUsersDataSource
) {
val locations get() = nearbyUsersDataSource.locations
}
class NearbyUsersViewModel @ViewModelInject constructor(
nearbyUsersRepository: NearbyUsersRepository
) : ViewModel() {
val locations get() = nearbyUsersRepository.locations
}
@AndroidEntryPoint
class NearbyUsersActivity : AppCompatActivity() {
private val viewModel: NearbyUsersViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
viewModel.locations.observe(this) { state: State ->
// Update views with the data.
}
}
}
這種方法可能很好用,直到你決定讓包含存儲(chǔ)庫接口的域?qū)营?dú)立于平臺(tái)(因?yàn)樗鼞?yīng)該是)。另外,一旦你需要將工作卸載到數(shù)據(jù)源的工作線程上,你會(huì)發(fā)現(xiàn)使用LiveData并不容易,也沒有成文的方法。
Using flows on Data Source and Repository
img
讓我們把我們的數(shù)據(jù)源轉(zhuǎn)換為使用Flow。我們有一個(gè)流構(gòu)建器,callbackFlow {},它將一個(gè)回調(diào)轉(zhuǎn)換為一個(gè)冷流。當(dāng)這個(gè)Flow被收集時(shí),它運(yùn)行傳遞給flow builder的代碼塊,添加GeoQuery監(jiān)聽器并到達(dá)awaitClose {},在那里它暫停運(yùn)行,直到Flow被關(guān)閉(也就是說,直到?jīng)]有人在收集,或者直到它因任何未捕獲的異常而被取消)。當(dāng)關(guān)閉時(shí),它就會(huì)刪除監(jiān)聽器,并且流量被取消。
@Singleton
class NearbyUsersDataSource @Inject constructor() {
// Ideally, those should be constructor-injected.
val geoFire = GeoFire(FirebaseDatabase.getInstance().getReference("geofire"))
val geoLocation = GeoLocation(0.0, 0.0)
val radius = 100.0
val geoQuery = geoFire.queryAtLocation(geoLocation, radius)
private fun GeoQuery.asFlow() = callbackFlow {
val listener: GeoQueryEventListener = object : GeoQueryEventListener {
val map = mutableMapOf<Key, GeoLocation>()
override fun onKeyEntered(key: String, location: GeoLocation) {
map[key] = location
}
override fun onKeyExited(key: String) {
map.remove(key)
}
override fun onKeyMoved(key: String, location: GeoLocation) {
map[key] = location
}
override fun onGeoQueryReady() {
emit(State.Ready(map.toMap()))
}
override fun onGeoQueryError(e: DatabaseError) {
emit(State.Error(map.toMap(), e.toException()))
}
}
addGeoQueryEventListener(listener)
awaitClose { removeGeoQueryEventListener(listener) }
}
val locations: Flow<State> = geoQuery.asFlow()
sealed class State(open val value: Map<Key, GeoLocation>) {
data class Ready(
override val value: Map<Key, GeoLocation>
) : State(value)
data class Error(
override val value: Map<Key, GeoLocation>,
val exception: Exception
) : State(value)
}
}
我們的Repository和ViewModel沒有任何變化,但是我們的Activity現(xiàn)在接收的是Flow而不是LiveData,所以它需要進(jìn)行調(diào)整:不是觀察LiveData,而是收集Flow。
@AndroidEntryPoint
class NearbyUsersActivity : AppCompatActivity() {
private val viewModel: NearbyUsersViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
lifecycleScope.launchWhenStarted {
viewModel.locations.collect {
// Update views with the data.
}
}
}
}
我們使用 launchWhenStarted {} 來收集Flow,所以只有當(dāng)Activity到達(dá)onStart() 生命周期狀態(tài)時(shí),coroutine才會(huì)自動(dòng)啟動(dòng),而當(dāng)它到達(dá)onStop() 生命周期狀態(tài)時(shí)則會(huì)自動(dòng)暫停。這類似于LiveData給我們提供的自動(dòng)處理Lifecycle的方式。
注意:你可能會(huì)選擇在你的表現(xiàn)層(活動(dòng))中繼續(xù)使用LiveData。在這種情況下,你可以通過使用Flow.asLiveData()擴(kuò)展函數(shù)在ViewModel中輕松地從Flow轉(zhuǎn)換為LiveData。這個(gè)決定會(huì)帶來一些后果,我們將在下一節(jié)課中討論,我們將展示使用SharedFlow和StateFlow端到端的通用性更強(qiáng),可能更適合你的架構(gòu)。
img
「What are the issues with using Flow in the View Layer?」
這種方法的第一個(gè)問題是對生命周期的處理,LiveData會(huì)自動(dòng)為我們處理。我們在上面的例子中通過使用 launchWhenStarted {}實(shí)現(xiàn)了類似的行為。
但還有一個(gè)問題:因?yàn)镕low是聲明性的,并且只在收集時(shí)運(yùn)行(物化),如果我們有多個(gè)收集器,那么每個(gè)收集器都會(huì)運(yùn)行一個(gè)新的Flow,彼此之間完全獨(dú)立。根據(jù)所做的操作,如數(shù)據(jù)庫或網(wǎng)絡(luò)操作,這可能是非常無效的。如果我們期望操作只做一次,以保證正確性,它甚至可能導(dǎo)致錯(cuò)誤的狀態(tài)。在我們的實(shí)際例子中,我們將為每個(gè)采集器添加一個(gè)新的GeoQuery監(jiān)聽器--可能不是一個(gè)關(guān)鍵問題,但肯定是在浪費(fèi)內(nèi)存和CPU周期。
?注意:如果你通過在ViewModel中使用Flow.asLiveData()將你的Repository Flow轉(zhuǎn)換為LiveData,LiveData就會(huì)成為Flow的唯一收集器,無論表現(xiàn)層中有多少個(gè)觀察者,都只有一個(gè)Flow被收集。然而,為了使這種架構(gòu)順利運(yùn)行,你需要保證你的每個(gè)其他組件都從ViewModel訪問你的LiveData,而不是直接從Repository訪問Flow。這可能會(huì)證明自己是一個(gè)挑戰(zhàn),這取決于你的應(yīng)用程序的解耦程度:所有需要存儲(chǔ)庫的組件,如交互器(用例)的實(shí)現(xiàn),現(xiàn)在將依賴于活動(dòng)實(shí)例來獲得ViewModel實(shí)例,這些組件的范圍需要相應(yīng)地限制。
我們只想要一個(gè)GeoQuery監(jiān)聽器,不管我們在視圖層有多少個(gè)采集器。我們可以通過在所有采集器之間共享流程來實(shí)現(xiàn)這一點(diǎn)。
?
SharedFlow to the rescue
SharedFlow是一個(gè)允許在多個(gè)Collecter之間共享自身的流,因此對于所有同時(shí)進(jìn)行的收集器來說,只有一個(gè)流被有效運(yùn)行(物化)。如果你定義了一個(gè)訪問數(shù)據(jù)庫的SharedFlow,并且它被多個(gè)收集器收集,那么數(shù)據(jù)庫訪問將只運(yùn)行一次,并且產(chǎn)生的數(shù)據(jù)將被共享給所有收集器。
StateFlow也可以用來實(shí)現(xiàn)同樣的行為:它是一個(gè)專門的SharedFlow,具有.值(它的當(dāng)前狀態(tài))和特定的SharedFlow配置(約束)。我們將在后面討論這些約束。
我們有一個(gè)操作符,用于將任何Flow轉(zhuǎn)換為SharedFlow。
fun <T> Flow<T>.shareIn(
scope: CoroutineScope,
started: SharingStarted,
replay: Int = 0
): SharedFlow<T> (source)
讓我們將其應(yīng)用于我們的數(shù)據(jù)源。
該范圍是所有用于物化Flow的計(jì)算將被完成的地方。由于我們的數(shù)據(jù)源是一個(gè)@Singleton,我們可以使用應(yīng)用程序進(jìn)程的LifecycleScope,它是一個(gè)LifecycleCoroutineScope,在進(jìn)程創(chuàng)建時(shí)被創(chuàng)建,只有在進(jìn)程銷毀時(shí)才被銷毀。
對于開始參數(shù),我們可以使用SharingStarted.WhileSubscribed(),這使得我們的Flow只有在訂閱者的數(shù)量從0變成1時(shí)才開始共享(具體化),而當(dāng)訂閱者的數(shù)量從1變成0時(shí)就停止共享。這類似于我們之前通過在onActive()回調(diào)中添加GeoQuery監(jiān)聽器和在onInactive()回調(diào)中刪除監(jiān)聽器來實(shí)現(xiàn)的LiveData行為。我們也可以將其配置為急切地啟動(dòng)(立即物化,永不去物化)或懶惰地啟動(dòng)(首次收集時(shí)物化,永不去物化),但我們確實(shí)希望它在不被下游收集時(shí)停止上游的數(shù)據(jù)庫收集。
關(guān)于術(shù)語的注意:就像我們對LiveData使用觀察者這個(gè)術(shù)語,對冷流使用收集者這個(gè)術(shù)語一樣,我們對SharedFlow使用訂閱者這個(gè)術(shù)語。對于重放參數(shù),我們可以使用1:新的訂閱者將在訂閱后立即獲得最后一個(gè)發(fā)出的值。
@Singleton
class NearbyUsersDataSource @Inject constructor() {
// Ideally, those should be constructor-injected.
val geoFire = GeoFire(FirebaseDatabase.getInstance().getReference("geofire"))
val geoLocation = GeoLocation(0.0, 0.0)
val radius = 100.0
val geoQuery = geoFire.queryAtLocation(geoLocation, radius)
private fun GeoQuery.asFlow() = callbackFlow {
val listener: GeoQueryEventListener = object : GeoQueryEventListener {
val map = mutableMapOf<Key, GeoLocation>()
override fun onKeyEntered(key: String, location: GeoLocation) {
map[key] = location
}
override fun onKeyExited(key: String) {
map.remove(key)
}
override fun onKeyMoved(key: String, location: GeoLocation) {
map[key] = location
}
override fun onGeoQueryReady() {
emit(State.Ready(map.toMap())
}
override fun onGeoQueryError(e: DatabaseError) {
emit(State.Error(map.toMap(), e.toException())
}
}
addGeoQueryEventListener(listener)
awaitClose { removeGeoQueryEventListener(listener) }
}.shareIn(
ProcessLifecycleOwner.get().lifecycleScope,
SharingStarted.WhileSubscribed(),
1
)
val locations: Flow<State> = geoQuery.asFlow()
sealed class State(open val value: Map<Key, GeoLocation>) {
data class Ready(
override val value: Map<Key, GeoLocation>
) : State(value)
data class Error(
override val value: Map<Key, GeoLocation>,
val exception: Exception
) : State(value)
}
}
把SharedFlow想象成一個(gè)流量收集器本身可能會(huì)有幫助,它把我們上游的冷流量具體化為熱流量,并在下游的許多收集器之間分享收集的值。在上游的冷流和下游的多個(gè)收集器之間有一個(gè)中間人。
現(xiàn)在,我們可能會(huì)認(rèn)為我們的活動(dòng)不需要調(diào)整。錯(cuò)了! 有一個(gè)問題:當(dāng)在一個(gè)用 launchWhenStarted {} 啟動(dòng)的 coroutine 中收集流量時(shí),coroutine 將會(huì)暫停。時(shí),該循環(huán)程序?qū)⒃趏nStop()時(shí)暫停,并在onStart()時(shí)恢復(fù),但它仍將被訂閱到該流。對于MutableSharedFlow來說,這意味著MutableSharedFlow.subscriptionCount對于暫停的coroutine不會(huì)改變。為了利用SharingStarted.WhileSubscribed()的力量,我們需要在onStop()上實(shí)際取消訂閱,并在onStart()上再次訂閱。這意味著取消收集的循環(huán)程序并重新創(chuàng)建它。
(更多細(xì)節(jié)見本期和本期)。
讓我們?yōu)檫@個(gè)通用目的創(chuàng)建一個(gè)類。
@PublishedApi
internal class ObserverImpl<T> (
lifecycleOwner: LifecycleOwner,
private val flow: Flow<T>,
private val collector: suspend (T) -> Unit
) : DefaultLifecycleObserver {
private var job: Job? = null
override fun onStart(owner: LifecycleOwner) {
job = owner.lifecycleScope.launch {
flow.collect {
collector(it)
}
}
}
override fun onStop(owner: LifecycleOwner) {
job?.cancel()
job = null
}
init {
lifecycleOwner.lifecycle.addObserver(this)
}
}
inline fun <reified T> Flow<T>.observe(
lifecycleOwner: LifecycleOwner,
noinline collector: suspend (T) -> Unit
) {
ObserverImpl(lifecycleOwner, this, collector)
}
inline fun <reified T> Flow<T>.observeIn(
lifecycleOwner: LifecycleOwner
) {
ObserverImpl(lifecycleOwner, this, {})
}
注意:如果你想在你的項(xiàng)目中使用這個(gè)自定義觀察器,你可以使用這個(gè)庫:https://github.com/psteiger/flow-lifecycle-observer 現(xiàn)在,我們可以調(diào)整我們的Activity來使用我們剛剛創(chuàng)建的.observeIn(LifecycleOwner)擴(kuò)展函數(shù)。
@AndroidEntryPoint
class NearbyUsersActivity : AppCompatActivity() {
private val viewModel: NearbyUsersViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
viewModel
.locations
.onEach { /* new locations received */ }
.observeIn(this)
}
}
當(dāng)LifecycleOwner的Lifecycle達(dá)到CREATED狀態(tài)(就在onStop()調(diào)用之前)時(shí),用observeIn(LifecycleOwner)創(chuàng)建的collector coroutine將被銷毀,一旦達(dá)到STARTED狀態(tài)(onStart()調(diào)用之后),將被重新創(chuàng)建。
注意:為什么是CREATED狀態(tài)?不應(yīng)該是STOPPED狀態(tài)嗎?一開始聽起來很反常,但這很有意義。Lifecycle.State只有以下幾種狀態(tài)。
創(chuàng)建、銷毀、初始化、恢復(fù)、開始。不存在STOPPED和PAUSED狀態(tài)。當(dāng)生命周期到達(dá)onPause()時(shí),它沒有進(jìn)入一個(gè)新的狀態(tài),而是回到了STARTED狀態(tài)。當(dāng)它到達(dá)onStop()時(shí),它又回到了CREATED狀態(tài)。
img
我們現(xiàn)在有一個(gè)數(shù)據(jù)源,它只實(shí)現(xiàn)一次,但將其數(shù)據(jù)分享給所有的訂閱者。一旦沒有訂閱者,它的上游收集就會(huì)停止,一旦第一個(gè)訂閱者重新出現(xiàn),就會(huì)重新啟動(dòng)。它對Android平臺(tái)沒有依賴性,也不與主線程綁定(通過簡單地應(yīng)用.flowOn()操作符:flowOn(Dispatchers.IO)或.flowOn(Dispatchers.Default),流量轉(zhuǎn)換可以發(fā)生在其他線程中)。
But what if I need to eventually access the current state of the flow without collecting it?
如果我們真的需要像使用LiveData那樣用.value訪問Flow的狀態(tài),我們可以使用StateFlow,它是一個(gè)專門的、受限的SharedFlow。我們可以應(yīng)用stateIn(),而不是應(yīng)用shareIn()操作符來具體化流。
fun <T> Flow<T>.stateIn(
scope: CoroutineScope,
started: SharingStarted,
initialValue: T
): StateFlow<T> (source)
從方法參數(shù)中我們可以看到,sharedIn()和stateIn()之間有兩個(gè)基本區(qū)別。
- stateIn()不支持重放的定制。StateFlow是一個(gè)具有固定重放=1的SharedFlow。這意味著新的訂閱者在訂閱時(shí)將立即得到當(dāng)前的狀態(tài)。
- stateIn()需要一個(gè)初始值。這意味著如果你當(dāng)時(shí)沒有初始值,你將需要使StateFlow類型T為空,或者使用一個(gè)密封的類來表示一個(gè)空的初始值。
?狀態(tài)流是一個(gè)共享流
狀態(tài)流是SharedFlow的一個(gè)特殊用途、高性能和高效的實(shí)現(xiàn),用于共享狀態(tài)這種狹窄但廣泛使用的情況。關(guān)于適用于所有共享流的基本規(guī)則、約束和操作符,請參見SharedFlow文檔。
狀態(tài)流總是有一個(gè)初始值,向新的訂閱者復(fù)制一個(gè)最新的值,不緩沖任何更多的值,但保留最后發(fā)出的一個(gè)值,并且不支持 resetReplayCache。當(dāng)一個(gè)狀態(tài)流用以下參數(shù)創(chuàng)建并對其應(yīng)用distinctUntilChanged操作符時(shí),它的行為與共享流完全一樣。
?
// MutableStateFlow(initialValue) is a shared flow with the following parameters:
val shared = MutableSharedFlow(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
shared.tryEmit(initialValue) // emit the initial value
val state = shared.distinctUntilChanged() // get StateFlow-like behavior
當(dāng)你需要一個(gè)在行為上有調(diào)整的StateFlow時(shí),使用SharedFlow,比如額外的緩沖,重放更多的值,或者省略初始值。
然而,注意選擇SharedFlow的明顯妥協(xié):你將失去StateFlow.value。
Which to choose, StateFlow or SharedFlow?
回答這個(gè)問題的簡單方法是試圖回答其他幾個(gè)問題。
"我真的需要在任何時(shí)候用myFlow.value訪問流的當(dāng)前狀態(tài)嗎?"
?如果這個(gè)問題的答案是否定的,你可以考慮SharedFlow。
?
"我是否需要支持發(fā)射和收集重復(fù)值?"
?如果這個(gè)問題的答案是肯定的,你將需要SharedFlow。
?
"我是否需要為新的訂閱者重放超過最新的值?"
?如果這個(gè)問題的答案是肯定的,你將需要SharedFlow。
?
正如我們所看到的,StateFlow用于所有的事情并不自動(dòng)是正確的答案。
- 它忽略(混淆)了重復(fù)的值,這是不可以配置的。有時(shí)你需要不忽略重復(fù)的值,例如:一個(gè)連接嘗試,將嘗試結(jié)果存儲(chǔ)在一個(gè)流中,每次失敗后需要重試。
- 另外,它需要一個(gè)初始值。因?yàn)镾haredFlow沒有.value,所以它不需要用初始值來實(shí)例化--收集器將直接暫停,直到第一個(gè)值出現(xiàn),在任何值到來之前,沒有人會(huì)嘗試訪問.value。如果你沒有StateFlow的初始值,你必須使StateFlow類型為nullable T?,并使用null作為初始值(或者為默認(rèn)的無值聲明一個(gè)密封類)。
- 另外,你可能想調(diào)整一下重放值。SharedFlow可以為新的訂閱者重放最后的n個(gè)值。StateFlow有一個(gè)固定的重放值為1--它只共享當(dāng)前的狀態(tài)值。
兩者都支持SharingStarted ( Eagerly, Lazily or WhileSubscribed())配置。我通常使用SharingStarted.WhileSubscribed(),并在Activity onStart()/onStop()上銷毀/創(chuàng)建我所有的收集器,所以當(dāng)用戶不積極使用應(yīng)用程序時(shí),數(shù)據(jù)源上游收集將停止(這類似于在LiveData onActive()/onInactive()上刪除/重新添加監(jiān)聽器)。
StateFlow對SharedFlow的約束可能不是最適合你的,你可能想用行為來調(diào)整并選擇使用SharedFlow。就我個(gè)人而言,我很少需要訪問myFlow.value,而且我喜歡SharedFlow的靈活性,所以我通常選擇SharedFlow。
在官方文檔中閱讀更多關(guān)于StateFlow和SharedFlow的內(nèi)容。
A practical case where SharedFlow instead of StateFlow is needed
考慮以下圍繞谷歌計(jì)費(fèi)客戶端庫的包裝器。我們有一個(gè)MutableSharedFlow billingClientStatus,用于存儲(chǔ)當(dāng)前與計(jì)費(fèi)服務(wù)的連接狀態(tài)。
我們將其初始值設(shè)置為SERVICE_DISCONNECTED。我們收集billingClientStatus,當(dāng)它不確定時(shí),我們嘗試啟動(dòng)與計(jì)費(fèi)服務(wù)的連接()。如果連接嘗試失敗,我們將發(fā)出SERVICE_DISCONNECTED。
在這個(gè)例子中,如果billingClientStatus是一個(gè)MutableStateFlow而不是MutableSharedFlow,當(dāng)它的值已經(jīng)是SERVICE_DISCONNECTED,而我們試圖將它設(shè)置為相同的值(連接重試失?。鼘⒑雎愿?,因此,它不會(huì)再嘗試重新連接。
@Singleton
class Biller @Inject constructor(
@ApplicationContext private val context: Context,
) : PurchasesUpdatedListener, BillingClientStateListener {
private var billingClient: BillingClient =
BillingClient.newBuilder(context)
.setListener(this)
.enablePendingPurchases()
.build()
private val billingClientStatus = MutableSharedFlow<Int>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
override fun onBillingSetupFinished(result: BillingResult) {
billingClientStatus.tryEmit(result.responseCode)
}
override fun onBillingServiceDisconnected() {
billingClientStatus.tryEmit(BillingClient.BillingResponseCode.SERVICE_DISCONNECTED)
}
// ...
// Suspend until billingClientStatus == BillingClient.BillingResponseCode.OK
private suspend fun requireBillingClientSetup(): Boolean =
withTimeoutOrNull(TIMEOUT_MILLIS) {
billingClientStatus.first { it == BillingClient.BillingResponseCode.OK }
true
} ?: false
init {
billingClientStatus.tryEmit(BillingClient.BillingResponseCode.SERVICE_DISCONNECTED)
billingClientStatus.observe(ProcessLifecycleOwner.get()) {
when (it) {
BillingClient.BillingResponseCode.OK -> with (billingClient) {
updateSkuPrices()
handlePurchases()
}
else -> {
delay(RETRY_MILLIS)
billingClient.startConnection(this@Biller)
}
}
}
}
private companion object {
private const val TIMEOUT_MILLIS = 2000L
private const val RETRY_MILLIS = 3000L
}
}
在這種情況下,我們需要使用SharedFlow,它支持發(fā)射連續(xù)的重復(fù)值。
On the GeoFire use-case
如果你有使用GeoFire的實(shí)際需要,我已經(jīng)開發(fā)了一個(gè)庫,geofire-ktx,允許隨時(shí)將GeoQuery對象轉(zhuǎn)換為Flow。它還支持獲取位于其他DatabaseReference根中的DataSnapshot,其子鍵與GeoFire根相同,因?yàn)檫@是GeoQuery的一個(gè)常見用例。它還支持將這些數(shù)據(jù)作為一個(gè)類的實(shí)例而不是DataSnapshot來獲取。這是通過Flow轉(zhuǎn)換完成的。該庫的源代碼完成了本文中給出的例子。
對于其他Android庫,請查看https://github.com/psteiger。
https://github.com/psteiger/geofire-ktx/blob/master/geofire/src/main/java/com/freelapp/geofire/flow/GeoQuery.kt
原文鏈接:https://proandroiddev.com/should-we-choose-kotlins-stateflow-or-sharedflow-to-substitute-for-android-s-livedata-2d69f2bd6fa5
向大家推薦下我的網(wǎng)站 https://xuyisheng.top/ 點(diǎn)擊原文一鍵直達(dá)
專注 Android-Kotlin-Flutter 歡迎大家訪問
往期推薦
本文原創(chuàng)公眾號(hào):群英傳,授權(quán)轉(zhuǎn)載請聯(lián)系微信(Tomcat_xu),授權(quán)后,請?jiān)谠瓌?chuàng)發(fā)表24小時(shí)后轉(zhuǎn)載。< END >作者:徐宜生
更文不易,點(diǎn)個(gè)“三連”支持一下??
