为什么 LiveData 值需要包装器?

Why is a wrapper necessary for a LiveData value?

我正在查看 JetSurvey project(Android Jetpack Compose 示例项目)并注意到他们创建了一个 class 来将 LiveData 值包装在他们的 ViewModel class 中.这是我所说的 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)
    }

似乎事件 class 的目的是避免导航发生多次。但是,我一开始不明白这是怎么发生的,因为只有在 LiveData 值更新后才会触发导航。每次更新值时,都会创建一个新的 Event 对象,因此它会再次 运行。

那么在Fragment中,viewModel.navigateTo.observe(viewLifecycleOwner)里面的代码有没有可能多次运行而值没有更新?如果是这样,在什么情况下会发生?

如果我对事件包装器的作用的理解不正确,那么它的实现目的是什么?有必要吗?

假设片段中的 LiveData 观察器导航到第二个片段。用户旋转屏幕,以便销毁第一个 Fragment 实例。当他们退出第二个 Fragment 时,将重新创建第一个 Fragment 的新实例,因此再次触发其观察者。如果没有事件包装器,它会突然而令人惊讶地立即导航回第二个 Fragment。

此外,一个 LiveData 可能有多个观察者。也许两个不​​同的 Fragment 正在观察相同的事件 LiveData,但您不想冒险为同一事件向用户显示两次消息。例如,他们可以导航到第二个 Fragment,该 Fragment 正在观察与第一个 Fragment 相同的事件 LiveData。一个事件触发,第二个 Fragment 观察器向用户显示一个对话框或其他东西。然后用户返回到第一个片段,同一个事件将触发第一个片段的观察者,因此事件被处理两次。

如果您的项目使用协同程序,则针对此事件观察问题的更简洁的解决方案是使用 replay 为 0 的 SharedFlow,而不是使用带有事件包装器 class 的 LiveData。我怀疑他们没有在 JetSurvey 项目中使用 SharedFlow 的原因是他们不想假设您已经熟悉 Flows,而这不是示例的内容。

一个名为 SingleLiveEvent 的替代解决方案出现在官方 Android 示例中一次,