我正在查看 JetSurvey project (Android Jetpack Compose 示例项目)并注意到他们创建了一个类来将 LiveData 值包装在他们的 ViewModel 类中。这是我正在谈论的类(class):
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
data class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
在 ViewModel 中,它是这样使用的:
private val _navigateTo = MutableLiveData<Event<Screen>>()
val navigateTo: LiveData<Event<Screen>> = _navigateTo
fun signInAsGuest() {
_navigateTo.value = Event(Survey)
}
似乎 Event 类的意义在于避免导航发生多次。但是,我一开始不明白这是怎么发生的,因为只有在 LiveData 值更新后才会触发导航。每次更新值时,都会创建一个新的 Event 对象,因此它会再次运行。
那么在 Fragment 中,viewModel.navigateTo.observe(viewLifecycleOwner)
中的代码是否有可能在值未更新的情况下运行多次?如果是这样,会在什么情况下发生?
如果我对事件包装器的作用的理解不正确,那么它的实现目的是什么?有必要吗?
最佳答案
假设 fragment 中的 LiveData 观察器导航到第二个 fragment 。用户旋转屏幕,以便销毁第一个 Fragment 实例。当他们退出第二个 Fragment 时,将重新创建第一个 Fragment 的新实例,因此再次触发其观察者。如果没有事件包装器,它会突然而令人惊讶地立即导航回第二个 fragment 。
此外,一个 LiveData 可能有多个观察者。也许两个不同的 Fragment 正在观察相同的事件 LiveData,但您不想冒险为同一事件向用户显示两次消息。例如,他们可以导航到第二个 Fragment,该 Fragment 正在观察与第一个 Fragment 相同的事件 LiveData。一个事件触发,第二个 Fragment 观察器向用户显示一个对话框或其他东西。然后用户返回到第一个 fragment ,同一个事件将触发第一个 fragment 的观察者,因此事件被处理两次。
如果您的项目使用协程,则针对此事件观察问题的更简洁的解决方案是使用 replay
为 0 的 SharedFlow,而不是使用带有事件包装器类的 LiveData。我怀疑他们没有在 JetSurvey 项目中使用 SharedFlow 的原因是他们不想假设您已经熟悉 Flows,而这不是示例的内容。
一个名为 SingleLiveEvent 的替代解决方案曾出现在官方 Android 示例中,but was considered too hacky to add to the Jetpack libraries .
关于android - 为什么 LiveData 值需要包装器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70790138/