天天看点

LiveData与ViewModel基础使用篇

一、基本概念

1、LiveData

1.1 LiveData 简介

LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。

2、LiveData 优势

  • 确保界面符合数据状态LiveData 遵循观察者模式。
当底层数据发生变化时,LiveData 会通知 Observer 对象。可以整合代码以在这些 Observer 对象来更新界面。这样一来,无需在每次应用数据发生变化时更新界面,因为观察者会替您完成更新。
  • 不会发生内存泄漏
观察者会绑定到 Lifecycle 对象,并在其关联的生命周期遭到销毁后进行自我清理。
  • 不会因 Activity 停止而导致崩溃
如果观察者的生命周期处于非活跃状态(如返回栈中的 Activity),它不会接收任何 LiveData 事件。
  • 不再需要手动处理生命周期
界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。
  • 数据始终保持最新状态
如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。
  • 适当的配置更改
如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。
  • 共享资源
可以使用单例模式扩展 LiveData 对象以封装系统服务,以便在应用中共享它们。LiveData 对象连接到系统服务一次,然后需要相应资源的任何观察者只需观察 LiveData 对象。
2、ViewModel

2.1 ViewModel简介

Android Jetpack的一部分,用于为界面准备数据

2.1 ViewModel 优势

  • 让应用以注重生命周期的方式存储和管理界面相关的数据
  • 让数据可在发生屏幕旋转等配置更改后继续留存
  • 从界面控制器逻辑中分离出视图数据所有权,使数据和试图操作更容易且更高效

二、基础使用

1、添加依赖
  • 项目build.gradle中
allprojects {
    repositories {
        google()
        jcenter()
    }
}
           
  • app模块下build.gradle中
def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
           
2、代码实战

2.1 创建ViewModel类

class NameTestViewModel:ViewModel() {
    val userName:MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }
}
           

2.2 Activity中使用

  1. 获取ViewModel实例
private val mViewModel: NameTestViewModel by viewModels()
           
  1. 创建观察者并为viewModel添加观察者
// 创建observer
        val contentObserver = Observer<String> { content ->
            // 数据变更更新ui
            tvLiveDataContent.text = content
        }

        // 添加观察者
        mViewModel.userName.observe(this, contentObserver)
           
  1. LiveData 设置、更新、转换数据
// 设置数据
        btnSetData.setOnClickListener {
           //  mViewModel.userName.value = "张三"
             mViewModel.userName.postValue("张三")
        }

        // 更新数据
        btnChangeData.setOnClickListener {
            mViewModel.userName.value = "李四"
        }

        // 转换数据
        mapDataFunction()
    }


           
  1. LiveData 转换数据Map、SwitchMap
  • Map 无需返回值
private fun mapDataFunction() {
        val mapString=MutableLiveData<String>()
        mapString.postValue("数据")

        // 转换数据
        btnMapData.setOnClickListener {
            val mapObserver = Transformations.map(mapString) { userName ->
                "我是转换后的 $userName"
            }
            mapObserver.observe(this){
                tvLiveDataContent.text = it  // text:我是转换后的数据
            }
        }
    }
           
  • switchMap 经过switchMap转换后必须有返回值,返回值为liveData类型数据
private fun switchMapData() {
        val switchMapString=MutableLiveData<String>()
            switchMapString.value="switchMapData"

        val switchMapStringTwo=MutableLiveData<String>()
        switchMapStringTwo.postValue("switchMapDataTwo")

        btnSwitchMapData.setOnClickListener {
            // 经过switchMap转换后必须有返回值,返回值为liveData类型数据
            val switchMapObserver=Transformations.switchMap(switchMapString){
                // 修改数据
                switchMapStringTwo.postValue(it)
                // 通过switchMap将switchMapStringTwo作为返回值
                switchMapStringTwo
            }

            switchMapObserver.observe(this){
                tvLiveDataContent.text = it // text:switchMapData
            }

        }
    }
           
  1. LiveData mediatorLiveData 合并多个数据源
private fun mediatorLiveData() {
        val sourceString = MutableLiveData<String>()
        sourceString.value = "sourceLiveDataOne"

        val sourceStringTwo = MutableLiveData<String>()
        sourceStringTwo.postValue("sourceLiveDataTwo")

        val mediatorLiveData = MediatorLiveData<String>()

        // 添加数据源
        mediatorLiveData.addSource(sourceString) {
            mediatorLiveData.value = it
        }
        mediatorLiveData.addSource(sourceStringTwo) {
            mediatorLiveData.value = it
        }

        // 更新数据
        btnMediatorLiveData.setOnClickListener {
            mediatorLiveData.observe(this) {
                tvLiveDataContent.text = it  // text: sourceLiveDataTwo
            }

        }
    }
           

使用mediatorLiveData的addSource添加多个数据源,按照执行先后顺序执行,后执行的会覆盖先执行的数据

3、ViewModel 补充

3.1 viewModel生命周期

  • ViewModel 对象存在的时间范围是获取 ViewModel 时传递给 ViewModelProvider 的 Lifecycle。
  • ViewModel 将一直留在内存中,直到限定其存在时间范围的 Lifecycle 永久消失
  • 对于 activity,是在 activity 完成时;而对于 fragment,是在 fragment 分离时
  • activity中ViewModel生命周期
    LiveData与ViewModel基础使用篇

3.2 Fragment间使用ViewModel共享数据

  1. 创建两个Fragment
  2. 获取viewModel实例
  3. 设置监听
  4. 更新ui
class FirstFragment : Fragment() {
// 获取viewModel
private val model:DataTestViewModel by activityViewModels()
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return layoutInflater.inflate(R.layout.layout_fragment_first, container, false) }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // 设置观察者
        model.userName.observe(viewLifecycleOwner, Observer {
            // 更新ui
            tvContent.text=it
        })
    }
}

class SecondFragment:Fragment() {
    private val model: DataTestViewModel by activityViewModels()
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return layoutInflater.inflate(R.layout.layout_fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        model.userName.observe(viewLifecycleOwner, Observer {
            tvContent.text=it
        })
    }
}
           
  1. 将fragment添加到activity中
private fun addFragment() {
        val firstFragment=FirstFragment()
        val secondFragment=SecondFragment()
        supportFragmentManager.beginTransaction()
            .add(R.id.flOne,firstFragment)
            .add(R.id.flTwo,secondFragment)
            .commitAllowingStateLoss()
    }
           

因为两个fragment共享ViewModel,同时实现了监听,所有当viewmodel中数据发生变化,两个fragment都能收到响应,实现ui更新

3.3 协程与ViewModel一起使用

  1. 添加KTX依赖
// ktx
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
           
  1. 结合ViewModelScope使用
// ViewModelScope
    init {
        viewModelScope.launch {
            userName.value="defaultValue"
        }
    }
           
  1. Fragment中结合LifecycleScope使用
// lifecycleScope 执行耗时操作
        btnLifecycleScope.setOnClickListener {
            viewLifecycleOwner.lifecycleScope.launch {
                delay(100)
                // 修改数据
                model.userName.postValue("lifecycleScope")
            }
        }
           
  1. 可重启生命周期感知型协程

    在Fragment的onViewCreated()中监听生命周期变化并打印日志

// 可重启生命周期感知型携程
        viewLifecycleOwner.lifecycleScope.launch {

            viewLifecycleOwner.whenCreated {
                Log.d(TAG,"lifeCycle onCreated")
            }
            viewLifecycleOwner.whenStarted {
                Log.d(TAG,"lifeCycle onStarted")
            }
            viewLifecycleOwner.whenResumed {
                Log.d(TAG,"lifeCycle onResume")

            }

        }
           
  • 日志打印
D/FirstFragment: lifeCycle onCreated
D/FirstFragment: lifeCycle onStarted
D/FirstFragment: lifeCycle onResume
           

可见,我们通过viewLifecycleOwner观察到了fragment生命周期的变化。

  1. 挂起生命周期感知型协程

    即使 CoroutineScope 提供了适当的方法来自动取消长时间运行的操作,在某些情况下,可能需要暂停执行代码块(除非 Lifecycle 处于特定状态),手动在finally释放资源

private fun hangUpScope() {
        viewLifecycleOwner.lifecycleScope.launch {
            try {
                whenStarted {
                    var success = false
                    // 模拟携程执行1s耗时操作
                    withContext(Dispatchers.IO) {
                        delay(1000)
                        success = true
                    }
                    if (success) {
                        model.userName.postValue("success")
                    }
                }
            }finally {
                // finally 中释放资源
                if (lifecycle.currentState>=Lifecycle.State.STARTED){
                    releaseSource()
                }
            }
        }
    }
           

// 释放资源

private fun releaseSource() {
        Log.d(TAG, "release source")
    }
           
  • 日志打印
D/FirstFragment: release source
           
  1. 结合liveData与协程并通过emit将数据发送出去
  • ViewModel中
// 延迟1s 协程中模拟网络请求并通过emit将数据发送出去
    val liveDataItem= liveData {
        delay(1000)
        val data="John"
        emit(data)
    }
           
  • Fragment中监听数据变化
model.liveDataItem.observe(viewLifecycleOwner, Observer {
            Log.d(TAG,it)
        })
           
  • 日志打印
D/FirstFragment: John
           
  1. 使用 liveData 构建器函数调用 suspend 函数,并将结果作为 LiveData 对象传送
  • 使用liveData在协程中处理数据,并通过emit将结果发出
  • viewModel中代码
// 延迟1s 协程中模拟网络请求并通过emit将数据发送出去
    val liveDataItem= liveData {
        delay(1000)
        val data="John"
        emit(data)
    }

    val resultBean= liveData {
        emit(ResultBean(ResultBean.STATUS_LOADING))
        try {
            delay(2000)
            emit(ResultBean(ResultBean.STATUS_SUCCESS))
        }catch (e:Exception){
            emit(ResultBean(ResultBean.STATUS_FAIL))
        }
    }
           
  • Fragment中代码
private fun liveDataWithRoutineCombination() {
        model.liveDataItem.observe(viewLifecycleOwner, Observer {
            Log.d(TAG,it)
        })

        model.resultBean.observe(viewLifecycleOwner, Observer {
            when(it.status){
                ResultBean.STATUS_LOADING->{
                    Log.d(TAG, "resultBean loading")
                }
                ResultBean.STATUS_SUCCESS->{
                    Log.d(TAG, "resultBean success")
                }
                ResultBean.STATUS_FAIL->{
                    Log.d(TAG, "resultBean fail")
                }
            }
        })
    }
           
  • 日志打印
D/FirstFragment: resultBean loading
D/FirstFragment: John
D/FirstFragment: resultBean success
           
  1. ViewModel 中使用SavedStateHandle保存状态

    在onSaveInstance中保存数据,并在下次重启时,通过savedStateHandle读取数据,类似我们在onSaveInstance()中保存数据,在onCreate()中通过读取bundle恢复数据

  • 支持的数据类型

    保留在 SavedStateHandle 中的数据将作为 Bundle 与 Activity 或 Fragment 的 savedInstanceState 的其余部分一起保存和恢复

    LiveData与ViewModel基础使用篇

如果该类没有扩展上述列表中的任何一项,则应考虑通过添加 @Parcelize Kotlin 注解或直接实现 Parcelable 来使该类变为 Parcelable 类型或者实现Serializable实现序列化

  • 代码实例

    ViewModel注意构造方法中添加SavedStateHandle

class DataViewModelTwo(private val savedStateHandle: SavedStateHandle):ViewModel(){

    private val TAG="tag"

    // 设置query数据
    fun setQuery(query: String) {
        if (savedStateHandle.contains("query")) {
            Log.d(TAG, "contains query==== ${savedStateHandle.get<UserBean>("query")}")
        }else{
            Log.d(TAG,"保存数据")
            savedStateHandle["query"] = UserBean(userName = query,userAge = 10)
        }
    }

    // 获取数据
    fun getSavedValue(): UserBean? {
        return savedStateHandle.get<UserBean>("query")
    }
}
           
  • 获取ViewModel对象
private val model2: DataViewModelTwo by activityViewModels()
           
  • Fragment的onSaveInstance中保存数据
override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        model2.setQuery("啦啦啦啦")
    }
           
  • 在Fragment中onResume()方法中模拟获取数据
override fun onResume() {
        super.onResume()
        // saveData
        saveDataFunction()
    }

    private fun saveDataFunction() {
      Log.d(TAG, "query==== ${model2.getSavedValue()}")
    }
           
  • 日志打印
D/tag: query==== null
D/tag: 保存数据
 D/tag: query==== UserBean(userName=啦啦啦啦, userAge=10)
           

通过上面的例子我们就实现了viewModel保存数据的效果

  • 参考:

    https://developer.android.google.cn/topic/libraries/architecture/viewmodel?hl=zh_cn

    https://developer.android.google.cn/topic/libraries/architecture/livedata?hl=zh_cn

    https://developer.android.google.cn/topic/libraries/architecture/viewmodel-savedstate?hl=zh_cn