我有一个应用程序使用 Androids ViewModel 类和导航组件在片段之间导航。我将如何处理来自 ViewModel 的导航?我正在使用 RxJava,我正在考虑让 Fragments 监听导航事件,然后以这种方式触发导航。处理这个的正常方法是什么?如果有帮助,我也会使用 Dagger 进行依赖注入。
3 回答
根据LiveData with SnackBar、Navigation 和其他事件博客文章:
一些数据应该只使用一次,例如 Snackbar 消息、导航事件或对话框触发器。
与其尝试通过库或架构组件的扩展来解决这个问题,不如将其作为一个设计问题来面对。我们建议您将您的活动视为您所在州的一部分。
他们详细介绍了SingleLiveEvent类的使用,该类确保每个 Navigation 事件仅由 Observer 接收一次(即,您的 Fragment 可以访问您的NavController
)。
另一种选择是使用“事件包装器”模型,其中必须将事件显式标记为已处理。
如果你使用 MVP,P 只会调用 V 上触发导航的方法。
MVVM 中的等效方法是使用专用的 observable/listener/callback,如果使用 RxJava 完成,它可以由PublishSubject
. 这满足一次性要求。相反,如果您需要在订阅之前响应可能发出的事件,您可以使用 aBehaviorSubject<Optional<T>>
并创建它createDefault(absent<T>())
,使用触发它,onNext(Optional.of(navigationObject))
然后让 VM 知道导航发生时,然后 VM 可以使用清除它onNext(absent())
或者,如果你想将它工作到一些包罗万象的 redux/mvi 状态,你可能有一些包含所有状态的状态类,包括一些指示视图导航到某处的属性,在接收/对此采取行动时,视图会告诉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
}
})
但为了让它更简单,实际上避免在任何地方检查内容是否已被使用,您可以创建另一个扩展观察者的类并在那里进行检查:
/**
* 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