使用 MVVM 导航 android

Navigation with MVVM android

我有一个应用程序使用 Androids ViewModel class 和导航组件在片段之间导航。我将如何处理来自 ViewModel 的导航?我正在使用 RxJava,我正在考虑让 Fragments 监听导航事件,然后以这种方式触发导航。处理这个问题的正常方法是什么?如果有帮助,我也在使用 Dagger 进行依赖注入。

根据 LiveData with SnackBar, Navigation, and other events blog post:

Some data should be consumed only once, like a Snackbar message, a navigation event or a dialog trigger.

Instead of trying to solve this with libraries or extensions to the Architecture Components, it should be faced as a design problem. We recommend you treat your events as part of your state.

他们详细介绍了 SingleLiveEvent class 的使用,确保每个导航事件仅被 Observer 接收一次(即,您的 Fragment 可以访问您的 NavController) .

另一种选择是使用 'Event wrapper' 模型,其中事件必须明确标记为已处理。

如果您使用的是 MVP,P 只会调用 V 上触发导航的方法。

MVVM 中的等效方法是拥有一个专用的 observable/listener/callback,如果使用 RxJava 完成,它可以由 PublishSubject 驱动。这满足了一次性要求。相反,如果您需要响应可能在您订阅之前发出的事件,您可以使用 BehaviorSubject<Optional<T>> 并使用 createDefault(absent<T>()) 创建它,使用 onNext(Optional.of(navigationObject)) 触发它然后让 VM 知道什么时候发生导航,然后 VM 可以使用 onNext(absent())

清除它

或者,如果您想将其处理成某种包罗万象的 redux/mvi-like 状态,您可能会有某种状态 class,其中所有状态都包括某些 属性 指示视图要导航到某处,在 receiving/acting 上,View 会告诉 VM 它已经这样做了,VM 会将状态设置为与当前状态相同但没有导航。例如(在 Kotlin 中)state = state.copy(navigateToX = false)

对于您的活动,一般来说,您可以使用这样的东西:

/**
 * Used as a wrapper for data that is exposed via a LiveData that represents an event.
 */
open class Event<out T>(private val content: T) {

    @Suppress("MemberVisibilityCanBePrivate")
    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.getShowAlertFragment().observe(viewLifecycleOwner, Event{
        event?.getContentIfNotHandled()?.let {
            // your code here
    }
})

但是为了让它更简单并且实际上避免检查内容之前是否已在任何地方使用过,您可以创建另一个 class 来扩展观察者并在那里进行检查:

/**
 * An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has
 * already been handled.
 *
 * [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled.
 */
class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T>> {
    override fun onChanged(event: Event<T>?) {
        event?.getContentIfNotHandled()?.let {
            onEventUnhandledContent(it)
        }
    }
}

现在,您可以将代码简化为如下所示:

viewModel.getShowAlertFragment().observe(viewLifecycleOwner, EventObserver {
        // do you stuff here and you know it will only be called once
    }
})

这个想法取自此文件 Android 个示例 Here