手把手教你搭建android模块化项目框架(七)存储之room

上期我们聊到room,本期就来简单说一下room的用法。
常规room我们不聊怎么用了,跟着官方文档一步一步使用即可。

传送门

老规矩,先上效果。

 override fun testRoom() {
        //常规flow监听
        lifecycleScope.launchWhenResumed {
            UserDB.getUserFlow("test1").collect {
                Log.v("collect", "collect: $it")
            }
        }

        //常规flow操作符监听
        UserDB.getUserFlow("test1")
//            .catch {  }//错误捕获
            .onEach {
                Log.v("launchIn", "launchIn: $it")
            }
//            .catch {  } //onEach中的错误捕获
            .launchIn(lifecycleScope).start()

        //转化为liveData后的监听 private val userModelObs = UserDB.getUserFlow("test1").asLiveData()
        //live_data_ktx 库  将flow转为livedata private val userModelObs = UserDB.getUserFlow("test1").asLiveData()
        userModelObs.observe(this) {
            Log.v("livedata", "livedata: $it")
        }

        //修改数据
        lifecycleScope.launchWhenResumed {
            UserDB.updateUserAsync(UserModel("test1", "test6", 5))
        }
    }
//打印结果
2023-08-28 15:43:44.691 23583-23583 launchIn                yz.l.fm                              V  launchIn: UserModel(uid=test1, name=test5, gender=5)
2023-08-28 15:43:44.692 23583-23583 launchIn                yz.l.fm                              V  launchIn: UserModel(uid=test1, name=test6, gender=5)
2023-08-28 15:43:44.692 23583-23583 livedata                yz.l.fm                              V  livedata: UserModel(uid=test1, name=test5, gender=5)
2023-08-28 15:43:44.692 23583-23583 livedata                yz.l.fm                              V  livedata: UserModel(uid=test1, name=test6, gender=5)
2023-08-28 15:43:44.692 23583-23583 collect                 yz.l.fm                              V  collect: UserModel(uid=test1, name=test5, gender=5)
2023-08-28 15:43:44.692 23583-23583 collect                 yz.l.fm                              V  collect: UserModel(uid=test1, name=test6, gender=5)

我们看到每个结果打印了两次,其中name由5变成了6,其中5是原始值,6是最后修改数据使用的值,这里就是使用flow的好处了,修改数据库直接能够反馈到所有监听flow的地方,并且flow自带生命周期,无需担心内存泄露问题。如此处理,也能让本地数据杜绝EventBus等事件总线来回传递,造成Event灾难。

下面我们一步一步来实现这个效果。

初始化room,这里我与官方处理的方式略有差异
根据我们的模块化方案,room初始化我们放置在:features:feature_common:common_room_db模块中

@SuppressLint("StaticFieldLeak")
object RoomDB {
    private lateinit var context: Context

    //application初始化时调用,如果采用其他的单例方式需要每次传入context,使用比较麻烦。
    fun init(context: Context) {
        this.context = context.applicationContext
    }

    val INSTANCE: AppDataBase by lazy { Holder.holder }

    private object Holder {
        val holder by lazy {
            Room.databaseBuilder(
                context.applicationContext,
                AppDataBase::class.java,
                "android_room_db.db" //数据库名称
            )
                .allowMainThreadQueries() //允许启用同步查询,即:允许主线程可以查询数据库,这个配置要视情况使用,一般不推荐同步查询
                .fallbackToDestructiveMigration()//如果数据库升级失败了,删除重新创建
                .enableMultiInstanceInvalidation()//多进程查询支持
//              .addMigrations(MIGRATION_1_2) //数据库版本升级,MIGRATION_1_2为要执行的表格执行sql语句,例如database.execSQL("ALTER TABLE localCacheMusic ADD COLUMN time Int NOT NULL default 0 ;")
                .build()
        }
    }
}

@Database(
    entities = [UserEntity::class],
    version = 1, exportSchema = false
)
@TypeConverters(value = [LocalTypeConverter::class]) //自定义数据处理转换,这里我们将list都转为json存储
abstract class AppDataBase : RoomDatabase() {
    abstract fun userDao(): UserDao
}

//本类可以根据具体业务需求自行处理,这里随便写了个demo,没有经过测试。
open class LocalTypeConverter {
    @TypeConverter
    fun json2StrListEntity(src: String?): List<String>? =
        src.toObject()

    @TypeConverter
    fun strList2Json(data: List<String>?): String = gson.toJson(data ?: mutableListOf<String>())

    @TypeConverter
    fun date2Long(date: Date?): Long {
        return date?.time ?: System.currentTimeMillis()
    }

    @TypeConverter
    fun long2Date(time: Long): Date {
        return Date(time)
    }
}

接下来我们创建table,这里我们将数据库模型与实际使用的模型完全隔离开,并且使用扩展方法进行数据转换处理,避免业务模型变更影响到数据变更,到时候维护起来比较麻烦。并且难以做数据库升级。本文中所有entity结尾的类为数据库模型,model结尾的类为业务模型。

根据我们的模块化方案,其中Entity放置在:features:feature_common:common_room_db模块中,Model类及转换类放置在data_xxxx模块中,依赖关系为,data_xxxxx implementation project(":features:feature_common:common_room_db")

@Entity(primaryKeys = ["uid", "remoteName"])
data class UserEntity(
    var uid: String,
    var remoteName: String = "",
    val name: String = "",
    val gender: Int = 1
)

data class UserModel(
    var uid: String = "",
    var name: String = "",
    var gender: Int = 1
)
//数据转换
fun UserModel.toEntity(remoteName: String) =
    UserEntity(uid = this.uid, remoteName = remoteName, name = this.name, gender = gender)

fun UserEntity.toUserModel() = UserModel(uid, name, gender)

然后我们创建查询dao,room基本用法,不懂可以查看下上述的官网说明。

根据我们的模块化方案,dao存储在:features:feature_common:common_room_db模块中

//这里注意,增删改查都可以使用@Query操作符,只需要在后边写上需要操作的语句即可
//例如 @Query("DELETE FROM UserEntity") 也可以正常执行。
@Dao
interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun saveAsync(userEntity: UserEntity)

    @Update
    suspend fun updateAsync(userEntity: UserEntity)

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun saveSync(userEntity: UserEntity)

    @Delete
    suspend fun deleteAsync(userEntity: UserEntity)

    @Query("SELECT * FROM UserEntity WHERE remoteName=:remoteName")
    suspend fun getUserListAsync(remoteName: String): List<UserEntity>

    @Query("SELECT * FROM UserEntity WHERE remoteName=:remoteName")
    fun getUserListFlow(remoteName: String): Flow<List<UserEntity>>

    @Query("SELECT * FROM UserEntity WHERE uid=:uid")
    suspend fun getUserAsync(uid: String): UserEntity?

    @Query("SELECT * FROM UserEntity WHERE uid=:uid")
    fun getUserSync(uid: String): UserEntity?

    @Query("SELECT * FROM UserEntity WHERE uid=:uid")
    fun getUserFlow(uid: String): Flow<UserEntity?>
}

然后我们在data_xxxx模块中创建代理查询类,并提供将业务模型转为数据库模型&数据库模型转为业务模型的代理,方便使用。

代码如下

//这里我列举了 Async异步方式,Sync同步方式及flow方式进行数据的增删改查。
//sync方式需要创建room时调用allowMainThreadQueries(),否则会报错
//Async方式需要在协程中使用。
//flow需要协程的scope支持,尽量使用activity&fragment中的lifecycleScope来处理
object UserDB {
    private val dao: UserDao by lazy {
        RoomDB.INSTANCE.userDao()
    }

    suspend fun saveUserAsync(user: UserModel) {
        dao.saveAsync(user.toEntity("test"))
    }

    suspend fun updateUserAsync(user: UserModel) {
        dao.updateAsync(user.toEntity("test"))
    }

    fun saveUserSync(user: UserModel) {
        dao.saveSync(user.toEntity("test"))
    }

    suspend fun getUserAsync(uid: String): UserModel? {
        return dao.getUserAsync(uid)?.toUserModel()
    }

    fun getUserSync(uid: String): UserModel? {
        return dao.getUserSync(uid)?.toUserModel()
    }

    fun getUserFlow(uid: String): Flow<UserModel?> {
        return dao.getUserFlow(uid).map {
            it?.toUserModel()
        }
    }
}

如此,我们便达到了文章开头的使用方式。

完整项目地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容