<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>

          【譯】LiveData with Coroutines and Flow

          共 16156字,需瀏覽 33分鐘

           ·

          2021-12-01 16:18

          點擊上方藍字關(guān)注我,知識會給你力量


          這個系列我做了協(xié)程和Flow開發(fā)者的一系列文章的翻譯,旨在了解當前協(xié)程、Flow、LiveData這樣設(shè)計的原因,從設(shè)計者的角度,發(fā)現(xiàn)他們的問題,以及如何解決這些問題,pls enjoy it。

          Part I: Reactive UIs

          從Android的早期開始,我們就很快了解到Android的生命周期很難理解,充滿了邊緣案例,而保持理智的最好方法就是盡可能地避免它們。

          為此,我們建議采用分層架構(gòu),這樣我們就可以編寫?yīng)毩⒂赨I的代碼,而不用過多考慮生命周期。例如,我們可以添加一個持有業(yè)務(wù)邏輯的領(lǐng)域?qū)樱愕膽?yīng)用程序?qū)嶋H做什么)和一個數(shù)據(jù)層。

          img

          此外,我們了解到表現(xiàn)層可以被分成不同的組件,承擔不同的責(zé)任。

          • View--處理生命周期的回調(diào)、用戶事件和Activity或Fragment的導(dǎo)航
          • Presenter、ViewModel--為View提供數(shù)據(jù),并且大多不知道在View中進行的生命周期。這意味著沒有中斷,也不需要在重新創(chuàng)建視圖時進行清理。

          撇開命名不談,有兩種機制可以將數(shù)據(jù)從ViewModel/Presenter發(fā)送到View。

          • 擁有對視圖的引用并直接調(diào)用它。通常與Presenters的工作方式有關(guān)。
          • 將可觀察的數(shù)據(jù)暴露給觀察者。通常與ViewModels的工作方式有關(guān)。

          這是一個在Android社區(qū)相當成熟的慣例,但你會發(fā)現(xiàn)有一些文章有不同意見。有數(shù)百篇博客文章以不同的方式定義Presenter、ViewModel、MVP和MVVM。我的建議是,你專注于你的表現(xiàn)層的特性,使用Android架構(gòu)組件ViewModel。

          • 在配置變化中保存下來,如旋轉(zhuǎn)、地域變化、窗口大小調(diào)整、黑暗模式切換等。
          • 有一個非常簡單的生命周期。它有一個單一的生命周期回調(diào),onCleared,一旦它的生命周期所有者完成,就會被調(diào)用。

          ViewModel被設(shè)計為使用觀察者模式來使用。

          • 它不應(yīng)該有對視圖的引用。
          • 它將數(shù)據(jù)暴露給觀察者,但不知道這些觀察者是什么。你可以使用LiveData來實現(xiàn)這一點。

          當一個視圖(一個Activity、Fragment或任何生命周期的所有者)被創(chuàng)建時,ViewModel被獲得,它開始通過一個或多個LiveDatas暴露數(shù)據(jù),而視圖訂閱了這些數(shù)據(jù)。

          img

          這個訂閱可以用LiveData.observe設(shè)置,也可以用Data Binding庫自動設(shè)置。

          現(xiàn)在,如果設(shè)備被旋轉(zhuǎn),那么視圖將被銷毀(#1),并創(chuàng)建一個新的實例(#2)。

          img

          如果我們在ViewModel中有一個對Activity的引用,我們將需要確保。

          • 當視圖被銷毀時清除它
          • 如果視圖處于transitional狀態(tài),避免訪問。

          但有了ViewModel+LiveData,我們就不必再處理這個問題了。這就是為什么我們在《應(yīng)用程序架構(gòu)指南》中推薦這種方法。

          Scopes

          由于Activities和Fragments比ViewModels有相等或更短的壽命,我們可以開始討論操作的范圍了。

          操作是你在應(yīng)用中需要做的任何事情,比如從網(wǎng)絡(luò)上獲取數(shù)據(jù)、過濾結(jié)果或計算一些文本的排列。

          對于你創(chuàng)建的任何操作,你需要考慮其范圍:從啟動到取消的時間范圍。讓我們看兩個例子。

          • 你在一個Activity的onStart中啟動一個操作,你在onStop中停止它。
          • 你在ViewModel的initblock中啟動一個操作,然后在onCleared()中停止它。
          img

          看一下這個圖,我們可以找到每個操作的意義所在。

          • 在一個作用于Activity的操作中獲取數(shù)據(jù)操作,將迫使我們在旋轉(zhuǎn)后再次獲取它,所以它應(yīng)該被作用于ViewModel。
          • 而排列文本在作用于ViewModel的操作中是沒有意義的,因為在旋轉(zhuǎn)之后,你的文本容器可能已經(jīng)改變了形狀。

          顯然,現(xiàn)實世界中的應(yīng)用可以有比這些更多的作用域。例如,在Android Dev Summit應(yīng)用程序中,我們可以使用。

          • Fragment scopes,每個屏幕有多個
          • Fragment ViewModel作用域,每屏一個
          • Main Activity scopes
          • Main Activity ViewModel scope
          • Application scope
          img

          這可能會產(chǎn)生很多不同的作用域,所以管理所有的作用域會讓人不知所措。我們需要一種方法來結(jié)構(gòu)化這種并發(fā)性!

          一個非常方便的解決方案是Kotlin Coroutines。

          我們喜歡在Android中使用Coroutines有很多原因。其中一些是。

          • 很容易脫離主線程。Android應(yīng)用為了獲得流暢的用戶體驗而不斷地在線程間切換,而Coroutines讓這一切變得超級簡單。
          • 有最小的代碼模板。Coroutines被嵌入到語言中,所以使用諸如suspend功能的東西是很容易的。
          • 結(jié)構(gòu)化的并發(fā)性。這意味著你不得不定義你的操作范圍,而且你可以享受一些代碼層面的保證,從而消除大量的模板代碼,如清理代碼等。你可以把結(jié)構(gòu)化并發(fā)想象成“自動取消”。

          如果你想了解coroutines的介紹,可以看看Android的介紹和Kotlin的官方文檔。

          Part II: Launching coroutines with Architecture Components

          Jetpack的架構(gòu)組件提供了一堆語法糖,所以你不必擔心Jobs和它們的取消行為。你只需要選擇你的操作范圍。

          ViewModel scope

          這是啟動coroutine最常見的方式之一,因為大多數(shù)數(shù)據(jù)操作都是從ViewModel開始的。使用viewModelScope擴展,當ViewModel被清除時,Job會自動取消。使用viewModelScope. launch來啟動coroutine。

          class MainActivityViewModel : ViewModel {

              init {
                  viewModelScope.launch {
                      // Do things!
                  }    
              }
          }

          Activity and Fragment scopes

          同樣,如果你使用lifecycleScope.launch,你可以將操作的范圍限定在一個視圖的特定實例上。

          如果你用launchWhenResumed、launchWhenStarted或launchWhenCreated,則會將操作限制在某一生命周期狀態(tài),你甚至可以有一個更窄的范圍。

          class MyActivity : Activity {
              override fun onCreate(state: Bundle?) {
                  super.onCreate(savedInstanceState)

                  lifecycleScope.launch {
                      // Run
                  }

                  lifecycleScope.launchWhenResumed {
                      // Run
                  }
               }
           }

          Application scope

          全應(yīng)用程序范圍有很好的用例:

          https://medium.com/androiddevelopers/coroutines-patterns-for-work-that-shouldnt-be-cancelled-e26c40f142ad

          但是首先,如果你的代碼最終必須被執(zhí)行,你應(yīng)該考慮使用WorkManager。

          ViewModel + LiveData

          到目前為止,我們已經(jīng)看到了如何啟動一個coroutine,但沒有看到如何從它那里接收一個結(jié)果。你可以像這樣使用一個MutableLiveData。

          // Don't do this. Use liveData instead.
          class MyViewModel : ViewModel() {
              private val _result = MutableLiveData<String>()
              val result: LiveData<String> = _result

              init {
                  viewModelScope.launch {
                      val computationResult = doComputation()
                      _result.value = computationResult
                    }
                }
            }

          但是,由于你將把這個結(jié)果暴露給你的視圖,你可以通過使用liveData coroutine builder來節(jié)省一些模板代碼,它可以啟動一個coroutine,讓你通過一個不可變的LiveData來暴露結(jié)果。你可以使用emit()來向它發(fā)送更新。

          class MyViewModel : ViewModel() {
              val result = liveData {
                  emit(doComputation())
              }
          }

          LiveData Coroutine builder with a switchMap

          在某些情況下,只要LiveData的值發(fā)生變化,你就想啟動一個coroutine。例如,當你在開始數(shù)據(jù)加載操作之前,你需要一個ID參數(shù)。有一個方便的模式,那就是使用Transformations.switchMap。

          private val itemId = MutableLiveData<String>()

          val result = itemId.switchMap {
              liveData { emit(fetchItem(it)) }
          }

          result是一個不可變的LiveData,只要itemId有新的值,就會用調(diào)用fetchItem suspend函數(shù)的結(jié)果來更新數(shù)據(jù)。

          Emit all items from another LiveData

          這個功能不太常見,但也可以節(jié)省一些模板代碼:你可以使用emitSource傳遞一個LiveData數(shù)據(jù)源。當你想先發(fā)射一個初始值,然后再發(fā)射一連串的值時,這很有用。

          liveData(Dispatchers.IO) {
              emit(LOADING_STRING)
              emitSource(dataSource.fetchWeather())
          }

          Cancelling coroutines

          如果你使用上面的任何一種模式,你就不必明確地取消Job。然而,有一件重要的事情要記?。篶oroutine的取消是協(xié)作式的。

          這意味著,如果調(diào)用的coroutine被取消了,你必須幫助Kotlin停止一個Job。比方說,你有一個啟動無限循環(huán)的suspend函數(shù)。Kotlin沒有辦法為你停止這個循環(huán),所以你需要合作,定期檢查這個Job是否在活動狀態(tài)。你可以通過檢查isActive屬性來做到這一點。

          suspend fun printPrimes() {
              while(isActive) {
                  // Compute
              }
          }

          順便說一下,如果你使用kotlinx.coroutines中的任何函數(shù)(如delay),你應(yīng)該知道它們都是可取消的,這意味著它們會為你做這種檢查。

          suspend fun printPrimes() {
              while(true) { // Ok-ish because we call delay inside
                  // Compute
                  delay(1000)
              }
          }

          也就是說,我建議你無論如何都要添加這個檢查,因為將來可能會有人刪除這個延遲調(diào)用,在你的代碼中引入一個微妙的錯誤。

          One-shot vs multiple values

          為了理解coroutines(以及反應(yīng)式UI),我們需要對以下內(nèi)容進行重要區(qū)分。

          • One-shot操作。它們只運行一次,可以返回一個結(jié)果
          • 返回多個值的操作。對一個數(shù)據(jù)源的訂閱,可以在一段時間內(nèi)發(fā)出多個值
          img

          One-shot operations with coroutines

          img

          使用suspend函數(shù)并使用viewModelScope或liveData{}調(diào)用它們是運行非阻塞操作的一種非常方便的方法。

          class MyViewModel {
              val result = liveData {
                  emit(repository.fetchData())
              }
          }

          然而,當我們在監(jiān)聽變化時,事情就變得有點復(fù)雜了。

          Receiving multiple values with LiveData

          我在《LiveData beyond the ViewModel》(2018)中談到了這個話題,在那里我談到了,LiveData從未被設(shè)計成一個功能齊全的流構(gòu)建器這一事實。

          https://medium.com/androiddevelopers/livedata-beyond-the-viewmodel-reactive-patterns-using-transformations-and-mediatorlivedata-fda520ba00b7

          img

          現(xiàn)在,更好的方法是使用Kotlin的Flow(警告:有些部分仍在試驗中)。Flow類似于RxJava中的反應(yīng)式流功能。

          然而,雖然輪子讓非阻塞的一次性操作變得更容易,但這對Flow來說并不是同樣的情況。Flow仍然是難以掌握的。不過,如果你想創(chuàng)建快速而可靠的反應(yīng)式UI,我認為值得花時間來學(xué)習(xí)。由于它是語言的一部分,而且是一個小的依賴項,許多庫都開始添加Flow支持(比如Room)。

          因此,我們可以從數(shù)據(jù)源和存儲庫中暴露Flow,而不是LiveData,但ViewModel仍然暴露LiveData,因為它是生命周期感知的。

          img

          Part III: LiveData and coroutines patterns

          ViewModel patterns

          讓我們看看一些可用于ViewModels的模式,比較一下LiveData和Flow的使用。

          LiveData: Emit N values as LiveData

          val currentWeather: LiveData<String> = dataSource.fetchWeather()

          如果我們不做任何轉(zhuǎn)換,我們可以簡單地將一個分配給另一個。

          Flow: Emit N values as LiveData

          我們可以使用liveData coroutine builder和Flow上的collect(這是一個接收每個發(fā)射值的終端操作符)的組合。

          // Don't use this
          val currentWeatherFlow: LiveData<String> = liveData {
              dataSource.fetchWeatherFlow().collect {
                  emit(it)
              }
          }

          但由于它有很多模板代碼,所以我們添加了Flow.asLiveData()擴展函數(shù),它可以在一行中做同樣的事情。

          val currentWeatherFlow: LiveData<String> = dataSource.fetchWeatherFlow().asLiveData()

          LiveData: Emit 1 initial value + N values from data source

          如果數(shù)據(jù)源暴露了一個LiveData,我們可以使用emitSource在用emit發(fā)射一個初始值后進行批量更新。

          val currentWeather: LiveData<String> = liveData {
              emit(LOADING_STRING)
              emitSource(dataSource.fetchWeather())
          }

          Flow: Emit 1 initial value + N values from data source

          同樣,我們可以天真地做到這一點。

          // Don't use this
          val currentWeatherFlow: LiveData<String> = liveData {
              emit(LOADING_STRING)
              emitSource(
                  dataSource.fetchWeatherFlow().asLiveData()
              )
          }

          但如果我們利用Flow自己的API,事情看起來就會整潔很多。

          val currentWeatherFlow: LiveData<String> = 
              dataSource.fetchWeatherFlow()
                  .onStart { emit(LOADING_STRING) }
                  .asLiveData()

          onStart設(shè)置初始值,這樣做我們只需要向LiveData轉(zhuǎn)換一次。

          LiveData: Suspend transformation

          比方說,你想對來自數(shù)據(jù)源的東西進行轉(zhuǎn)換,但它可能是CPU密集型的,所以它是在一個suspend函數(shù)中。

          你可以在數(shù)據(jù)源的LiveData上使用switchMap,然后用LiveData生成器創(chuàng)建coroutine?,F(xiàn)在你只需對收到的每個結(jié)果調(diào)用emit即可。

          val currentWeatherLiveData: LiveData<String> =
              dataSource.fetchWeather().switchMap {
                   liveData { emit(heavyTransformation(it)) }
              }

          Flow: Suspend transformation

          這就是Flow與LiveData相比真正的優(yōu)勢所在。我們可以再次使用Flow的API來更優(yōu)雅地做事情。在這種情況下,我們使用Flow.map來在每次更新時應(yīng)用轉(zhuǎn)換。這一次,由于我們已經(jīng)在一個coroutine上下文中,我們可以直接調(diào)用它。

          val currentWeatherFlow: LiveData<String> =
              dataSource.fetchWeatherFlow()
                  .map { heavyTransformation(it) }
                  .asLiveData()

          Repository patterns

          img

          關(guān)于資源庫沒有什么好說的,因為如果你在使用Flow,你只需要使用Flow的API來轉(zhuǎn)換和組合數(shù)據(jù)。

          val currentWeatherFlow: Flow<String> =
              dataSource.fetchWeatherFlow()
                  .map { ... }
                  .filter { ... }
                  .dropWhile { ... }
                  .combine { ... }
                  .flowOn(Dispatchers.IO)
                  .onCompletion { ... }

          Data source patterns

          再次,讓我們區(qū)分一下One-shot場景和Flow。

          img

          One-shot operations in the data source

          如果你正在使用一個支持suspend函數(shù)的庫,如Room或Retrofit,你可以簡單地從你的suspend函數(shù)中使用它們。

          suspend fun doOneShot(param: String) : String = retrofitClient.doSomething(param)

          然而,有些工具和庫還不支持coroutine,而是基于回調(diào)。

          在這種情況下,你可以使用suspendCoroutine或suspendCancellableCoroutine。

          (我不知道你為什么要使用不可取消的版本,但請在評論中告訴我!)

          suspend fun doOneShot(param: String) : Result<String> =
              suspendCancellableCoroutine { continuation ->
                  api.addOnCompleteListener { result ->
                      continuation.resume(result)
                  }.addOnFailureListener { error ->
                      continuation.resumeWithException(error)
                  }.fetchSomething(param)
                }

          當你調(diào)用它時,你會得到一個continuation。在這個例子中,我們使用的API讓我們設(shè)置了一個完成的監(jiān)聽器和一個失敗的監(jiān)聽器,所以在它們的回調(diào)中,當我們收到數(shù)據(jù)或錯誤時,我們會調(diào)用continuation.resume或continuation.resumeWithException。

          值得注意的是,如果這個coroutine被取消,resume將被忽略,所以如果你的請求需要很長的時間,這個coroutine將處于活動狀態(tài),直到其中一個回調(diào)被執(zhí)行。

          Exposing Flow in the data source

          Flow builder

          如果你需要創(chuàng)建一個假的數(shù)據(jù)源的實現(xiàn),或者你只是需要一些簡單的東西,你可以使用flow構(gòu)造器,做一些類似的事情。

          override fun fetchWeatherFlow(): Flow<String> = flow {
              var counter = 0
              while(true) {
                  counter++
                  delay(2000)
                  emit(weatherConditions[counter % weatherConditions.size])
              }
          }

          這段代碼每隔兩秒就會發(fā)出一個天氣狀況。

          Callback-based APIs

          如果你想把基于回調(diào)的API轉(zhuǎn)換為Flow,你可以使用callbackFlow。

          fun flowFrom(api: CallbackBasedApi): Flow<T> = callbackFlow {
              val callback = object : Callback {
                  override fun onNextValue(value: T) {
                      offer(value)
                  }
                  override fun onApiError(cause: Throwable) {
                      close(cause)
                  }
                  override fun onCompleted() = close()
              }
              api.register(callback)
              awaitClose { api.unregister(callback) }
          }

          它看起來令人生畏,但如果你把它拆開,你會發(fā)現(xiàn)它有很大的意義。

          • 當我們有一個新的Value時,我們調(diào)用offer方法
          • 當我們想停止發(fā)送更新時,我們調(diào)用close(cause?)
          • 我們使用awaitClose來定義流程關(guān)閉時需要執(zhí)行的內(nèi)容,這對于取消注冊回調(diào)來說是非常完美的。

          總之,coroutines和Flow將繼續(xù)存在。但它們并不能在所有地方取代LiveData。即使是非常有前途的StateFlow(目前是實驗性的),我們?nèi)匀挥蠮ava編程語言和DataBinding的用戶需要支持,所以它在一段時間內(nèi)不會被廢棄 :)

          原文鏈接:https://medium.com/androiddevelopers/livedata-with-coroutines-and-flow-part-i-reactive-uis-b20f676d25d7

          向大家推薦下我的網(wǎng)站 https://xuyisheng.top/  點擊原文一鍵直達

          專注 Android-Kotlin-Flutter 歡迎大家訪問



          往期推薦


          本文原創(chuàng)公眾號:群英傳,授權(quán)轉(zhuǎn)載請聯(lián)系微信(Tomcat_xu),授權(quán)后,請在原創(chuàng)發(fā)表24小時后轉(zhuǎn)載。
          < END >
          作者:徐宜生

          更文不易,點個“三連”支持一下??


          瀏覽 51
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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看片在线观看 | 人妖乱伦视频 |