<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 30428字,需瀏覽 61分鐘

           ·

          2021-12-29 21:22

          0e2e1e486f0875c71373aae1d50b475e.webp

          點(diǎn)擊上方藍(lán)字關(guān)注我,知識(shí)會(huì)給你力量

          cb68b077f0ab3f0bcfd8515e5ccb10d1.webp

          這個(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ù)訪問工作交給工作線程。

          61ba9c8b4231391e2b18feca579bc099.webpimg

          不過,我們不能只是用純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

          e71a9d94d1aac5cc615ea7de55cfa9da.webpimg

          讓我們首先展示一下從數(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

          907149d22784c18b939358ade7f00d0e.webpimg

          讓我們把我們的數(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)。

          683703a1351bf0497d2875ce114807d0.webpimg

          「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)。

          061d45397b6d234f32983cf32b088bea.webpimg

          我們現(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)是正確的答案。

          1. 它忽略(混淆)了重復(fù)的值,這是不可以配置的。有時(shí)你需要不忽略重復(fù)的值,例如:一個(gè)連接嘗試,將嘗試結(jié)果存儲(chǔ)在一個(gè)流中,每次失敗后需要重試。
          2. 另外,它需要一個(gè)初始值。因?yàn)镾haredFlow沒有.value,所以它不需要用初始值來實(shí)例化--收集器將直接暫停,直到第一個(gè)值出現(xiàn),在任何值到來之前,沒有人會(huì)嘗試訪問.value。如果你沒有StateFlow的初始值,你必須使StateFlow類型為nullable T?,并使用null作為初始值(或者為默認(rèn)的無值聲明一個(gè)密封類)。
          3. 另外,你可能想調(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è)“三連”支持一下??


          瀏覽 57
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  中日韩欧美在线 | 操逼网站免费视频 | 国产乱码精品一品二品 | 一级黄色A片 | 国产精品久久久久久久猫咪 |