Android MVVM ViewModel的输入流

We’ve covered the output streams (ViewState and ActionState) in part 1

我们已经在第1部分中介绍了输出流(ViewState和ActionState)

Quick recap of the output streams in part 1:

第1部分中的输出流快速回顾:

  • MVVM leverages Observer pattern in which the ViewModel is the Subject while the View is the Observer

    MVVM利用观察者模式,其中ViewModel是主题,而View是观察者
  • But the View can have multiple Observers. I proposed a) ViewState Observer for UI updates like setting TextView’s text and b) ActionState Observer for events like “log out of all devices”

    但是视图可以有多个观察者。 我提出了a)ViewState Observer,用于UI更新,例如设置TextView的文本; b)ActionState Observer,用于诸如“注销所有设备”之类的事件

In this part, I’m omitting the ActionState Observer, as the ViewState Observer alone is sufficient to demonstrate the “good practice” I want to introduce here.

在这一部分中,我省略了ActionState观察器,因为仅ViewState观察器就足以证明我想在此处介绍的“良好实践”。

挑战 (The challenge)

Now after initialisation, there are important events with important data that need to be processed like onActivityResult , __Swipe to Refresh__. Only the View (Activity/ Fragment) receives them so it’s supposed to relay them to the ViewModel.

现在,初始化之后,需要处理具有重要数据的重要事件,例如onActivityResult ,__ Swipe to Refresh__。 只有视图(活动/片段)会接收它们,因此应该将它们中继到ViewModel。

How?

怎么样?

We end up doing something this (you are not alone, happened in my previous app)

我们最终做了这样的事情(您并不孤单,发生在我之前的应用中)

The View

风景

override fun onCreate(...) {
    viewModel.init(...)// Side note: don't re-init on rotation
}
override fun onActivityResult(...) {
    // process success/ failure, extract data
    viewModel.onActivityResult(...)
} 
fun onRefreshed() {
    viewModel.onRefreshed()
}
fun onSomeButtonClicked() {
    viewModel.onSomeButtonClicked()
}

It works but we can do better. The above approach opens up door for hard-to-debug side effects.

它有效,但我们可以做得更好。 上面的方法为难以调试的副作用打开了大门。

Example of side effects

副作用示例

The ViewModel

ViewModel

private val viewState = MutableLiveData<ViewState>()
private var someUsefulVariable: Int? = null // mutable!


internal fun init(...) {
  doStep1()
  //...
  .doStepK()
  .map { stepKResult -> 
        someUsefulVariable = ... // derived from stepKresult
        return@map processData(stepKResult, someUsefulVariable)
  }
  //...
  .handleResult { finalResult ->
                 viewState.value = ViewState.DataLoaded(...)
                }
}


internal fun onActivityResult() {
  //...
  someUsefulVariable = -1 // random value here, no particular meaning
}
internal fun onRefreshed() {
  //...
  someUsefulVariable = null // random value here, no particular meaning
}
internal fun onSomeButtonClicked() {
  //...
  someUsefulVariable = 0 // random value here, no particular meaning
}

in which

在其中

  • someUsefulVariable is mutable and it affects the final result of the main logic of the ViewModel. (In a perfect world where we can a perfect stateless ViewModel, this won’t exist. In reality, there have many cases of this in the thousands of ViewModels I’ve worked with in the last 2.5 years in 2 different tech unicorns and I firmly believe they’ll never cease to exist)

    someUsefulVariable是可变的,它影响ViewModel主要逻辑的最终结果。 (在一个完美的世界中,我们可以拥有一个完美的无状态ViewModel,这是不存在的。实际上,在过去的2.5年中,我在2个不同的技术独角兽公司中使用的成千上万个ViewModel中有很多这种情况。坚信他们永远不会停止存在)

  • The main logic of the ViewModel doStep1()...doStepK()...handleResult { finalResult -> can be complex and consists of many step and takes some time to finish

    ViewModel doStep1()...doStepK()...handleResult { finalResult ->的主要逻辑可能很复杂,包含很多步骤,需要一些时间才能完成

  • The syntax here like .handleResult { finalResult ->are imaginary. It could be RxJava’s .subscribe { or Kotlin Flow’s .collect {

    .handleResult { finalResult ->这样的语法是虚构的。 可能是RxJava的.subscribe {或Kotlin Flow的.collect {

As you can see, the final result of the main logic can be inadvertently changed when the View meddles with the ViewModel’s state post initialisation.

如您所见,当视图与初始化后的ViewModel状态混合在一起时,可能会无意中更改主要逻辑的最终结果。

To make matter worse, race condition can cause the final result to vary, thus render debugging super difficult

更糟糕的是,竞争条件可能导致最终结果有所不同,从而使调试变得异常困难

The diagram looks something like this:

该图如下所示:

Image for post
It’s hard to work with data flowing both ways
双向处理数据都很困难

Doesn’t it look like Model-View-Presenter (MVP) there?

它看起来不像Model-View-Presenter(MVP)吗?

视图-> ViewModel->视图-单向数据 (View -> ViewModel -> View — Unidirectional data)

Well, as covered part 1, data flow one way from the ViewModel’s Subject to the View’s Observer (Observer pattern). Why not add 1 more stream to provide the inputs like button clicks (from the View to the ViewModel)?

好吧,作为第1部分的介绍,数据从ViewModel的Subject到View的Observer( Observer模式 )的一种方式流动。 为什么不多添加1个流来提供按钮单击(从View到ViewModel)等输入呢?

I have been using RxJava’s PublishSubject or BehaviouralSubject (depending whether I need to emit a default value or not) for this purpose. Surely, there’s something similar in Kotlin Flow. (Is it Channels like BroadcastChannel?)

为此,我一直在使用RxJava的PublishSubject或BehaviouralSubject(取决于我是否需要发出默认值)。 当然,Kotlin Flow也有类似之处。 (是像BroadcastChannel这样的频道吗?)

The View

风景

val inputSubject = PublishSubject.create<String>() // immutable!
val inputSubjectReadOnly = inputSubject.debounce(500L, TimeUnit.MILLISECONDS)
val someViewModel = SomeViewModel(inputSubjectReadOnly, ...)


override fun onCreate(...) {
    someViewModel.init(...)
}


override fun onSomeButtonClicked(...) {
     // process success/ failure, extract data
     inputSubject.onNext(data) // emit to input stream ViewModel consumes
}

take note that inputSubjectReadOnly is read-only, therefore preventing the reverse side-effect i.e. the ViewModel cannot alter the inputs from the View!

请注意, inputSubjectReadOnly是只读的,因此避免了反作用,即ViewModel无法更改View的输入!

The ViewModel

ViewModel

class SomeViewModel(val inputStream: Observable<SomeDataType>, ...) {
private val viewState = MutableLiveData<ViewState>()
  
internal fun init(...) {
  doStep1()
  //...
  .doStepK()
  .zipWith(inputStream) // combine/ merge 2 streams into 1 using Pair
  .map { pair ->
        val stepKresult = pair.first
        val input = pair.second // data from button click
        someUsefulVariable = ... // derived from stepKresult and button click data
        return@map processData(stepKResult, someUsefulVariable)
       }
  .handleResult { finalResult -> viewState.value = ViewState.DataLoaded(...) }
}

Prerequisite: As you can this, Input Stream cannot be used without a reactive programming framework like Kotlin Flow or RxJava

先决条件 :如此,如果没有像Kotlin Flow或RxJava这样的React性编程框架,就不能使用Input Stream。

Now it seems easier to read and debug our ViewModel. Data seems to follow one way only

现在,似乎更容易阅读和调试我们的ViewModel。 数据似乎只遵循一种方式

Image for post
Data flows one way, easier to troubleshoot
数据以一种方式流动,更易于故障排除

翻译自: https://proandroiddev.com/supercharged-android-mvvm-input-stream-c117073760d7

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值