上节讲了Jetpack Room的基本使用,提到了Room是支持Kotlin Flow的,因此这次尝试了一下。
先看下实际效果。
上一节不好的地方
通过前面对Jetpck Room 的基本使用,可以了解到,使用Room操作数据库是超级方便的,但是有一点不好的地方就是,每次修改了数据之后,都要去调用一次查询updateView方法,来更新界面。
binding.btnInsert.setOnClickListener {
launch {
// Room操作数据库不能在主线程,因此这里使用IO线程操作
withContext(Dispatchers.IO) {
dataBase.userDao().insertUser(createUser())
}
// 插入后更新UI
updateView()
Toast.makeText(this@MainActivity, "Insert success", Toast.LENGTH_SHORT).show()
}
}
private fun updateView() {
launch {
// 查询数据并且更新UI
val text = withContext(Dispatchers.IO) {
val users = dataBase.userDao().queryAllUser()
users.joinToString("\n") { "${it.id}-${it.name} ${it.gender} ${it.age}" }
}
binding.tvInfo.text = text
}
}
那么有没有一种方法能够解决这种问题呢?答案就是使用Kotlin Flow。
改造成Flow的方式
首先导入依赖
// 核心库
implementation "androidx.room:room-runtime:$room_version"
// 使用kapt(Kotlin annotation processing tool)处理注解
kapt "androidx.room:room-compiler:$room_version"
// Room协程支持
implementation("androidx.room:room-ktx:$room_version")
然后改造一下UserDao,使返回值变成Flowable可观察数据类型,很简单,就是将原来的返回值用Flow包起来就行了
@Dao
interface UserFlowDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUser(vararg users: User)
@Delete
fun deleteUser(user: User)
@Query("SELECT * FROM user")
fun queryAllUser(): Flow<List<User>> // 修改返回值为Flow
}
再然后改造一下UserDataBase,新增一个获取UserFlowDao的抽象方法
@Database(entities =[User::class,],version = 1)
abstract class UserDataBase :RoomDatabase(){
abstract fun userDao(): UserDao
// 新增方法
abstract fun userFlowDao(): UserFlowDao
}
怎样使用呢
还是像上一节一样,创建一个UserDataBase
private val dataBase: UserDataBase by lazy {
Room.databaseBuilder(this, UserDataBase::class.java, "user.db").build()
}
然后改造一下updateView方法
private fun updateView(){
// onEach表示处理每次数据库更新后返回的新用户列表
dataBase.userFlowDao().queryAllUser().onEach { users ->
val text = users.joinToString("\n") { "${it.id}-${it.name} ${it.gender} ${it.age}" }
binding.tvInfo.text = text
}.launchIn(this) // launchIn(this)表示在当前Activity的协程scope监听
}
launchIn的实现很简单,源码只有几行,简单易懂。
public fun <T> Flow<T>.launchIn(scope: CoroutineScope): Job = scope.launch {
collect() // tail-call
}
好了,这下只需要执行一次updateView方法,数据库中的内容改变就能监听到,从而实现自动更新UI了。
贴上源码
package com.ricky.room.flow
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.room.Room
import com.ricky.room.databinding.ActivityMainBinding
import com.ricky.room.db.User
import com.ricky.room.db.UserDataBase
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import java.lang.Exception
class FlowRoomActivity : AppCompatActivity(), CoroutineScope by MainScope() {
private val binding: ActivityMainBinding by lazy { ActivityMainBinding.inflate(layoutInflater) }
private val dataBase: UserDataBase by lazy {
Room.databaseBuilder(this, UserDataBase::class.java, "user.db").build()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
binding.btnInsert.setOnClickListener {
launch {
withContext(Dispatchers.IO) {
dataBase.userFlowDao().insertUser(createUser())
}
Toast.makeText(this@FlowRoomActivity, "Insert success", Toast.LENGTH_SHORT).show()
}
}
binding.btnDelete.setOnClickListener {
launch {
withContext(Dispatchers.IO) {
dataBase.userFlowDao().deleteUser(createUser().copy(id = getUid()))
}
Toast.makeText(this@FlowRoomActivity, "Delete success", Toast.LENGTH_SHORT).show()
}
}
updateView()
}
private fun getUid(): Long {
return try {
binding.etId.text.toString().toLong()
} catch (e: Exception) {
0L
}
}
private fun createUser(): User {
val name = binding.etName.text.toString()
val gender = binding.etGender.text.toString()
val age = binding.etAge.text.toString()
return User(name = name, gender = gender, age = age)
}
// 使用Flow监听数据库,数据改变自动更新UI
private fun updateView(){
dataBase.userFlowDao().queryAllUser().onEach { users ->
val text = users.joinToString("\n") { "${it.id}-${it.name} ${it.gender} ${it.age}" }
binding.tvInfo.text = text
}.launchIn(this)
}
}