初探Jetpack(一) – ViewModel
初探Jetpack(二) – Lifecycles
LiveData 是 Jetpack 提供的一种响应式变成组件,它可以包含任何类型的数据,并在数据发现变化的时候,通知观察者。
LiveData 具有生命周期的感知能力,指它能感觉 activity,fragment ,service 的生命周期,且只有这些组件处于活跃生命周期状态时,LiveData 感觉到数据变化,才会发出通知。
什么意思呢?即如果 activity ,fragment 等观察者处于生命周期的 STARTED, ONRESUMED状态,则 LiveData 会认为该观察者处于活跃状态,LiveData 就会将最近的通知给活跃的观察者,而非活跃状态的观察者则收不到通知。
所以,当我们把 LiveData 和 有 LifecycleOwner 接口对象结合,当 Lifecycle 的对象状态为 DESTROYED 的时候,便可移除此观察者。 这对于 Activity 和 Fragment 特别有用,因为它们可以放心地观察 LiveData 对象而不必担心泄露(当 Activity 和 Fragment 的生命周期被销毁时,系统会立即退订它们)。
即我们不用手动去处理生命周期,它会自动管理这些操作。
创建 LiveData 实例以存储某种类型的数据。这通常在 ViewModel 类中完成。
在第一张章,我们学习了 ViewModel 的计时器功能 初探Jetpack(一) – ViewModel,这里再使用 LiveData 对它进行扩展:
class DataViewModel() :ViewModel() {
private val _counter = MutableLiveData()
val counter : LiveData get() = _counter;
init {
_counter.value = 0;
}
fun plusOne(){
val size = _counter.value ?: 0;
_counter.value = size+1;
}
}
从上面看到,我们申明了一个 MutableLiveData 对象,并指定泛型为 Int,表示它的数据为整型。
MutableLiveData 是一种可变的 LiveData,有三个方法,分别是 getValue(),setValue()和 postValue()。
// 这里用了 getValue 和 setValue 的语法糖
val size = _counter.value ?: 0;
_counter.value = size+1;
为了不让外面调用 MutableLiveData,把它设置成 private,且公布出去的 counter 是 MutableLiveData 的 get() 属性方法的返回值,这样 _counter 变量就对外面不可见了。
后面我们在activity 中,这样写:
val viewModel = ViewModelProvider(this).get(DataViewModel::class.java)
plusOne.setOnClickListener{
//点击增加
viewModel.plusOne()
}
//添加数据观察者
val textObserver = Observer { count ->
infoText.text = count.toString()
}
viewModel.counter.observe(this,textObserver)
注意看到,使用了 MutableLiveData 的observe方法,它接受两个参数,第一个参数为 LifecycleOwner,直接传递 this 即可,第二是是 observe 接口,当数据发生变化时,就会毁掉这里的接口。我们把数据更新到 textview 即可,效果如下:
第一行代码 第三版
官网 LifeData
LiveData 为了能够应对各种不同的需求场景,提供了 map 和 switchMap 两种转换方法。
先看 map,这个方法的作用是将实际包含 LiveData 的数据和仅用于观察数据的 LiveData 进行转换。什么意思呢?比如我新建了一个类,User:
data class User (var firstName:String,var lastName:String,var age:Int)
然后新建一个 LiveData 来包含 User 数据:
class UserViewModel : ViewModel() {
private val userViewModel = MutableLiveData()
}
这样看,跟上面好像没啥区别,但是 activity 中,只想显示 name,不要 age,怎么办呢?
这个时候,就可以使用 map(),它可以将 User 类型的 LiveData 自由地转型成任意其他类型的 LiveData ,如下:
class UserViewModel : ViewModel() {
private val userLiveData = MutableLiveData()
val userName:LiveData = Transformations.map(userLiveData){
user -> "${user.firstName} ${user.lastName}"
}
fun getUserName(user: User){
userLiveData.value = user;
}
}
可以看到,我们使用了 Transformations.map() 方法来进行 LiveData 的类型转换,它接受两个参数,第一个 LiveData,第二个就是当 userLiveData 数据发生变化时,需要转换的逻辑,这里直接拿到它的 firstName 和 lastName。
ok,调用我们这样调用:
val viewModel = ViewModelProvider(this).get(UserViewModel::class.java)
plusOne.setOnClickListener{
viewModel.getUserName(User("zhang","san",2))
}
//添加数据观察者
viewModel.userName.observe(this, Observer { string ->
infoText.text = string
})
在click的时候,调用 getUserName,让数据发生变化,这里的 Observer 只需要知道它的string 即可:
如果 ViewModel 中的某个 LiveData 对象是调用另外的方法获取的,则可以借助 switchMap 方法,将这个 LiveData 转换成可观察的 LiveData 对象。
什么意思呢?比如,上面的例子中,LiveData 都是在 ViewModel 中创建,但是不可能是这种理想情况,如果在外部创建呢,新建一个单利类,让它返回 livedata:
object Repository {
fun getUser(name:String):LiveData{
val liveData = MutableLiveData()
liveData.value = User(name,name,0)
return liveData;
}
}
这里其实模仿的是,根据name,拿到一个数据库的 User 数据,这里直接新建一个 User 即可。
然后,修改一下 UserViewModel:
class UserViewModel : ViewModel() {
fun getUser(name:String) : LiveData{
return Repository.getUser(name)
}
}
获取了 LiveData 对象,那接下来呢?如何观察数据?如下写法?:
viewModel.getUser("test").observe(this, Observer { user ->
})
这肯定是不行的!!!每次 getUser 返回都是一个新的 liveData,而上述写法会一直观察老的 LiveData 实例,根本无法观察数据的变化。
这个时候,就可以使用 switchMap 了,如下修改:
class UserViewModel : ViewModel() {
private val userLiveData = MutableLiveData()
val user : LiveData = Transformations.switchMap(userLiveData){
name -> Repository.getUser(name)
}
fun getUser(name: String){
userLiveData.value = name;
}
}
可以看到,我们定义了一个 string 类型的 MutableLiveData对象,用来观察 name 的变化,然后调用 Transformations 的 switchMap 方法。它接收两个参数,第一个为LiveData ,传入userLiveData ,第二个参数时一个转换函数,注意!这个转换函数必须返回一个 LiveData 对象,这里返回时通过 Repository 返回的一个新的 LiveData 数据。
这个每次 userLiveData.value 的值发生变化,就会调用 switchMap 的转换函数啦。
在activity 中这样调用:
val viewModel = ViewModelProvider(this).get(UserViewModel::class.java)
plusOne.setOnClickListener{
val name = (0..100).random().toString()
viewModel.getUser(name)
}
viewModel.user.observe(this, Observer { user ->
infoText.text = user.firstName
})