Android – MVVM中ViewModel状态的最佳实践?

我正在开发一个Android应用程序,使用LiveData上的MVVM模式(可能是Transformations)和View和ViewModel之间的DataBinding.由于应用程序“正在增长”,现在ViewModels包含大量数据,后者中的大多数都保存为LiveData以使视图订阅它们(当然,UI需要这些数据,不管是双向绑定如何每个EditTexts或单向绑定).我听到(和Google搜索)关于在ViewModel中保存代表UI状态的数据.但是,我发现的结果只是简单而通用.我想知道是否有人提示或者可以就此案例分享一些关于最佳实践的知识.简单来说,考虑到LiveData和DataBinding可用,在ViewModel中存储UI(View)状态的最佳方法是什么?提前感谢您的回答!

解决方法:

我在工作中遇到同样的问题,可以分享对我们有用的东西.我们在Kotlin开发100%,所以下面的代码示例也是如此.

UI状态

为了防止ViewModel因大量LiveData属性而变得臃肿,请为要观察的视图(Activity或Fragment)公开单个ViewState.它可能包含以前由多个LiveData公开的数据以及视图可能需要正确显示的任何其他信息:

data class LoginViewState (
    val user: String = "",
    val password: String = "",
    val checking: Boolean = false
)

Note, that I’m using a Data class with immutable properties for the state and deliberately don’t use any Android resources. This is not something specific to MVVM, but an immutable view state prevents UI inconsistencies and threading problems.

在ViewModel内部创建一个LiveData属性来公开状态并初始化它:

class LoginViewModel : ViewModel() {
    val state = MutableLiveData<LoginViewState>()

    init {
        state.value = LoginViewState()
    }
}

然后发出一个新状态,从ViewModel内的任何地方使用Kotlin的Data类提供的复制功能:

state.value = state.value!!.copy(checking = true)

在视图中,像观察任何其他LiveData一样观察状态并相应地更新布局.在View层中,您可以将状态属性转换为实际视图可见性,并使用具有对Context的完全访问权限的资源:

viewModel.state.observe(this, Observer {
    it?.let {
        userTextView.text = it.user
        passwordTextView.text = it.password
        checkingImageView.setImageResource(
            if (it.checking) R.drawable.checking else R.drawable.waiting
        )
    }
})

合并多个数据源

由于您之前可能在ViewModel中公开了数据库或网络调用的结果和数据,因此您可以使用MediatorLiveData将这些结果和数据混合到单个状态:

val state = MediatorLiveData<LoginViewState>()

state.addSource(databaseUserLiveData, { name ->
    state.value = state.value!!.copy(user = name)
})
...

数据绑定

由于统一的,不可变的ViewState基本上破坏了数据绑定库的通知机制,因此我们使用可扩展的BindingState来扩展BaseObservable以有选择地通知更改的布局.它提供了一个刷新函数,可以接收相应的ViewState:

更新:删除了if语句检查更改的值,因为数据绑定库已经只处理实际更改的值.感谢@CarsonH​​olzheimer

class LoginBindingState : BaseObservable() {
    @get:Bindable
    var user = ""
        private set(value) {
            field = value
            notifyPropertyChanged(BR.user)
        }

    @get:Bindable
    var password = ""
        private set(value) {
            field = value
            notifyPropertyChanged(BR.password)
        }

    @get:Bindable
    var checkingResId = R.drawable.waiting
        private set(value) {
            field = value
            notifyPropertyChanged(BR.checking)
        }

    fun refresh(state: AngryCatViewState) {
        user = state.user
        password = state.password
        checking = if (it.checking) R.drawable.checking else R.drawable.waiting
    }
}

在观察视图中为BindingState创建一个属性,并从Observer调用refresh:

private val state = LoginBindingState()

...

viewModel.state.observe(this, Observer { it?.let { state.refresh(it) } })
binding.state = state

然后,将状态用作布局中的任何其他变量:

<layout ...>

    <data>
        <variable name="state" type=".LoginBindingState"/>
    </data>

    ...

        <TextView
            ...
            android:text="@{state.user}"/>

        <TextView
            ...
            android:text="@{state.password}"/>

        <ImageView
            ...
            app:imageResource="@{state.checkingResId}"/>
    ...

</layout>

高级信息

一些样板文件肯定会受益于扩展函数和Delegated属性,例如更新ViewState和通知BindingState中的更改.

如果您想了解使用“干净”架构的架构组件的状态和状态处理的更多信息,您可以查看Eiffel on GitHub.

它是我专门为处理不可变视图状态和与ViewModel和LiveData进行数据绑定而创建的库,以及将它与Android系统操作和业务用例粘合在一起.
文档比我在这里提供的内容更深入.

上一篇:MVVM


下一篇:程序骨架(框架思想)