我在工作中遇到了同样的问题,可以分享对我们有用的方法。我们正在 100% 使用 Kotlin 进行开发,因此以下代码示例也将如此。
界面状态
为防止 ViewModel 因大量 LiveData 属性而变得臃肿,请公开单个 ViewState 以供查看(Activity 或 Fragment)进行观察。它可能包含先前由多个LiveData 公开的数据以及视图可能需要正确显示的任何其他信息:
data class LoginViewState (
val user: String = "",
val password: String = "",
val checking: Boolean = false
)
请注意,我正在使用具有不可变状态属性的 Data 类,并且故意不使用任何 Android 资源。这不是 MVVM 特有的,但不可变的视图状态可以防止 UI 不一致和线程问题。
在ViewModel 内部创建一个LiveData 属性以公开状态并对其进行初始化:
class LoginViewModel : ViewModel() {
private val _state = MutableLiveData<LoginViewState>()
val state : LiveData<LoginViewState> get() = _state
init {
_state.value = LoginViewState()
}
}
然后要发出新状态,请在 ViewModel 内的任何位置使用 Kotlin 的 Data 类提供的 copy 函数:
_state.value = _state.value!!.copy(checking = true)
在视图中,像观察任何其他LiveData 一样观察状态并相应地更新布局。在视图层中,您可以将状态的属性转换为实际的视图可见性,并使用具有对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 将它们合并为单一状态:
private val _state = MediatorLiveData<LoginViewState>()
val state : LiveData<LoginViewState> get() = _state
_state.addSource(databaseUserLiveData, { name ->
_state.value = _state.value!!.copy(user = name)
})
...
数据绑定
由于统一的、不可变的ViewState 本质上破坏了数据绑定库的通知机制,我们使用一个扩展BaseObservable 的可变BindingState 来选择性地通知布局更改。它提供了一个refresh函数,接收对应的ViewState:
更新:删除了检查更改值的 if 语句,因为数据绑定库已经负责仅呈现实际更改的值。感谢@CarsonHolzheimer
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>
高级信息
某些样板文件肯定会受益于扩展功能和委托属性,例如更新ViewState 和通知BindingState 中的更改。
如果您想了解更多关于使用“干净”架构使用架构组件处理状态和状态的信息,您可以查看Eiffel on GitHub。
这是我专门创建的一个库,用于处理不可变视图状态和与ViewModel 和LiveData 的数据绑定,并将其与 Android 系统操作和业务用例粘合在一起。
文档比我在这里提供的更深入。