将事件从ViewModel发送到MVVM中的活动/片段

今天,我们将讨论如何在MVVM中的Activity / Fragments和ViewModel之间交换事件。 要从ViewModel获取数据,建议在Activity / Fragment中订阅在ViewModel中找到的LiveData数据。 但是如何发送单个(不仅是)事件,例如显示通知或打开另一个片段?



大家好!

我叫Alexei,我是Home Credit Bank的一名Android开发人员。

在本文中,我将分享我们从ViewModels向视图发送和接收事件的方式(活动/片段)。

在我们的应用程序“家庭信用银行的分期付款商品”中,我们使用了片段,因此我们将讨论它们,但是所有内容都与活动有关。

我们想要什么?


我们有一个Fragment,其中包括几个ViewModel,数据由DataBinding 。 所有用户事件都接收ViewModel。 如果该事件是一个导航事件:您需要打开另一个片段/活动,显示AlertDialogSnackbar ,系统对Permissions的请求等,则应在片段中执行这样的事件。

到底是什么问题?


视图和ViewModels生命周期是不相关的。 与侦听器绑定是不可能的,因为ViewModel应该不了解有关片段的任何信息,也不应包含指向片段的链接,否则,正如您所知,内存将开始“ 欢喜 ”。

片段和ViewModels之间交互的标准方法是订阅位于ViewModel中的LiveData 。 由于这种方法未考虑事件是否已经完成,因此无法直接通过LiveData传输事件。

存在哪些解决方案:


1.使用SingleLiveEvent
优点:该事件执行一次。
缺点:一个事件-一个SingleLiveEvent 。 对于大量事件,N个事件对象出现在ViewModel中,每个事件对象都必须在片段中进行预订。

2.一个很好的例子
优点:一个事件也执行一次,您可以将数据从viewModel传输到片段。
缺点:需要事件中的数据,但是如果您需要在没有数据的情况下执行事件(val content: T) ,则需要创建另一个类。 它不能解决一次执行一种类型的事件的问题(事件本身只执行一次,但是这种类型的事件将被执行与从ViewModel运行它一样多的次数)。 例如,N个请求异步进行,但是没有网络。 每个请求都会返回一个网络错误,并将拉入N个有关网络错误的事件的片段,N个警报将在该片段中打开。 用户不会批准这样的决定:)。 我们应该向他显示一条错误消息。 换句话说,这种类型的事件应执行一次。

解决方案


我们以SingleLiveEvent的想法为基础保存事件处理信息。

定义可能的事件类型


 enum class Type { EXECUTE_WITHOUT_LIMITS, //    –   ,             EXECUTE_ONCE, //      WAIT_OBSERVER_IF_NEEDED,//    ,     -       WAIT_OBSERVER_IF_NEEDED_AND_EXECUTE_ONCE //    ,     -           } 

创建事件基类NavigationEvent


isHandled指示是否接收到事件(如果在片段中接收到Observer,我们认为该事件已执行)。

 open class NavigationEvent(var isHandled: Boolean = false, var type: Events.Type) 

创建一个Emitter类- Emitter


事件发射器类继承自LiveData < NavigationEvent >。 此类将在ViewModel中用于调度事件。

 class Emitter : MutableLiveData<NavigationEvent>() { private val waitingEvents: ArrayList<NavigationEvent> = ArrayList() private var isActive = false override fun onInactive() { isActive = false } override fun onActive() { isActive = true val postingEvents = ArrayList<NavigationEvent>() waitingEvents .forEach { if (hasObservers()) { this.value = it postingEvents.add(it) } }.also { waitingEvents.removeAll(postingEvents) } } private fun newEvent(event: NavigationEvent, type: Type) { event.type = type this.value = when (type) { Type.EXECUTE_WITHOUT_LIMITS, Type.EXECUTE_ONCE -> if (hasObservers()) event else null Type.WAIT_OBSERVER_IF_NEEDED, Type.WAIT_OBSERVER_IF_NEEDED_AND_EXECUTE_ONCE -> { if (hasObservers() && isActive) event else { waitingEvents.add(event) null } } } } /** Clear All Waiting Events */ fun clearWaitingEvents() = waitingEvents.clear() } 

isActive我们需要了解是否至少有一个Observer订阅了Emitter 。 如果订户已经出现并且等待他的事件已经累积,我们将发送这些事件。 一个重要的澄清:发送事件不是通过this.postValue(event)而是通过setter this.value = event是必要的。 否则,订阅者将仅收到列表中的最后一个事件。

发送新事件的方法newEvent(event, type)接受两个参数-实际上,事件本身和此事件的类型。

为了不记住所有类型的事件(长名称),我们将创建单独的公共方法,这些方法仅接受事件本身:

 class Emitter : MutableLiveData<NavigationEvent>() { … /** Default: Emit Event for Execution */ fun emitAndExecute(event: NavigationEvent) = newEvent(event, Type.EXECUTE_WITHOUT_LIMITS) /** Emit Event for Execution Once */ fun emitAndExecuteOnce(event: NavigationEvent) = newEvent(event, Type.EXECUTE_ONCE) /** Wait Observer Available and Emit Event for Execution */ fun waitAndExecute(event: NavigationEvent) = newEvent(event, Type.WAIT_OBSERVER_IF_NEEDED) /** Wait Observer Available and Emit Event for Execution Once */ fun waitAndExecuteOnce(event: NavigationEvent) = newEvent(event, Type.WAIT_OBSERVER_IF_NEEDED_AND_EXECUTE_ONCE) } 

正式地,您已经可以在ViewModel中订阅Emitter并接收事件,而不管它们的处理方式(无论事件是否已经处理)。

创建一个事件观察者类EventObserver


 class EventObserver(private val handlerBlock: (NavigationEvent) -> Unit) : Observer<NavigationEvent> { private val executedEvents: HashSet<String> = hashSetOf() /** Clear All Executed Events */ fun clearExecutedEvents() = executedEvents.clear() override fun onChanged(it: NavigationEvent?) { when (it?.type) { Type.EXECUTE_WITHOUT_LIMITS, Type.WAIT_OBSERVER_IF_NEEDED -> { if (!it.isHandled) { it.isHandled = true it.apply(handlerBlock) } } Type.EXECUTE_ONCE, Type.WAIT_OBSERVER_IF_NEEDED_AND_EXECUTE_ONCE -> { if (it.javaClass.simpleName !in executedEvents) { if (!it.isHandled) { it.isHandled = true executedEvents.add(it.javaClass.simpleName) it.apply(handlerBlock) } } } } } } 

该Observer接受高阶函数作为输入-事件处理将以片段形式编写(以下示例)。

clearExecutedEvents()方法,用于清除已执行的事件(那些本应执行一次的事件)。 更新屏幕时必需,例如,在swipeToRefresh()

好吧,实际上,这个观察者订阅了主要的onChange()方法,该方法在接收到新的发射器数据时发生。

如果事件具有无限次数的执行类型,则我们检查事件是否已执行并对其进行处理。 我们执行该事件,并指示已接收并处理该事件。

 if (!it.isHandled) { it.isHandled = true it.apply(handlerBlock) } 

如果该事件属于应该执行一次的类型,则检查该事件的类是否在哈希表中。 如果不是,请执行该事件并将该事件的类添加到哈希表中。

 if (it.javaClass.simpleName !in executedEvents) { if (!it.isHandled) { it.isHandled = true executedEvents.add(it.javaClass.simpleName) it.apply(handlerBlock) } } 

但是如何在事件内部传输数据?


为此,将MyFragmentNavigation接口,该接口将包含从NavigationEvent()继承的类。 在下面创建带有或不带有传递参数的各种类。

 interface MyFragmentNavigation { class ShowCategoryList : NavigationEvent() class OpenProduct(val productId: String, val productName: String) : NavigationEvent() class PlayVideo(val url: String) : NavigationEvent() class ShowNetworkError : NavigationEvent() } 

在实践中如何运作


从ViewModel发送事件:

 class MyViewModel : ViewModel() { val emitter = Events.Enitter() fun doOnShowCategoryListButtonClicked() = emitter.emitAndExecute(MyNavigation.ShowCategoryList()) fun doOnPlayClicked() = emitter.waitAndExecuteOnce(MyNavigation.PlayVideo(url = "https://site.com/abc")) fun doOnProductClicked() = emitter.emitAndExecute(MyNavigation.OpenProduct( productId = "123", productTitle = " Samsung") ) fun doOnNetworkError() = emitter.emitAndExecuteOnce(MyNavigation.ShowNetworkError()) fun doOnSwipeRefresh(){ emitter.clearWaitingEvents() ..//loadData() } } 

片段中接收事件:

 class MyFragment : Fragment() { private val navigationEventsObserver = Events.EventObserver { event -> when (event) { is MyFragmentNavigation.ShowCategoryList -> ShowCategoryList() is MyFragmentNavigation.PlayVideo -> videoPlayerView.loadUrl(event.url) is MyFragmentNavigation.OpenProduct -> openProduct(id = event.productId, name = event.otherInfo) is MyFragmentNavigation.ShowNetworkError -> showNetworkErrorAlert() } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // Observer   ViewModels     myViewModel.emitter.observe(viewLifecycleOwner, navigationEventsObserver) myViewModelSecond.emitter.observe(viewLifecycleOwner, navigationEventsObserver) myViewModelThird.emitter.observe(viewLifecycleOwner, navigationEventsObserver) } private fun ShowCategoryList(){ ... } private fun openProduct(id: String, name: String){ ... } private fun showNetworkErrorAlert(){ ... } } 

从本质上讲,我们仅基于LiveData得到了Rx- BehaviorSubjec ta和EventBus -a的LiveData ,其中Emitter可以在艺术家观察者出现之前收集事件,观察者可以监视事件的类型,并且在必要时仅调用一次。

欢迎在评论中提出建议。

链接到源
来自家庭信用银行的分期付款计划

Source: https://habr.com/ru/post/zh-CN477314/


All Articles