Я боролся с той же проблемой на работе и могу поделиться тем, что у нас работает.Мы разрабатываем 100% в Kotlin, поэтому следующие примеры кода будут также:
Состояние интерфейса
Чтобы предотвратить вздутие ViewModel
с большим количеством свойств LiveData
, выставьтеодин ViewState
для просмотра (Activity
или Fragment
) для наблюдения.Он может содержать данные, ранее представленные кратным LiveData
, и любую другую информацию, которая может потребоваться для правильного отображения представления:
data class LoginViewState (
val user: String = "",
val password: String = "",
val checking: Boolean = false
)
Обратите внимание, что я использую класс Data с неизменяемыми свойствамидля государства и сознательно не использовать ресурсы Android.Это не является чем-то специфичным для MVVM, но неизменяемое состояние просмотра предотвращает несоответствия пользовательского интерфейса и проблемы с многопоточностью.
Внутри ViewModel
создайте свойство LiveData
, чтобы отобразить состояние и инициализировать его:
class LoginViewModel : ViewModel() {
private val _state = MutableLiveData<LoginViewState>()
val state : LiveData<LoginViewState> get() = _state
init {
_state.value = LoginViewState()
}
}
Чтобы затем выдать новое состояние, используйте функцию copy
, предоставляемую классом данных Kotlin, из любого места внутри ViewModel
:
_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
, чтобы объединить их в одно состояние:
private val _state = MediatorLiveData<LoginViewState>()
val state : LiveData<LoginViewState> get() = _state
_state.addSource(databaseUserLiveData, { name ->
_state.value = _state.value!!.copy(user = name)
})
...
Привязка данных
Поскольку унифицировано,immutable ViewState
существенно нарушает механизм уведомления библиотеки привязки данных, мы используем изменяемый BindingState
, который расширяет BaseObservable
, чтобы выборочно уведомлять макет изменений.Он предоставляет функцию 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
и вызовите refresh
из Observer
:
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 на GitHub .
Это библиотека, которую я создал специально для обработки неизменяемых состояний просмотра и привязки данных с помощью ViewModel
и LiveData
, а также для склеивания их вместес операциями системы Android и бизнес-применениями.Документация идет глубже, чем я могу предоставить здесь.