从ViewModel观察LiveData


90

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


Answers:


38

在Google开发人员JoseAlcérreca的这篇博客文章中,建议在这种情况下使用一种转换(请参阅“存储库中的LiveData”段落),因为ViewModel不应保存与View(Activity,Context等)相关的任何引用,因为这样做会使操作变得困难去测试。


您设法让Transformation为您工作了吗?我的活动无法正常进行
-romaneso

23
单独进行转换无效,因为您在转换中编写的任何代码仅在某些实体观察到转换时才附加运行。
orbitbot '18

5
我不知道为什么这是推荐的答案,它与问题无关。2年后,我们仍然不知道如何在我们的视图模型中观察存储库数据的变化。
安德鲁

24

ViewModel文档中

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

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

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

我想如果您要遵守整个生命周期软件的概念,我们需要在“活动/片段”中移动观察代码。另外,我们可以在ViewModel中使用回调或RxJava。

另一个折衷方案是实现MediatorLiveData(或Transformations)并在ViewModel中进行观察(在此处输入您的逻辑)。注意,除非在“活动/片段”中观察到MediatorLiveData观察者,否则它不会触发(与“转换”相同)。我们要做的是在“活动/片段”中放置空白观察,其中实际工作实际上是在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和LiveData:Patterns + AntiPatterns,它建议进行转换。我认为除非观察到LiveData(否则可能需要在Activity / Fragment中完成),否则它不会起作用。


2
这方面有什么变化吗?还是RX,回调或空白观察只是解决方案?
qbait

2
有什么办法可以消除这些空白?
Ehsan Mashhadi

1
也许使用Flow(mLiveData.asFlow())或observeForever
马查多

如果您不想/不需要片段中的任何观察者逻辑,流解决方案似乎也可以工作
adek111

14

我认为您可以使用无需生命周期所有者接口的observeForever,并且可以从viewmodel观察结果


2
这似乎对我来说是正确的答案,尤其是在有关ViewModel.onCleared()的文档中这样说:“当ViewModel观察到一些数据并且您需要清除此订阅以防止此ViewModel泄漏时,这很有用。”
Yosef

2
抱歉,但是Cannot invoke observeForever on a background thread
博肯

1
那似乎很合理。尽管必须将观察者保存在viewModel字段中,然后在退订onCleared。至于后台线程-从主线程观察,就是这样。
Kirill Starostin

@Boken您可以observeForever通过GlobalScope.launch(Dispatchers.Main) { myvm.observeForever() }
rmir​​abelle

4

将Kotlin协程与Architecture组件一起使用。

您可以使用liveDatabuilder函数来调用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或更高版本。

也有一篇关于它的文章

更新:也有可能改变LiveData<YourData>Dao interface。您需要将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
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.