ViewModel的使用

背景

在制作安卓应用时发现,
可以通过绑定view上数据到ViewModel来实现view中元素赋值的简化,
因此想了解下ViewModel的具体情况,
但遇到一个羁绊–MVP模式,
一并来介绍下

MVP模式

是MVC模式的变种.
指的是model, view, presenter.
在presenter中调用model来更改model的内容,这个model不是ViewModel,
同时preseter不断调用view的接口来实现对view的操作.
原因是:

Presenters不应包含对视图的引用

这样可以

  • 分离视图,数据,逻辑的代码
  • view完全只负责显示,变得被动化
  • 可以解决一个activity中两个fragment的通信问题

但有些缺点

  • presenter负责的部分过多,既要管理界面逻辑,又要管理业务逻辑
  • presenter严重依赖view的接口,业务变更后需要改动的文件多,非常繁琐
    • view的接口声明
    • view的接口实现
    • presenter调用接口的代码

MVVM模式

是MVC的另一种变种.
指的是model, view, ViewModel
此处ViewModel代替了presenter原先的作用,
改进在于

  • ViewModel和view能够双向绑定,免去接口的使用,实现了数据驱动UI
    • 借用livedata可以将ViewModel的变动更新到view
    • 在view中在onchangelistener中调用ViewModel的方法可以把值的变动写入到ViewModel
  • livedata和ViewModel配合使用能够解决声明周期问题引起的activity在重新配置后数据丢失的问题

另外angular中常用的vm.xxx中的vm也是指ViewModel

实践

普通的一个文件中包办

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class MainActivity : AppCompatActivity() {
lateinit var show: TextView
// 没有model
private var bookName = "bookName"
private var price = 1
private var author = "author"

private fun refreshView() {
// 没有model的方法
show.text = "$bookName: $price, $author"
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// 为方便,layout使用anko来写了
verticalLayout {
show = textView("test")
button("plus1").onClick {
// 视为业务逻辑
price += 1

// 视为页面逻辑
refreshView()
}
}
}
}

MVP模式

首先提取出model

1
2
3
4
5
6
7
// 定义好模型的成员
data class Book(var bookName:String, var price: Int, var author: String) {
// 定义模型的方法
fun beforeRefresh(): String {
return "$bookName: $price, $author"
}
}

然后是被动显示的view

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 声明接口供presenter调用
interface IView {
// 这里可见MVP模式的繁琐,总是要使用接口
fun refreshView(str: String)
}

class MainActivity : AppCompatActivity(), IView {
lateinit var show: TextView
private lateinit var book: Book
lateinit var ipresenter: IPresenter

override fun refreshView(str: String) {
// view完全是被动显示
show.text = str
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

book = Book("bookName", 1, "author")
ipresenter = IPresenter(book, this)

verticalLayout {
show = textView("test")
button("plus1").onClick {
// 业务委托给了presenter
// 有时这里的plus1也是一个接口,具体的presenter提供具体的实现
ipresenter.plus1()
}
}
}
}

最后是执行页面逻辑和业务逻辑的presenter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 常常在多个presenter有着相似行为的时候使用
interface Ipresent {
fun plus1()
}

class IPresenter(private var book: Book, private var activity: IView) : Ipresent{

override fun plus1() {
// 直接操作model
book.price += 1

// 以接口的形式调用view的方法
activity.refreshView(book.beforeRefresh())
}

}

MVVM模式

model可以完全不变
使用ViewModel需要在gradle里声明使用的包,目前最新的是
这里包含了livedata和ViewModel

1
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

view摆脱了接口,迎来了observer的绑定,同时可以承担一部分的页面逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MainActivity : AppCompatActivity() {
private lateinit var show: TextView
// ViewModel成了内部成员,不再需要接口
private val mMainActivityViewModel by lazy {
ViewModelProvider(this).get(MainActivityViewModel::class.java)
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

verticalLayout {
show = textView("test")
button("plus1").onClick {
mMainActivityViewModel.plus1()
}
}

mMainActivityViewModel.displayStr.observe(this, Observer<String> {
// 可以承担一部分页面逻辑,比如it.toInt等等
show.text = it
})
}
}

ViewModel里面保留了原先的业务操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MainActivityViewModel : ViewModel() {
private val mDisplayStr = MutableLiveData<String> ()
val displayStr: LiveData<String>
get() = mDisplayStr

// ViewModel仍然操作model
private var book = Book("bookName", 1, "author")

fun plus1(){
// 业务逻辑等
book.price += 1
// 更新自身的值
mDisplayStr.postValue(book.beforeRefresh())
}
}

livedata如何解决activity重新配置问题

当配置发生更改,activity的行为是 onPause(), onStop(), onDestroy(),
onCreate(), onStart(), onResume() (参考官方说明)

ViewModel是使用ViewModelProvider创建的,
而实现上,ViewModel被保存在缓存中的ViewModelStore中

从onDestroy的实现来看,如果是配置更改引起的onDestroy,ViewModelStore不发出clear信号.

如何将view变动通知ViewModel

可以在onchangelistener中调用viewmodel的setter,
如果需要实时实现效果,可以定义扩展函数来增强功能,
减少调用时候的代码.
其次也可以不是实时的,
但这两种方法都不能令人满意.

谷歌有推出databinding,但需要借用DataBindingUtil.inflate(xx, layout~id~)
同时在xml中使用特殊写法 @={} 写上要bind的对象.

如何绑定大量的数据到view?

如果使用databinding,则可以不需要显式指明要observe的对象,算是一个解决办法.

参考

  1. mvp与mvvm的区别
  2. livedata的用法
  3. ViewModel应该提供哪些信息