知识的领域是无限的,我们的学习也是无限期的。这篇文章主要讲述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
属性,暴露一个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() = _stateinit {
_state.value = https://www.songbingjia.com/android/LoginViewState()
}
}
然后发出一个新的状态,在
copy
的任何地方使用Kotlin的Data类提供的ViewModel
函数:_state.value = https://www.songbingjia.com/android/_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 = https://www.songbingjia.com/android/_state.value!!.copy(user = name)
})
...
数据绑定由于统一的,不可变的
ViewState
基本上打破了数据绑定库的通知机制,我们使用可扩展的BindingState
扩展BaseObservable
以选择性地通知更改的布局。它提供了refresh
函数,接收相应的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
的观察视图中创建一个属性,并从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
中的更改。如果您想了解有关使用“干净”架构的Architecture Components处理状态和状态的更多信息,您可以查看Eiffel on GitHub。
这是我专门为处理不可变视图状态和
ViewModel
和LiveData
数据绑定而创建的库,以及将它与Android系统操作和业务用例粘合在一起。文档比我在这里提供的内容更深入。另一答案 I've designed a pattern based on the Unidirectional Data Flow using Kotlin with LiveData.
查看完整的中文帖子或YouTube演讲,以获得深入的解释。
中 -Android Unidirectional Data Flow with LiveData
YouTube-Unidirectional Data Flow - Adam Hurwitz - Medellín Android Meetup
Code Overview第1步,共6步 - 定义模型ViewState.kt
// Immutable ViewState attributes.
data class ViewState(val contentList:LiveData<
PagedList<
Content>
>
, ...)// View sends to business logic.
sealed class ViewEvent {
data class ScreenLoad(...) : ViewEvent()
...
}// Business logic sends to UI.
sealed class ViewEffect {
class UpdateAds : ViewEffect()
...
}
第2步,共6步 - 将事件传递给ViewModelFragment.kt
private val viewEvent: LiveData<
Event<
ViewEvent>
>
get() = _viewEvent
private val _viewEvent = MutableLiveData<
Event<
ViewEvent>
>
()override fun onCreate(savedInstanceState: Bundle?) {
...
if (savedInstanceState == null)
_viewEvent.value = https://www.songbingjia.com/android/Event(ScreenLoad(...))
}override fun onResume() {
super.onResume()
viewEvent.observe(viewLifecycleOwner, EventObserver { event ->
contentViewModel.processEvent(event)
})
}
第3步,共6步 - 处理事件ViewModel.kt
val viewState: LiveData<
ViewState>
get() = _viewState
val viewEffect: LiveData<
Event<
ViewEffect>
>
get() = _viewEffectprivate val _viewState = MutableLiveData<
ViewState>
()
private val _viewEffect = MutableLiveData<
Event<
ViewEffect>
>
()fun processEvent(event: ViewEvent) {
when (event) {
is ViewEvent.ScreenLoad ->
{
// Populate view state based on network request response.
_viewState.value = https://www.songbingjia.com/android/ContentViewState(getMainFeed(...),...)
_viewEffect.value = Event(UpdateAds())
}
...
}
第4步,共6步 - 使用LCE模式管理网络请求LCE.kt
sealed class Lce<
T>
{
class Loading<
T>
: Lce<
T>
()
data class Content<
T>
(val packet: T) : Lce<
T>
()
data class Error<
T>
(val packet: T) : Lce<
T>
()
}
Result.kt
sealed class Result {
data class PagedListResult(
val pagedList: LiveData<
PagedList<
Content>
>
?,
val errorMessage: String): ContentResult()
...
}
Repository.kt
fun getMainFeed(...)= MutableLiveData<
Lce<
Result.PagedListResult>
>
().also { lce ->
lce.value = https://www.songbingjia.com/android/Lce.Loading()
/* Firestore request here. */.addOnCompleteListener {
// Save data.
lce.value = Lce.Content(ContentResult.PagedListResult(...))
}.addOnFailureListener {
lce.value = Lce.Error(ContentResult.PagedListResult(...))
}
}
第5步,共6步 - 处理LCE国家【Android - MVVM中ViewModel状态的最佳实践()】ViewModel.kt
private fun getMainFeed(...) = Transformations.switchMap(repository.getFeed(...)) {
lce ->
when (lce) {
// SwitchMap must be observed for data to be emitted in ViewModel.
is Lce.Loading ->
Transformations.switchMap(/*Get data from Room Db.*/) {
pagedList ->
MutableLiveData<
PagedList<
Content>
>
().apply {
this.value = https://www.songbingjia.com/android/pagedList
}
}
is Lce.Content ->
Transformations.switchMap(lce.packet.pagedList!!) {
pagedList ->
MutableLiveData<
PagedList<
Content>
>
().apply {
this.value = pagedList
}
}
is Lce.Error ->
{
_viewEffect.value = Event(SnackBar(...))
Transformations.switchMap(/*Get data from Room Db.*/) {
pagedList ->
MutableLiveData<
PagedList<
Content>
>
().apply {
this.value = pagedList
}
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 无法使用数据绑定Android与ViewModel中的XML通信
- 通过mvvm android进行异常处理
- 在MVVM架构Android中启动服务的正确位置是什么
- Android中的MVVMCross绑定不起作用
- 使用Android MVP Clean Architecture实现交互者
- Android登录屏幕(双向绑定EditText和TextView)
- 没有Dagger2的Android Kotlin MVVM结构
- android viewmodels之间的inter通信
- 如何从Xamarin Android Activity中调用MvxViewModel()