背景
自从Google在I/O上宣布架构组件以来,网上已经有很多关于MVVM架构的讨论,所以许多喜欢Presenters的开发人员已经开始接受ViewModel世界,使用ViewModels可以减少样板代码,在配置更改期间管理数据,使得在多个片段之间共享数据变得容易。然而,它跟View之间的通信就变的更加难了
问题
我们举一个编辑配置文件屏幕的例子。在发送到服务器之前,用户数据必须进行数据验证,这意味着Presenter / ViewModel应该能够向View显示/隐藏提示告知用户。此外,如果在配置更改期间,那么也应该通知View。
1-Atiy0-oRTPDR4GGwjw5cow.png
Presenters和ViewModel不应包含对视图的引用。
对于Presenter类,我们通常定义某种契约接口,其中View实现Contract.view和Presenter实现Contract.presenter。现在,Presenter可以轻松地在View上调用方法,而无需任何activity/fragment 实例。
interface EditProfileContract {
interface view {
fun setProgress(show: Boolean)
fun showEmptyFirstNameError()
fun showEmptyLastNameError()
}
interface presenter {
fun saveProfile(firstName: String, lastName: String, bio: String, email: String, city: City, gender: String)
}
}
另一方面,ViewModel与View关系是非常松散的。我们通常使用LiveData或RxJava公开数据。一旦View订阅了ViewModel,它就会开始接收更新。如果是将数据传递给Views时,这方式是非常优秀的。但是当ViewModel需要传达View的状态、进度指示器状态、验证/服务器错误提示等等,会出现问题了。
解决方法
LiveData / Observable越少越好。所以我们正在寻找的是一种聚合需要传递给View的信息的方法。在大多数情况下,ViewModel需要传达三件事:
一:Data →这是需要在View上显示的内容,大多数代码示例都显示了如何将数据从ViewModel传递到View。
二、Status→这是传给View的错误状态如验证错误、服务器错误等等
三、State →UI状态,如进度指示器,对话框,每次开始观察ViewModel数据时都应传递给View。我们只需创建一个数据类来保存状态。
为了简洁起见,Data 我就不讲了大家都知道,我将其遗漏,主要讲讲 status 和state的设计
对于status的设计
enum class Status {
SUCCESS,
ERROR,
NO_NETWORK,
EMPTY_FIRST_NAME,
EMPTY_LAST_NAME,
EMPTY_CITY,
INVALID_URI
}
LiveData 不提供任何开箱的接口,但是创建一个名为SingleLiveEvent的实例,将LiveData状态公开给View层。可以参考Google的例子SingleLiveEvent
private val status = SingleLiveEvent<Status>()
fun getStatus(): LiveData<Status> {
return status
}
fun handleImage(intent: Intent?) {
intent?.data?.let {
avatar.value = it.toString()
} ?: run { status.value = Status.INVALID_URI }
}
View只需要观察状态LiveData并根据不同的状态/错误执行操作,在这个例子中,我们可以轻松地为每个错误显示不同的toast / snackbar。
viewModel.getStatus().observe(this, Observer { handleStatus(it) })
private fun handleStatus(status: Status?) {
when (status) {
Status.EMPTY_FIRST_NAME -> Toast.makeText(activity, "Please enter your first name!", Toast.LENGTH_SHORT).show()
Status.EMPTY_LAST_NAME -> Toast.makeText(activity, "Please enter your last name", Toast.LENGTH_SHORT).show()
Status.EMPTY_CITY -> Toast.makeText(activity, "Please choose your home city", Toast.LENGTH_SHORT).show()
Status.INVALID_URI -> Toast.makeText(activity, "Unable to load the photo", Toast.LENGTH_SHORT).show()
Status.SUCCESS -> {
startActivity(HomeFragment.newIntent(activity))
activity.finish()
}
else -> Toast.makeText(activity, "Something went wrong, please try again!", Toast.LENGTH_SHORT).show()
}
}
对于State的设计
每次开始观察ViewModel数据时都应传递给View。我们只需创建一个数据类来保存状态。
data class EditProfileState(
var isProgressIndicatorShown: Boolean = false,
var isCityDialogShown: Boolean = false,
var isGenderDialogShown: Boolean = false)
在ViewModel中将状态创建为MutableLiveData。由于我们只将LiveData暴露给View层,我们还应该为View提供setter来更新它的状态。
private val state = MutableLiveData<EditProfileState>()
fun getState(): LiveData<EditProfileState> {
return state
}
fun setProgressIndicator(isProgressIndicatorShown: Boolean) {
state.value?.isProgressIndicatorShown = isProgressIndicatorShown
}
fun setCityDialogState(isCityDialogShown: Boolean) {
state.value?.isCityDialogShown = isCityDialogShown
}
fun setGenderDialogState(isGenderDialogShown: Boolean) {
state.value?.isGenderDialogShown = isGenderDialogShown
}
根据状态数据在View图层中显示或隐藏City / Gender对话框。
总结
聚合信息(加载status,UIstate,errors)将有助于保持ViewModel的精简和清洁。、
推荐一下:
Android博客周刊 :每周分享国内外热门技术博客以及优秀的类库、Google视频、面试经历。
最新源码汇总:每周分享新的开源代码,有效果图,更直观。
请关注公众号,有惊喜。
网友评论