Jetpack系列(三) -- LiveData

前言

时间: 23/09/17

AndroidStudio版本: Giraffe 2022.3.1 JDK:17 开发语言: Kotlin

Gradle版本: 8.0 Gradle plugin Version: 8.1.1

基本使用

  • 添加依赖

    groovy 复制代码
    implementation("androidx.lifecycle:lifecycle-livedata:2.6.2")
    implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.2")
  • 修改MainViewModel

    • 给ViewModel添加 MutableLiveData<> 类型变量。

      kotlin 复制代码
      private var result = MutableLiveData<Long>()
    • 设置LiveData类型常量并赋值为result

      kotlin 复制代码
      val _result: LiveData<Long> = result
    • 因为result是私有变量,所以需要写一个修改值的方法

      kotlin 复制代码
      fun setResult(showLong: Long) {
          result.value = showLong
          time = showLong.times(1000)
      }
    • 整体

      kotlin 复制代码
      class MainViewModel : ViewModel() {
          var time: Long = 5000
      
          private var result = MutableLiveData<Long>()
      
          val _result: LiveData<Long> = result
      
          fun setResult(showLong: Long) {
              result.value = showLong
              time = showLong.times(1000)
          }
      
      }
  • 添加一个MainManage类,用来控制ViewModel中的数据变化

    kotlin 复制代码
    class MainManager(mainViewModel: MainViewModel) : LifecycleEventObserver {
        private val TAG = "MainManager"
    
        private var countDownTimer: CountDownTimer? = object : CountDownTimer(mainViewModel.time, 1000) {//添加一个倒计时,时间为time=5000ms
            override fun onTick(timeAfter: Long) {
                Log.d(TAG, "倒计时 --- ${timeAfter / 1000}s")
                mainViewModel.setResult(timeAfter / 1000)
            }
    
            override fun onFinish() {
                Log.d(TAG, "倒计时 结束")
            }
    
        }
    
    
        override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
            Log.d(TAG, "onStateChanged: $event")
            when (event) {
                Lifecycle.Event.ON_START -> countDownTimer?.start()
                Lifecycle.Event.ON_DESTROY -> {
                    countDownTimer?.cancel()
                    countDownTimer = null
                }
                else -> {}
            }
        }
    }
  • MainActivity中添加LiveData的observe

    kotlin 复制代码
    class MainActivity : AppCompatActivity() {
        private val TAG = "MainActivity"
    
        private lateinit var mainViewModel : MainViewModel
        private lateinit var binding: ActivityMainBinding
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            binding = ActivityMainBinding.inflate(layoutInflater)
            setContentView(binding.root)
            mainViewModel = ViewModelProvider(this)[MainViewModel::class.java]
            val mainManager = MainManager(mainViewModel)
            lifecycle.addObserver(mainManager)
            mainViewModel._result.observe(this) {//观察_result,_result的值会随着result变化而变化
                binding.showTime.text = "倒计时还剩下 ${it}s"//it是LiveData<>规定的类型Long
                if (it == 0L) {
                    Log.d(TAG, "  OVER!! ")
                    binding.showTime.text = "倒计时结束"//这里使用了ViewBinding,这个后续讲。
                    //不会使用可以先用findViewById绑定好视图再操作
                    //其实前面两篇文章我已经用到了这个,之前写代码的习惯,这个也是jetpack的组件技术,所以就后续再细说
                }
            }
        }
    }

Log显示

这里用的是私有变量,如果使用的kotlin默认的public的话,可以直接使用变量去完成这个observe的操作。但是共用变量可能会导致数据操作的完整性出现问题,因此我这边直接用的私有来声明

LiveData<>类型转化方法 - map & switchMap

从前面所讲的和代码来看,我们观察所接收到的是LiveData所指定的类型,如果这个类型是我们自己写的一个实体(entity)类,那么我们的需求有很大的可能是要拿到这个类中的某个属性,于是我们在拿到这个实体类后还需要进行加工,这样我们每次都要使用getter方法增加 Activity 中的代码量。

这个时候就需要使用到类型转化方法 ------ map 或者 switchMap 。

map

编写一个Data类Team,设置三个属性,id、name、point

kotlin 复制代码
data class Team(var name: String, var id: Long, var point: Int)

然后再编写一个TeamViewModel,对应Team。

kotlin 复制代码
class TeamViewModel : ViewModel() {
    private var teamData = MutableLiveData<Team>()
    val _teamData: LiveData<Team> = teamData

    fun setTeamData(team: Team) {
        teamData.value = team
    }
}

然后在Activity中observe,这个时候观察的对象就是Team。如果我们只是想要显示point属性,那么就需要使用getter。

假设我们的需求就只是获取每一个team的分数,那我们可以对ViewModel中的LiveData进行修改。

由于书本中Transformations这个类被Google "优化" 了。现在可以直接用LiveData使用map方法,即teamData.map

kotlin 复制代码
    val _teamData: LiveData<Int> = teamData.map {
        it.point
   }

这样就可以把LiveData指定的类型改为point的类型。在观察时直接使用即可。

布局和Activity的代码往下看

switchMap

switchMap转化是将从外部,例如其它app进程或网络中获取到的LiveData转化为可观察的方式。举个栗子。

新建一个TeamRespository类,添加getTeamPoint方法。

kotlin 复制代码
class TeamRespository {
    /*
    * 根据id获取得分,'模拟'网络请求
    */
    fun getTeamPoint(id: Int): LiveData<Int> {
        val teamScoreLiveData = MutableLiveData<Int>()
        if (id == 7) {//图个方便
            teamScoreLiveData.value = 55
        } else {
            teamScoreLiveData.value = 0
        }
        return teamScoreLiveData
    }
}

这个getTeamPoint 方法后续会在 ViewModel 中调用 。先看下layout布局。

新建一个activity_team.xml到layout中。

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".activities.MainActivity">

        <TextView
            android:id="@+id/team_info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            android:textSize="32sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/textInputLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="16dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/team_info">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/input_text"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:inputType="number"
                android:hint="Please text Team ID" />
        </com.google.android.material.textfield.TextInputLayout>

        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/submit"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:text="submit"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/textInputLayout" />

        <TextView
            android:id="@+id/score_show"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/submit" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

主要是添加了一个输入框和按钮,来通过输入的ID获取得分。

看到按钮,这个时候第一时间想到的实现方法,就是在点击监听中,拿到ID,调取获取得分的方法,显示出结果。

在ViewModel中添加一个返回类型为LiveData的getPoint方法,这样才可以在 Activity 中添加 observe 方法。

kotlin 复制代码
//viewmodel中
fun getPoint(id: Int): LiveData<Int> {
	return TeamRespository().getTeamPoint(id)
}
//activity中
teamViewModel.setTeamData(team1)
binding.submit.setOnClickListener {
	teamViewModel.getPoint(binding.inputText.text.toString().toInt()).observe(this, Observer { 
		binding.scoreShow.text = "score: $it"
	})
}

看着会觉得有点奇怪,因为每次获取得分的时候都会返回一个新的LiveData,所以这个observe其实压根没啥作用,甚至不如直接写成

kotlin 复制代码
binding.submit.setOnClickListener {
    binding.scoreShow.text
    =  "score: ${teamViewModel.getPoint(binding.inputText.text.toString().toInt())}"
}

嘿嘿,甚至看着还更简单点。但是这中方式完全就和LiveData不搭边,甚至之前写的全都是冗余代码。

怎么解决这种情况呢?可以看到,我们是通过ID获取对应的分数的,那我们就可以定义一个可观察的id变量

kotlin 复制代码
//viewmodel中
private var teamIdLiveData = MutableLiveData<Int>()

fun setTeamId(teamId: Int) {
    teamIdLiveData.value = teamId
}

然后通过switchMap将 teamIdLiveData 映射为返回的分数结果类型

kotlin 复制代码
//viewmodel中
var newPoint: LiveData<Int> = teamIdLiveData.switchMap {id: Int -> 
	return@switchMap TeamRespository().getTeamPoint(id)
}

这样我们就可以在activity中添加对newPoint的observe,点击事件则是调用setTeamId方法,因为调用了setTeamId方法 teamIdLiveData 的值才会改变,newPoint 的observe方法才会监听到变化执行相关操作。

kotlin 复制代码
teamViewModel.newPoint.observe(this) {
    binding.scoreShow.text = "score: $it"
}
binding.submit.setOnClickListener {
    teamViewModel.setTeamId(binding.inputText.text.toString().toInt())
}

result显示

总结

LiveData 一般都定义在 ViewModel 中,作为可观察数据,它具有生命周期感知的能力,可以确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。

Lifecycle、ViewModel 和 LiveData之间的关系比较密切,一般都是结合三者一起使用。利用好这三者可以大幅度简化 Activity 中的代码,为后续 mvvm 框架的解耦作好铺垫。

本文没有对于 LiveData 的原理进行剖析,因为原理代码相对比较简单。感兴趣的可以自行阅读,主要方向是 observe 方法。

相关推荐
萌面小侠Plus8 小时前
Android笔记(三十三):封装设备性能级别判断工具——低端机还是高端机
android·性能优化·kotlin·工具类·低端机
wk灬丨11 小时前
Android Kotlin Flow 冷流 热流
android·kotlin·flow
晨曦_子画12 小时前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
大福是小强12 小时前
005-Kotlin界面开发之程序猿初试Composable
kotlin·界面开发·桌面应用·compose·jetpack·可组合
&岁月不待人&14 小时前
Kotlin by lazy和lateinit的使用及区别
android·开发语言·kotlin
小白学大数据17 小时前
正则表达式在Kotlin中的应用:提取图片链接
开发语言·python·selenium·正则表达式·kotlin
bytebeats2 天前
Kotlin 注解全面指北
android·java·kotlin
jzlhll1232 天前
kotlin android Handler removeCallbacks runnable不生效的一种可能
android·开发语言·kotlin
&岁月不待人&2 天前
Kotlin 协程使用及其详解
开发语言·kotlin
苏柘_level62 天前
【Kotlin】 基础语法笔记
开发语言·笔记·kotlin