我有一个单独的类,我在其中处理数据获取(特别是 Firebase),我通常从中返回 LiveData 对象并异步更新它们。现在我想将返回的数据存储在 ViewModel 中,但问题是为了获得所述值,我需要观察从我的数据获取类返回的 LiveData 对象。观察方法需要一个 LifecycleOwner 对象作为第一个参数,但我的 ViewModel 中显然没有它,我知道我不应该在 ViewModel 中保留对 Activity/Fragment 的引用。我应该怎么办?
8 回答
在Google 开发人员 Jose Alcérreca 的这篇博客文章中,建议在这种情况下使用转换(请参阅“存储库中的 LiveData”段落),因为ViewModel不应包含与View
(Activity、Context 等)相关的任何引用,因为它很难去测试。
在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 和 LiveData: Patterns + AntiPatterns建议转换。除非观察到 LiveData(这可能需要在 Activity/Fragment 中完成),否则我认为它不起作用。
我认为您可以使用不需要生命周期所有者接口的observeForever,并且可以从视图模型中观察结果
使用流程
文档中的指南被误解了
然而,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)
}
附言
asFlow
使LiveData
启动时激活的流程collect
。我认为使用MediatorLiveData
orTransformations
并附加一个虚拟观察者的解决方案没有差异,使用Flow
除了发射值之外LiveData
总是在ViewModel
实例中观察到。
您可以使用liveData
builder 函数来调用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
}
距离最初的帖子已经有一段时间了,但我最近偶然发现了同样的问题(也与 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()
})
我知道这个话题已经有了惊人的答案,但我也想添加我自己的答案:
如果你想坚持,LiveData
你可以随时使用Transformations.map
,这样你就不必observe
在中,ViewModel
而只在Fragment
/Activity
中。
否则,您可以使用SharedFlow
可观察的单个事件。更多,我在这里写了一篇文章:https ://coroutinedispatcher.com/posts/decorate_stateful_mvvm/
您不必传入 viewLifecycleOwner
,因为毕竟在只需要最新结果时调用ViewModel
是没有意义的。observe
ViewModel
View
例如,如果您需要获取一个 ID(作为 LiveData)并使用它再次调用 LiveData。将 ID 存储在 selectedID 中,Transformations 将观察该字段并在其更改时调用 getAllByID(selectedID) (也是 LiveData)。
var selectedID = MutableLiveData<Int>()
val objects = Transformations.switchMap(selectedID) { getAllByID(it) }