从 ViewModel 观察 LiveData

Observing LiveData from ViewModel

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

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

ViewModel 文档中

However ViewModel objects must never observe changes to lifecycle-aware observables, such as LiveData objects.

另一种方式是让数据实现 RxJava 而不是 LiveData,那么它就没有 lifecycle-aware.

的好处了

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

我猜如果你想遵守 lifecycle-ware 的整个想法,我们需要在 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 完成),否则它不会起作用。

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

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

更新:也可以在Daointerface中更改LiveData<YourData>。您需要在函数中添加 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
}

使用流量

文档中的指南被误解了

However ViewModel objects must never observe changes to lifecycle-aware observables, such as LiveData objects.

在这个Github issue中,他描述了应用上述规则的情况是被观察的生命周期感知的可观察对象由另一个生命周期范围托管。 observed LiveData in ViewModel contains observed LiveData.

没有问题

使用流量

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

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

使用 StateFlow

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

PS

asFlow 产生了一个流程,使 LiveDatacollect 开始时激活。我认为使用 MediatorLiveDataTransformations 并附加虚拟观察者的解决方案使用 Flow 没有区别,除了从 LiveData 发出的值总是在 ViewModel实例。

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

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

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

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

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

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

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

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

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

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()
})

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

var selectedID = MutableLiveData<Int>()

val objects = Transformations.switchMap(selectedID) { getAllByID(it) }