ViewModel:一次性事件反模式

发表在
6分钟阅读 2022年6月1日

- - -

ViewModel事件行动源于ViewModel, UI应该执行。例如,向用户显示一条消息,或者导航到另一个屏幕,当应用程序状态的变化。

我们指导ViewModel事件固执己见的在两种不同的方式:

  1. 只要一次性事件起源于ViewModel,ViewModel应该立即处理该事件导致状态更新。ViewModel应该只公开应用程序状态。没有暴露事件减少从视图模型状态意味着ViewModel不是真理的来源来自这些事件的状态;单向数据流(UDF)描述的优点只有消费者,比他们的生产者发送事件。
  2. 国家应该使用一个可观测的数据夹类型。
UDF之后,状态流从UI视图模型,和事件从UI视图模型

在你的应用程序,你可能会暴露UI使用ViewModel事件芬兰湾的科特林通道或其他反应流等SharedFlow你看过,也许这是一个模式在其他项目。当生产者(视图模型)比消费者(UI-Compose或观点),可与ViewModel事件中,这些api不保证的交付和处理这些事件。这可能导致bug给开发人员和未来的问题,这也是一个不可接受的用户体验对于大多数应用程序

你应该立即处理ViewModel事件,导致UI状态更新。试图揭露事件作为一个对象使用通道等反应的解决方案或SharedFlow并不能保证交货和处理事件。

案例研究

这里有一个例子的实现视图模型在一个应用程序的典型的支付流。下面的代码片段中,MakePaymentViewModel直接告诉UI导航到付款结果屏幕当付款请求的结果回来。我们将使用这个例子中探索为什么这样处理一次性ViewModel事件带来的问题和工程成本的上升。

UI将使用这个事件和相应的导航:

navigateToPaymentResultScreen实现上面有多个设计缺陷。

反模式# 1:关于付款完成的状态可以丢失

一个通道并不能保证的交付和处理事件。因此,事件可能会丢失,使UI处于不一致的状态。这可能发生在UI的一个例子(消费者)的背景和停止通道收集后视图模型(生产商)发送一个事件。也是表示对其他api,并不是一个可观测的数据夹类型等SharedFlow,这可能发出事件即使没有消费者听他们。

这是一个反模式,因为支付结果状态在UI层建模不是耐用原子如果我们考虑它的酸的事务。付款可能成功存储库而言,但是我们从来没有搬到合适的下一个屏幕。

注意:此反模式可以减轻通过使用Dispatchers.Main.immediate当发送和接收事件。但是,如果这不是强制的线头检查,这个解决方案可以容易出错的开发者很容易忘记。

反模式# 2:告诉UI采取行动

对于一个应用程序,支持多种屏幕尺寸,UI动作执行给定ViewModel事件根据屏幕大小可能不同。例如,案例研究时要导航到付款结果屏幕应用程序上运行的手机;但如果应用在平板电脑上运行,动作可以显示相同的结果在不同的部分屏幕。

ViewModel应该告诉UI什么应用状态和UI应该确定如何来反映。ViewModel不应该告诉它应该采取行动的UI。

反模式# 3:不立即处理的一次性事件

建模和一些事件火和忘记-在飞行中是导致问题。这是难以遵守ACID属性最高,所以可能不能保证数据的可靠性和完整性。状态、事件发生。事件并不是处理时间越长,问题就变得越困难。ViewModel事件,尽快处理事件,生成一个新的UI状态。

在案例研究中,我们创建了一个事件,表示为一个对象布尔——,并把它使用相接触通道:

/ /创建通道事件建模为一个布尔值
val _navigateToPaymentResultScreen = <布尔>频道()
/ /触发事件
_navigateToPaymentResultScreen.send (isPaymentSuccessful)

一旦你这样做,你的责任确保只有一次交付和处理。如果你必须模型事件作为一个对象出于某种原因,限制其寿命尽可能短,以便它不会有机会迷路。

处理一次性事件在ViewModel通常可以归结为一个方法调用——例如,更新UI状态。一旦你调用这个方法,你知道它成功完成或者抛出一个异常,你知道它到底发生了一次。

案例研究改进

如果你在这些情况下,重新考虑什么一次性ViewModel事件实际上意味着对你的UI。立即处理它们,减少他们UI状态暴露使用一个可观测的数据等StateFlowmutableStateOf

UI状态更好的代表UI在给定的时间点,它会给你更多的交付和处理担保,通常更容易测试和持续集成与应用程序的其余部分。

如果你很难找到一个方法来减少一次性ViewModel事件状态,考虑为你的UI事件实际上意味着什么。

在上面的示例中,视图模型应该公开的实际付款应用程序数据——数据在这种情况下,而不是告诉UI动作。下面是更好的表示,ViewModel事件处理和减少,,并使用一个可观测的数据夹类型相接触。

在上面的代码中,事件处理立即通过调用_uiState.update(# L28)与新paymentResult数据(# L31);没有为这个事件现在迷路了。事件已经减少到状态,paymentResultMakePaymentUiState反映了支付结果应用程序数据。

,用户界面反应paymentResult改变并采取相应行动。

注意:如果你的用例的活动没有完成()和backstack保存在你的视图模型需要公开函数来清除paymentResult从UiState(即设置字段活动开始后),将调用另一个。可以找到一个这样的例子消费事件可以触发状态更新部分的文档。

正如前面提到的,UI层的额外的考虑部分,你可以让UI屏幕与多个流的状态,如果这就是你的用例需要。重要的是,这些流是可观测的数据夹类型。在上面的示例中,一个独特的暴露,因为UI状态流isLoading国旗和paymentResult财产高度交织在一起。分离出来可能会导致不一致在ui的例子中,如果isLoading真正的paymentResult不是。通过一起在同一个UiState类,我们更加意识到不同领域,使UI屏幕的状态,让我们更少的错误。

希望这篇文章帮助你理解的原因为什么我们建议1)一次性ViewModel事件处理的立即和减少他们的状态,和2)暴露状态使用一个可观测的数据夹类型。我们相信这种方法保证加工运输给你更多,通常更容易测试和持续集成与应用程序的其余部分。

免责声明:与其他体系结构指导,把这作为一个准则,它适应您的需求。

关于这个主题的更多信息,请查看UI事件文档

特别感谢亚当•鲍威尔无休止的讨论、知识和输入他的这篇博客。也,啤酒Stamato何塞Alcerreca彻底的审查。

- - -

- - -

Baidu