【问题标题】:Observing LiveData from ViewModel从 ViewModel 观察 LiveData
【发布时间】:2018-05-10 23:46:33
【问题描述】:

我有一个单独的类,我在其中处理数据获取(特别是 Firebase),我通常从中返回 LiveData 对象并异步更新它们。现在我想将返回的数据存储在 ViewModel 中,但问题是为了获得所述值,我需要观察从数据获取类返回的 LiveData 对象。观察方法需要一个 LifecycleOwner 对象作为第一个参数,但我的 ViewModel 中显然没有它,我知道我不应该在 ViewModel 中保留对 Activity/Fragment 的引用。我该怎么办?

【问题讨论】:

标签: android android-architecture-components android-livedata android-viewmodel


【解决方案1】:

在 Google 开发人员 Jose Alcérreca 的 this blog post 中,建议在这种情况下使用转换(请参阅“存储库中的实时数据”段落),因为 ViewModel 不应包含与 @ 相关的任何引用987654322@(活动、上下文等),因为它很难测试。

【讨论】:

  • 您是否设法让转换为您工作?我的活动不起作用
  • 单独的转换不起作用,因为您在转换中编写的任何代码仅在某些实体观察到转换时附加运行。
  • 我不知道为什么这是推荐的答案,它与问题无关。 2 年后,我们仍然不知道如何在我们的视图模型中观察存储库数据的变化。
  • @Andrew 嗨,安德鲁,我很好奇。为什么不将片段中的数据观察为 viewmodel.repository.liveModelData.observe {this, }
  • 这不能回答问题吗?如果您需要视图模型中的特定值来执行其他操作,然后将其提供给片段怎么办??
【解决方案2】:

在ViewModel 文档中

但是,ViewModel 对象绝不能观察生命周期感知可观察对象的更改,例如 LiveData 对象。

另一种方法是让数据实现 RxJava 而不是 LiveData,那么它就没有生命周期感知的好处。

在todo-mvvm-live-kotlin 的谷歌示例中,它在 ViewModel 中使用没有 LiveData 的回调。

我猜如果你想遵守作为生命周期软件的整个想法,我们需要在 Activity/Fragment 中移动观察代码。否则,我们可以在 ViewModel 中使用回调或 RxJava。

另一个折衷方案是在 ViewModel 中实现 MediatorLiveData(或转换)并观察(将您的逻辑放在这里)。注意 MediatorLiveData 观察者不会触发(与转换相同),除非它在 ​​Activity/Fragment 中观察到。我们所做的就是在 Activity/Fragment 中放置一个空白观察,其中真正的工作实际上是在 ViewModel 中完成的。

// ViewModel
fun start(id : Long) : LiveData<User>? {
    val liveData = MediatorLiveData<User>()
    liveData.addSource(dataSource.getById(id), Observer {
        if (it != null) {
            // put your logic here
        }
    })
}

// Activity/Fragment
viewModel.start(id)?.observe(this, Observer {
    // blank observe here
})

PS:我读过ViewModels and LiveData: Patterns + AntiPatterns,它建议转换。除非观察到 LiveData(这可能需要在 Activity/Fragment 中完成),否则我认为它不起作用。

【讨论】:

  • 这方面有什么变化吗?还是 RX、回调或空白观察只是解决方案?
  • 有什么办法可以摆脱这些空白观察?
  • 可能使用 Flow (mLiveData.asFlow()) 或 observeForever。
  • 如果您不想在 Fragment 中拥有/不需要任何观察者逻辑,流解决方案似乎可以工作
【解决方案3】:

我认为你可以使用不需要生命周期所有者接口的observeForever,你可以从视图模型中观察结果

【讨论】:

  • 这对我来说似乎是正确的答案,尤其是在有关 ViewModel.onCleared() 的文档中说:“当 ViewModel 观察到一些数据并且您需要清除此订阅以防止泄漏这个 ViewModel。”
  • 对不起Cannot invoke observeForever on a background thread
  • 这似乎很合理。尽管必须在 viewModel 字段中保存观察者并在onCleared 取消订阅。至于后台线程 - 从主线程观察,就是这样。
  • @Boken 你可以通过GlobalScope.launch(Dispatchers.Main) { myvm.observeForever() }强制从main调用observeForever
  • 经过几个小时的研究。我想这样做的唯一好方法是在视图组件中使用空白观察,因为当视图(应该附加的可观察对象)被销毁时,observeForever 可能会导致问题。这是我能找到的,我仍然在这个问题上。如果我找到更方便的答案,我会更新。
【解决方案4】:

使用流程

文档中的指南被误解了

但是,ViewModel 对象绝不能观察生命周期感知可观察对象的更改,例如 LiveData 对象。

在这个Github issue 中,他描述了应用上述规则的情况是观察到的生命周期感知的可观察对象由另一个生命周期范围托管。 观察LiveData在ViewModel中包含观察到的LiveData是没有问题的。

使用流程

class MyViewModel : ViewModel() {
    private val myLiveData = MutableLiveData(1)

    init {
        viewModelScope.launch {
            myLiveData.asFlow().collect {
                // Do Something
            }
        }
    }
}

使用状态流

class MyViewModel : ViewModel() {
    private val myFlow = MutableStateFlow(1)
    private val myLiveData = myFlow.asLiveData(viewModelScope.coroutineContext)
}

PS

asFlow 生成一个流,使LiveData 在启动collect 时激活。我认为使用MediatorLiveData 或Transformations 并附加一个虚拟观察者的解决方案使用Flow 没有区别,除了在ViewModel 实例中始终观察到来自LiveData 的值。

【讨论】:

    【解决方案5】:

    Use Kotlin coroutines with Architecture components.

    您可以使用liveData 构建器函数调用suspend 函数,将结果作为LiveData 对象提供。

    val user: LiveData<User> = liveData {
        val data = database.loadUser() // loadUser is a suspend function.
        emit(data)
    }
    

    您还可以从块中发出多个值。每个emit() 调用都会暂停块的执行,直到在主线程上设置LiveData 值。

    val user: LiveData<Result> = liveData {
        emit(Result.loading())
        try {
            emit(Result.success(fetchUser()))
        } catch(ioException: Exception) {
            emit(Result.error(ioException))
        }
    }
    

    在您的 gradle 配置中,使用 androidx.lifecycle:lifecycle-livedata-ktx:2.2.0 或更高版本。

    还有一个关于它的article。

    更新:也可以在Dao interface 中更改LiveData&lt;YourData&gt;。您需要在函数中添加suspend 关键字:

    @Query("SELECT * FROM the_table")
    suspend fun getAll(): List<YourData>
    

    在ViewModel 中,您需要像这样异步获取它:

    viewModelScope.launch(Dispatchers.IO) {
        allData = dao.getAll()
        // It's also possible to sync other data here
    }
    

    【讨论】:

      【解决方案6】:

      距离最初的帖子已经有一段时间了,但我最近偶然发现了同样的问题(也与 Firebase 相关),我能够通过转换解决它。

      我有一个存储库类,其中包含使用 Firebase 的 ValueEventListener 收集的 liveData 对象。 ViewModel 持有对此存储库的引用。

      现在,在 ViewModel 中,不再使用从存储库返回 LiveData 值的函数,然后通过观察者将其传递给 Fragment,如下所示:

      fun getMyPayments(): LiveData<HashMap<String, Int>> {
          return repository.provideMyPayments()
      }
      

      在被 ViewModel 中的另一个函数处理后,我使用一个带有 Transformations.map 的 val 来表示 LiveData 的最终结果:

      val myRoomPaymentsList : LiveData<HashMap<String, HashMap<String, Payment>>> = Transformations.map(repository.provideMyPayments()) {data ->
              getRoomPaymentsList(data)
          }
      

      注意第一个参数是你观察到的数据源,第二个参数是你想要得到的结果。 此 val 是一个 LiveData val,它保存存储库中的最新值并根据需要在 Fragment 中提供它,将所有处理保留在 ViewModel 中,并且仅将 UI 函数保留在 Framgent 本身中。

      然后,在我的 Fragment 中,我在这个 val 上放置了一个观察者:

      viewModel.myRoomPaymentsList.observe(viewLifecycleOwner, {
          roomPayments = it
          graphFilterPeriod()
      })
      

      【讨论】:

        【解决方案7】:

        我知道这个话题已经有很棒的答案,但我也想添加自己的答案:

        如果您想坚持使用LiveData,您可以始终使用Transformations.map,这样您就不必在ViewModel 中使用observe,而只需在Fragment/Activity 中使用。

        否则,您可以使用SharedFlow,一个可观察的单个事件。更多,我在这里写了一篇文章:https://coroutinedispatcher.com/posts/decorate_stateful_mvvm/

        您不必在ViewModel 中传递viewLifecycleOwner,因为在View 只需要最新结果时,在ViewModel 中调用observe 是没有意义的。

        【讨论】:

          【解决方案8】:

          例如,如果您需要获取一个 ID(作为 LiveData)并使用它再次调用 LiveData。将 ID 存储在 selectedID 中,Transformations 将观察该字段并在它发生变化时调用 getAllByID(selectedID)(也是 LiveData)。

          var selectedID = MutableLiveData<Int>()
          
          val objects = Transformations.switchMap(selectedID) { getAllByID(it) }
          

          【讨论】:

            猜你喜欢
            • 2020-03-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多