手把手教你搭建android模塊化項目框架(七)存儲之room

上期我們聊到room蝙昙,本期就來簡單說一下room的用法杭棵。
常規(guī)room我們不聊怎么用了客年,跟著官方文檔一步一步使用即可谎僻。

傳送門

老規(guī)矩,先上效果膛锭。

 override fun testRoom() {
        //常規(guī)flow監(jiān)聽
        lifecycleScope.launchWhenResumed {
            UserDB.getUserFlow("test1").collect {
                Log.v("collect", "collect: $it")
            }
        }

        //常規(guī)flow操作符監(jiān)聽
        UserDB.getUserFlow("test1")
//            .catch {  }//錯誤捕獲
            .onEach {
                Log.v("launchIn", "launchIn: $it")
            }
//            .catch {  } //onEach中的錯誤捕獲
            .launchIn(lifecycleScope).start()

        //轉(zhuǎn)化為liveData后的監(jiān)聽 private val userModelObs = UserDB.getUserFlow("test1").asLiveData()
        //live_data_ktx 庫  將flow轉(zhuǎn)為livedata private val userModelObs = UserDB.getUserFlow("test1").asLiveData()
        userModelObs.observe(this) {
            Log.v("livedata", "livedata: $it")
        }

        //修改數(shù)據(jù)
        lifecycleScope.launchWhenResumed {
            UserDB.updateUserAsync(UserModel("test1", "test6", 5))
        }
    }
//打印結(jié)果
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)

我們看到每個結(jié)果打印了兩次粮坞,其中name由5變成了6蚊荣,其中5是原始值,6是最后修改數(shù)據(jù)使用的值莫杈,這里就是使用flow的好處了互例,修改數(shù)據(jù)庫直接能夠反饋到所有監(jiān)聽flow的地方,并且flow自帶生命周期筝闹,無需擔(dān)心內(nèi)存泄露問題媳叨。如此處理,也能讓本地數(shù)據(jù)杜絕EventBus等事件總線來回傳遞关顷,造成Event災(zāi)難糊秆。

下面我們一步一步來實現(xiàn)這個效果。

初始化room议双,這里我與官方處理的方式略有差異
根據(jù)我們的模塊化方案痘番,room初始化我們放置在:features:feature_common:common_room_db模塊中

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

    //application初始化時調(diào)用,如果采用其他的單例方式需要每次傳入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" //數(shù)據(jù)庫名稱
            )
                .allowMainThreadQueries() //允許啟用同步查詢汞舱,即:允許主線程可以查詢數(shù)據(jù)庫,這個配置要視情況使用宗雇,一般不推薦同步查詢
                .fallbackToDestructiveMigration()//如果數(shù)據(jù)庫升級失敗了昂芜,刪除重新創(chuàng)建
                .enableMultiInstanceInvalidation()//多進程查詢支持
//              .addMigrations(MIGRATION_1_2) //數(shù)據(jù)庫版本升級,MIGRATION_1_2為要執(zhí)行的表格執(zhí)行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]) //自定義數(shù)據(jù)處理轉(zhuǎn)換泌神,這里我們將list都轉(zhuǎn)為json存儲
abstract class AppDataBase : RoomDatabase() {
    abstract fun userDao(): UserDao
}

//本類可以根據(jù)具體業(yè)務(wù)需求自行處理,這里隨便寫了個demo舞虱,沒有經(jīng)過測試欢际。
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)
    }
}

接下來我們創(chuàng)建table,這里我們將數(shù)據(jù)庫模型與實際使用的模型完全隔離開砾嫉,并且使用擴展方法進行數(shù)據(jù)轉(zhuǎn)換處理幼苛,避免業(yè)務(wù)模型變更影響到數(shù)據(jù)變更窒篱,到時候維護起來比較麻煩焕刮。并且難以做數(shù)據(jù)庫升級。本文中所有entity結(jié)尾的類為數(shù)據(jù)庫模型墙杯,model結(jié)尾的類為業(yè)務(wù)模型配并。

根據(jù)我們的模塊化方案,其中Entity放置在:features:feature_common:common_room_db模塊中高镐,Model類及轉(zhuǎn)換類放置在data_xxxx模塊中溉旋,依賴關(guān)系為,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
)
//數(shù)據(jù)轉(zhuǎn)換
fun UserModel.toEntity(remoteName: String) =
    UserEntity(uid = this.uid, remoteName = remoteName, name = this.name, gender = gender)

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

然后我們創(chuàng)建查詢dao嫉髓,room基本用法观腊,不懂可以查看下上述的官網(wǎng)說明邑闲。

根據(jù)我們的模塊化方案,dao存儲在:features:feature_common:common_room_db模塊中

//這里注意,增刪改查都可以使用@Query操作符梧油,只需要在后邊寫上需要操作的語句即可
//例如 @Query("DELETE FROM UserEntity") 也可以正常執(zhí)行苫耸。
@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模塊中創(chuàng)建代理查詢類,并提供將業(yè)務(wù)模型轉(zhuǎn)為數(shù)據(jù)庫模型&數(shù)據(jù)庫模型轉(zhuǎn)為業(yè)務(wù)模型的代理儡陨,方便使用褪子。

代碼如下

//這里我列舉了 Async異步方式,Sync同步方式及flow方式進行數(shù)據(jù)的增刪改查骗村。
//sync方式需要創(chuàng)建room時調(diào)用allowMainThreadQueries()嫌褪,否則會報錯
//Async方式需要在協(xié)程中使用。
//flow需要協(xié)程的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()
        }
    }
}

如此笼痛,我們便達到了文章開頭的使用方式。

完整項目地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末信轿,一起剝皮案震驚了整個濱河市晃痴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌财忽,老刑警劉巖倘核,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異即彪,居然都是意外死亡紧唱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門隶校,熙熙樓的掌柜王于貴愁眉苦臉地迎上來漏益,“玉大人,你說我怎么就攤上這事深胳〈掳蹋” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵舞终,是天一觀的道長轻庆。 經(jīng)常有香客問我,道長敛劝,這世上最難降的妖魔是什么余爆? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮夸盟,結(jié)果婚禮上蛾方,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好桩砰,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布拓春。 她就那樣靜靜地躺著,像睡著了一般亚隅。 火紅的嫁衣襯著肌膚如雪痘儡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天枢步,我揣著相機與錄音沉删,去河邊找鬼。 笑死醉途,一個胖子當(dāng)著我的面吹牛矾瑰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播隘擎,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼殴穴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了货葬?” 一聲冷哼從身側(cè)響起采幌,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎震桶,沒想到半個月后休傍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡蹲姐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年磨取,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柴墩。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡忙厌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出江咳,到底是詐尸還是另有隱情逢净,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布歼指,位于F島的核電站爹土,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏东臀。R本人自食惡果不足惜着饥,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一犀农、第九天 我趴在偏房一處隱蔽的房頂上張望惰赋。 院中可真熱鬧,春花似錦、人聲如沸赁濒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拒炎。三九已至挪拟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間击你,已是汗流浹背玉组。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留丁侄,地道東北人惯雳。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像鸿摇,于是被迫代替她去往敵國和親石景。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容