Room数据库实战:搭配RxJava使用与封装

一、介绍

接着上一篇Room的基本使用介绍(不会Room基本使用的先看这一篇),每次使用增删改查功能都需要new Thread,不方便也不好管理,本章主要介绍RxJava如何搭配Room使用。

二、引入RxJava库

def latest_version = "2.2.5"
//room
implementation "androidx.room:room-runtime:$latest_version"
implementation "androidx.room:room-ktx:$latest_version"
kapt "androidx.room:room-compiler:$latest_version"
// optional - RxJava support for Room
implementation "androidx.room:room-rxjava2:$latest_version"

三、项目实战

使用前需要先准备数据实体数据库数据访问对象(DAO)。 其中数据实体和数据库的内容与上一篇的内容一样,核心在数据访问对象的修改

数据实体

@Entity
class RunRecord {
    @PrimaryKey(autoGenerate = true)
    var id: Long? = null
    
    @ColumnInfo(name = "userId")
    var userId: String? = null
    
    @ColumnInfo(name = "pathLine")
    var pathLine: String? = null
    
    @ColumnInfo(name = "totalStep")
    var totalStep:Int? = 0
}

数据库

@Database(entities = [ RunRecord::class], version = 1)
abstract class YzDatabase : RoomDatabase() {
    abstract fun runDao(): RunDao?
    companion object {
        private const val DB_NAME = "yzWill.db"
        @Volatile
        private var instance: YzDatabase? = null
        fun getInstance(context: Context) =
            instance ?: synchronized(this) {
                instance ?: Room.databaseBuilder(
                    context,
                    YzDatabase::class.java, DB_NAME
                ).build()
            }
    }
}

数据访问对象

与上一篇的区别就是把方法的返回值外包装了一层Single(为什么使用Single后面说),例如:Long ->Single

@Dao
interface RunDao{

    /**
     *
     * 插入一条跑步信息,id不用传,会自动增长.建议不传
     * @param info RunRecord?
     * @return Single<Long>  返回插入的主键id
     */
    @Insert
    fun insert(info: RunRecord?): Single<Long>

    /**
     * 删除某一条,需要传入主键ID 例如,:
    RunRecord().apply {
    this.id = 1
    }
     * @return Single<Int>  1 成功 0失败
     */
    @Delete
    fun delete(info: RunRecord?): Single<Int>

    /**
     * 需要传入主键ID 例如,传入id,修改userId为 "weng":
    RunRecord().apply {
    this.id = 1
    this.userId = "weng"
    }
     * @return Single<Int>  1 成功 0失败
     */
    @Update
    fun update(vararg info: RunRecord?): Single<Int>

    /**
     * 根据userId获取最后一条
     * @param userId String
     * @return RunRecord?
     */
    @Query("SELECT * FROM RunRecord WHERE userId LIKE :userId order by userId desc  LIMIT 1")
    fun findLastByUserId(userId: String): Single<RunRecord?>
    
    /**
     * 查询全部数据
     */
    @get:Query("SELECT * FROM RunRecord")
    val all: Single<List<RunRecord?>?>
}

使用

下面就举两个插入和删除的栗子

  val db = YzDatabase.getInstance(this).runDao()
        //插入一条数据,返回主键id
        db?.insert(RunRecord().apply {
            pathLine = "插入一条信息"
            userId = "小小虫"
        })
            ?.subscribeOn(Schedulers.io())
            ?.observeOn(AndroidSchedulers.mainThread())
            ?.doOnSuccess {
                Log.e(tag, "id:$it")
            }?.subscribe()
  val db = YzDatabase.getInstance(this).runDao()
        //删除ID为1的数据,返回 0:删除失败 1:删除成功
        db?.delete(RunRecord().apply {
            id = 1
            pathLine = "插入一条信息"
            userId = "小小虫"
        })
            ?.subscribeOn(Schedulers.io())
            ?.observeOn(AndroidSchedulers.mainThread())
            ?.doOnSuccess {
                Log.e(tag, "success:${it == 1}")
            }?.subscribe()

四、封装

通过上文,我们可以虽然把Room结合了RxJava,但用起来很明显有很多冗余的代码,可以简单的封装一下。增加一个DBManager类,如下:

class DBManager(context: Context) {

    private var mDB: YzDatabase? = YzDatabase.getInstance(context)
    companion object {
        @Volatile
        private var instance: DBManager? = null
        fun getInstance(context: Context) =
            instance ?: synchronized(this) {
                instance ?: DBManager(context)
            }
    }

    fun insertRunRecord(info: RunRecord): Single<Long>? {
        return extra(mDB?.runDao()?.insert(info))
    }
    
     fun findLastByUserId(userId: String): Single<RunRecord?>? {
        return extra(mDB?.runDao()?.findLastByUserId(userId))
    }

    private fun <T> extra(single: Single<T>?): Single<T>? {
        return single
            ?.subscribeOn(Schedulers.io())
            ?.observeOn(AndroidSchedulers.mainThread())
    }
}

使用

可以看到,代码量明显的减少。

 DBManager.getInstance(this).insertRunRecord(RunRecord().apply {
            pathLine = "插入一条信息"
            userId = "小小虫"
        }) ?.doOnSuccess {
            Log.e(tag, "id:$it")
        }?.subscribe()

五、踩坑

先看一段查询数据库的代码,如下:

DBManager.getInstance(this).findLastByUserId(userId = "小小虫") 
         ?.doOnSuccess {
            Log.e(tag, "info:$it")
        }?.subscribe()

一段很简单的代码,但问题是,如果你查到数据了,一切都好。如果查不到,对不起,App闪退,报错如下:

在这里插入图片描述

解决方法如下:

  1. 不要使用RxJava(。。。),也就是用最原始的方法,new Thread去查询。
  2. 不要使用Single接收,使用**Maybe(推荐)**或者Flowable、Observable。
  3. 如果你还要用Single,那么请在包一层List,也就是Single<List>。

看下官方的解释
在这里插入图片描述

至于这个问题的原因,其实跟Single的特性相关,因为Single本身的设计就是必须发出单个非空值的类型。

为什么使用Single?

RxJava的被观察者有五个,如下:

  • Observable: 用来发射0或者多个数据
  • Flowable: Observable的升级版,增加了背压策略
  • Single: 用来发射单个数据onSuccess或错误onError事件
  • Maybe: 用来发射0或者单个数据
  • Completable: 不发射数据,Room好像也不支持()

可以看到,数据库的增加改查都是需要0次或者单次的结果返回的,那么就只需要在Single和Maybe中选择。当然,你使用Observable和Flowable也是可以的,这里推荐使用Maybe来作为查询数据的返回,效率相比其他两种会比较快。


版权声明:本文为wumeixinjiazu原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。