Jetpack Room再次尝试——Flow与Room结合

4,916 阅读1分钟

上节讲了Jetpack Room的基本使用,提到了Room是支持Kotlin Flow的,因此这次尝试了一下。
先看下实际效果。

202107232232.gif

上一节不好的地方

通过前面对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)
    }
}