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生命周期
    在这里插入图片描述

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 的其余部分一起保存和恢复
    在这里插入图片描述

如果该类没有扩展上述列表中的任何一项,则应考虑通过添加 @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
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Room与LiveDataViewModel结合是为了实现数据持久化、数据观察与界面更新的最佳实践。 Room是Google为Android提供的一个持久化数据库解决方案,它简化了数据库操作并提供了编译时检查,同时支持在后台线程中进行数据库访问。LiveData是一种可观察的数据容器,它可以感知生命周期,并在数据变化时自动更新。ViewModel则负责管理界面相关的数据,并且与Activity或Fragment的生命周期无关,避免了数据丢失或重复请求的问题。 将Room与LiveData结合使用,可以将Room中的数据变化以LiveData的形式暴露出来。这样,在数据库发生变化时,LiveData会自动通知相关观察者进行界面更新。LiveData还具有自动清理机制,当观察者的生命周期结束时会自动停止数据的更新,避免了内存泄漏问题。 而结合ViewModel可以更好地管理界面相关的数据。ViewModel可以在配置改变时(如屏幕旋转)保留数据,并且避免了异步任务导致的内存泄漏。通过在ViewModel中缓存LiveData对象,可以在Activity或Fragment恢复时继续观察数据变化,并保持界面更新的一致性。 总结而言,Room与LiveDataViewModel的结合可以实现一个可靠、高效、响应式的数据处理框架,使得数据的持久化、观察与界面更新变得十分简单。这样的架构设计也有利于代码的维护与扩展,提高了开发效率与用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值