美文网首页
Jetpack之LiveData

Jetpack之LiveData

作者: 0246eafe46bd | 来源:发表于2021-12-15 10:41 被阅读0次

LiveData

LiveData是Jetpack提供的一种响应式编程组件,它可以包含任何类型的数据,并在数据发生变化的时候通知给观察者,适合与ViewModel结合在一起使用,就可以让ViewModel将数据的变化主动通知给Activity

LiveData的基本用法

不使用LiveData的ViewModel在单线程模式下确实可以正常工作,但如果ViewModel的内部开启了线程去执行一些耗时逻辑,之后就立即去获取最新的数据,得到的肯定还是之前的数据

修改ViewModel

MyViewModel

class MyViewModel(countReserved: Int) : ViewModel() {
    // counter变量用于计数
    var counter = MutableLiveData<Int>()

    init {
        counter.value = countReserved
    }

    fun plus() {
        //LiveData的getValue()方法所获得的数据是可能为空的
        val value = counter.value ?: 0
        counter.value = value + 1
    }
}

将counter变量修改成了一个MutableLiveData对象,并指定它的泛型为Int,表示它包含的是整型数据。MutableLiveData是一种可变的LiveData,它的用法很简单,主要有3种读写数据的方法,分别是getValue()、setValue()和postValue()方法。getValue()方法用于获取LiveData中包含的数据;setValue()方法用于给LiveData设置数据,但是只能在主线程中调用;postValue()方法用于在非主线程中给LiveData设置数据

调用LiveData的getValue()方法所获得的数据是可能为空的,因此这里使用了一个?:操作符,当获取到的数据为空时,就用0来作为默认计数

现在counter变量已经变成了一个LiveData对象,任何LiveData对象都可以调用它的observe()方法来观察数据的变化,observe()方法接收两个参数:第一个参数是一个LifecycleOwner对象,Activity本身就是一个LifecycleOwner对象,因此直接传this就好;第二个参数是一个Observer接口,当counter中包含的数据发生变化时,就会回调到这里,因此可以在这里将最新的计数更新到界面上即可

修改MyActivity

class MyActivity : AppCompatActivity() {
    private lateinit var viewModel: MyViewModel
    private lateinit var preferences: SharedPreferences
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)
        // 获取退出app前保存的数据
        preferences = getPreferences(Context.MODE_PRIVATE)
        val countReserved = preferences.getInt("count_reserved", 0)

        // 不可以直接去创建ViewModel的实例,一定要通过ViewModelProvider来获取ViewModel的实例
        viewModel =
            ViewModelProvider(this, MyViewModelFactory(countReserved)).get(MyViewModel::class.java)
        viewModel.counter.observe(this) {
            num_text.text = it.toString()
        }
        // 按钮点击监听
        plus_btn.setOnClickListener {
            // 使用viewModel修改数据
            viewModel.plus()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        preferences.edit().putInt("count_reserved", viewModel.counter.value ?: 0).apply()
    }
}

不用担心ViewModel的内部会不会开启线程执行耗时逻辑。不过需要注意的是,如果你需要在子线程中给LiveData设置数据,一定要调用postValue()方法,而不能再使用setValue()方法,否则会发生崩溃

只暴露不可变的LiveData给外部

前面的代码将counter这个可变的LiveData暴露给了外部。这样即使是在ViewModel的外面也是可以给counter设置数据的,从而破坏了ViewModel数据的封装性,因此最好只暴露不可变的LiveData给外部,这样在非ViewModel中就只能观察LiveData的数据变化,而不能给LiveData设置数据

MyViewModel

class MyViewModel(countReserved: Int) : ViewModel() {
    // 只暴露不可变的LiveData给外部
    val counter: LiveData<Int>
        get() = _counter

    // counter变量用于计数
    private var _counter = MutableLiveData<Int>()

    init {
        _counter.value = countReserved
    }

    fun plus() {
        //LiveData的getValue()方法所获得的数据是可能为空的
        val value = _counter.value ?: 0
        _counter.value = value + 1
    }
}

map和switchMap

map

map()方法,这个方法的作用是将实际包含数据的LiveData和仅用于观察数据的LiveData进行转换

示例

加入说有一个User类,User中包含用户的姓名和年龄

data class User(var name: String, var age: Int)

在ViewModel中创建一个相应的LiveData来包含User类型的数据

class MyViewModel(countReserved: Int) : ViewModel() {
    val user = MutableLiveData<User>()
}

如果Activity中明确只会显示用户的姓名,而完全不关心用户的年龄,map()方法就是专门用于解决这种问题的,它可以将User类型的LiveData自由地转型成任意其他类型的LiveData

class MyViewModel(countReserved: Int) : ViewModel() {

    private val user = MutableLiveData<User>()
    val userName: LiveData<String> = Transformations.map(user) { user ->
        user.name
    }
}

先将将userLiveData声明成了private,以保证数据的封装性,再调用map()方法,map()方法接收两个参数:第一个参数是原始的LiveData对象;第二个参数是一个转换函数,我们在转换函数里编写具体的转换逻辑即可

switchMap

LiveData对象的实例都是在ViewModel中创建的,但很有可能ViewModel中的某个LiveData对象是调用另外的方法获取的

如果ViewModel中的某个LiveData对象是调用另外的方法获取的,那么我们就可以借助switchMap()方法,将这个LiveData对象转换成另外一个可观察的LiveData对象

示例

Repository单例类

object Repository {
    fun getUser(userId: String): LiveData<User> {
        val liveData = MutableLiveData<User>()
        liveData.value = User(userId, 4)
        return liveData
    }
}

在Repository类中添加了一个getUser()方法,这个方法接收一个userId参数。一般userId参数去服务器请求或者到数据库中查找相应的User对象,但是这里只是模拟示例,因此每次将传入的userId当作用户姓名来创建一个新的User对象

需要注意的是,每次调用getUser()方法都会返回一个新的LiveData实例

然后在MyViewModel中也定义一个getUser()方法,并且让它调用Repository的getUser()方法来获取LiveData对象

class MyViewModel(countReserved: Int) : ViewModel() {
    ......
    fun getUser(userId: String): LiveData<User> {
        return Repository.getUser(userId)
    }
}

这样的话,在Activity中使用下面的代码就无法观察LiveData的数据变化

viewModel.getUser(userId).observe(this) {
    
}

因为每次调用getUser()方法返回的都是一个新的LiveData实例,而上述写法会一直观察老的LiveData实例,使用switchMap方法则可解决此问题

class MyViewModel(countReserved: Int) : ViewModel() {

    ......

    private val userIdLiveData = MutableLiveData<String>()
    val user: LiveData<User> = Transformations.switchMap(userIdLiveData) { userId ->
        Repository.getUser(userId)
    }

    fun getUser(userId: String) {
        userIdLiveData.value = userId
    }
}

定义了一个新的userIdLiveData对象,用来观察userId的数据变化,然后调用了Transformations的switchMap()方法,用来对另一个可观察的LiveData对象进行转换

switchMap()方法同样接收两个参数:第一个参数传入我们新增的userIdLiveData,switchMap()方法会对它进行观察;第二个参数是一个转换函数,注意,我们必须在这个转换函数中返回一个LiveData对象

整体工作流程。首先,当外部调用MainViewModel的getUser()方法来获取用户数据时,并不会发起任何请求或者函数调用,只会将传入的userId值设置到userIdLiveData当中。一旦userIdLiveData的数据发生变化,那么观察userIdLiveData的switchMap()方法就会执行,并且调用我们编写的转换函数。然后在转换函数中调用Repository.getUser()方法获取真正的用户数据。同时,switchMap()方法会将Repository.getUser()方法返回的LiveData对象转换成一个可观察的LiveData对象,对于Activity而言,只要使用下面的方法去观察这个LiveData对象就可以了

viewModel.user.observe(this) {

}

如果上面的ViewModel中某个获取数据的方法(getUser)是没有参数的,就要创建一个空的LiveData对象,如下

class MyViewModel(countReserved: Int) : ViewModel() {
    ......

    private val refreshLiveData = MutableLiveData<Any?>()
    val refreshResult: LiveData<Any?> = Transformations.switchMap(refreshLiveData) {
        // 假设Repository已经定义了refreshLiveData方法
        Repository.refreshLiveData()
    }

    fun refresh() {
        refreshLiveData.value = refreshLiveData.value
    }
}

定义一个不带参数的refresh()方法,又对应地定义了一个refreshLiveData,但是它不需要指定具体包含的数据类型,因此将LiveData的泛型指定成Any?

在refresh()方法中,将refreshLiveData原有的数据取出来(默认是空),再重新设置到refreshLiveData当中,这样就能触发一次数据变化,因为LiveData内部不会判断即将设置的数据和原有数据是否相同

然后在Activity中观察refreshResult这个LiveData对象即可,这样只要调用了refresh()方法,观察者的回调函数中就能够得到最新的数据

viewModel.refreshResult.observe(this){
    
}

LiveData之所以能够成为Activity与ViewModel之间通信的桥梁,并且还不会有内存泄漏的风险,靠的是Lifecycles组件。LiveData在内部使用了Lifecycles组件来自我感知生命周期的变化,从而可以在Activity销毁的时候及时释放引用,避免产生内存泄漏的问题。另外,由于要减少性能消耗,当Activity处于不可见状态的时候(比如手机息屏,或者被其他的Activity遮挡),如果LiveData中的数据发生了变化,是不会通知给观察者的。只有当Activity重新恢复可见状态时,才会将数据通知给观察者,如果在Activity处于不可见状态的时候,LiveData发生了多次数据变化,当Activity恢复可见状态时,只有最新的那份数据才会通知给观察者

相关文章

网友评论

      本文标题:Jetpack之LiveData

      本文链接:https://www.haomeiwen.com/subject/tioefrtx.html