使用Kotlin構(gòu)建MVVM應(yīng)用程序—第三部分:Room

目錄

寫在前面

這是使用Kotlin構(gòu)建MVVM應(yīng)用程序—第三部分:Room

在上一篇中我們了解了MVVM是怎么處理網(wǎng)絡(luò)數(shù)據(jù)的麻削,而這一篇?jiǎng)t介紹的是如何進(jìn)行數(shù)據(jù)持久化掏熬。

Room

Room是google推出的一個(gè)數(shù)據(jù)持久化庫王悍,它是 Architecture Component的一部分。它讓SQLiteDatabase的使用變得簡(jiǎn)單,大大減少了重復(fù)的代碼,并且把SQL查詢的檢查放在了編譯時(shí)。

Room使用起來非常簡(jiǎn)單疆液,而且可以和RxJava配合使用,和我們的技術(shù)體系十分契合棚放。

加入依賴

首先在項(xiàng)目的build.gradle中加入

allprojects {
    repositories {
        maven {
            url 'https://maven.google.com'
        }
        jcenter()
    }
}

接著在app的build.gradle中加入它的依賴

//room (local)
implementation 'android.arch.persistence.room:runtime:1.0.0'
implementation 'android.arch.persistence.room:rxjava2:1.0.0'
kapt 'android.arch.persistence.room:compiler:1.0.0'
//facebook出品枚粘,可在Chrome中查看數(shù)據(jù)庫
implementation 'com.facebook.stetho:stetho:1.5.0'

現(xiàn)在的結(jié)構(gòu)

MVVM

這里我們多了一層Repository,使用這一層來確保單一數(shù)據(jù)源飘蚯,保證數(shù)據(jù)來源的唯一和正確性(即不管是來自網(wǎng)絡(luò)或是本地緩存的)馍迄。ViewModel層并不需要知道它使用到的數(shù)據(jù)是怎么來的,就好似開發(fā)者并不需要知道設(shè)計(jì)師是如何畫出UI圖的一樣局骤。

開始正文

使用Room進(jìn)行持久化

  1. 新建相應(yīng)的表

Room為每個(gè)用@Entity注解了的類創(chuàng)建一張表

@Entity(tableName = "articles")
class Article(var title: String?){

    @PrimaryKey
    @ColumnInfo(name = "articleid")
    var id: Int = 0
    var content: String? = null
    var readme: String? = null
    @SerializedName("describe")
    var description: String? = null
    var click: Int = 0
    var channel: Int = 0
    var comments: Int = 0
    var stow: Int = 0
    var upvote: Int = 0
    var downvote: Int = 0
    var url: String? = null
    var pubDate: String? = null
    var thumbnail: String? = null
}
  1. 創(chuàng)建相關(guān)的Dao

相當(dāng)于Retrofit中的api接口

DAO負(fù)責(zé)定義操作數(shù)據(jù)庫的方法攀圈。在SQLite實(shí)現(xiàn)的版本中,所有的查詢都是在LocalUserDataSource文件中完成的峦甩,里面主要是 使用了Cursor對(duì)象來完成查詢的工作赘来。有了Room,我們不再需要Cursor的相關(guān)代碼凯傲,而只需在Dao類中使用注解來定義查詢犬辰。

@Dao
interface PaoDao{

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insetAll(articles: List<Article>)

    @Query("SELECT * FROM Articles WHERE articleid= :id")
    fun getArticleById(id:Int):Single<Article>


    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertArticle(article :Article)

}
  1. 創(chuàng)建數(shù)據(jù)庫

    相當(dāng)于創(chuàng)建RetrofitClient對(duì)象

    我們需要定義一個(gè)繼承了RoomDatabase的抽象類。這個(gè)類使用@Database來注解冰单,列出它所包含的Entity以及操作它們的 DAO 幌缝。

    @Database(entities = arrayOf(Article::class),version = 1)
    abstract class AppDatabase :RoomDatabase(){
    
        abstract fun paoDao(): PaoDao
    
        companion object {
            @Volatile private var INSTANCE: AppDatabase? = null
            fun getInstance(context: Context): AppDatabase =
                    INSTANCE ?: synchronized(this) {
                        INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
                    }
    
            private fun buildDatabase(context: Context) =
                    Room.databaseBuilder(context.applicationContext,
                            AppDatabase::class.java, "app.db")
                            .build()
        }
    
    }
    

Over ,集成Room十分的簡(jiǎn)單诫欠。

更多關(guān)于Room的使用方法涵卵,它的遷移,表之間的關(guān)聯(lián)和字段荒叼。

推薦查看泡網(wǎng)的Room專題:Room

實(shí)踐

這里我們對(duì)上一篇中的從服務(wù)器端獲取到的Article文章進(jìn)行持久化轿偎。

  1. 修改一下Model層的代碼,添加Repository作為ViewModel層的數(shù)據(jù)源
class PaoRepo constructor(private val remote:PaoService,private val local :PaoDao){
    //首先查看本地?cái)?shù)據(jù)庫是否存在該篇文章
    fun getArticleDetail(id:Int)= local.getArticleById(id)
            .onErrorResumeNext {
                //本地?cái)?shù)據(jù)庫不存在被廓,會(huì)拋出EmptyResultSetException
                //轉(zhuǎn)而獲取網(wǎng)絡(luò)數(shù)據(jù),成功后保存到數(shù)據(jù)庫
                remote.getArticleDetail(id)
                        .doOnSuccess { local.insertArticle(it) }
            }
}

我們的目錄結(jié)構(gòu)會(huì)如下圖所示:

結(jié)構(gòu)
  1. 修改我們的ViewModel層的數(shù)據(jù)源

在上一篇中我們使用的是PaoService網(wǎng)絡(luò)數(shù)據(jù)作為數(shù)據(jù)源坏晦,這里只需要修改為PaoRepo

class PaoViewModel(private val repo: PaoRepo) 

以后統(tǒng)一使用PaoRepo來為PaoViewModel提供數(shù)據(jù)

  1. 在View層中將PaoRepo注入到PaoViewModel
//////model
val remote=Retrofit.Builder()
        .baseUrl(Constants.HOST_API)
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .addConverterFactory(GsonConverterFactory.create())
        .build().create(PaoService::class.java)
val local=AppDatabase.getInstance(applicationContext).paoDao()
val repo = PaoRepo(remote, local)
/////ViewModel
mViewMode= PaoViewModel(repo)
////binding
mBinding.vm=mViewMode

看看效果

image

Roomigrant

Roomigrant is a helper library to automatically generate Android Room library migrations using compile-time code generation.

這是一個(gè)幫助開發(fā)者進(jìn)行Room數(shù)據(jù)遷移的庫,使用起來非常方便。

在使用Room的過程中昆婿,或多或少都會(huì)遇到增加表间护、改變表字段的情況。但是Room的遷移比較麻煩挖诸,可以看看理解Room的數(shù)據(jù)遷移這篇文章,增加新表還好法精,只需要修改版本號(hào)多律,但是如果需要修改表字段的話,由于sqlite的歷史原因:SQLite的ALTER TABLE命令非常局限搂蜓,只支持重命名表以及添加新的字段狼荞。

因此我們需要:

  • 創(chuàng)建一個(gè)新的臨時(shí)表,
  • 把users表中的數(shù)據(jù)拷貝到臨時(shí)表中
  • 丟棄users表
  • 把臨時(shí)表重命名為users

使用Room帮碰,Migration的實(shí)現(xiàn)是這樣的:

static final Migration MIGRATION_3_4 = new Migration(3, 4) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        // Create the new table
        database.execSQL(
                "CREATE TABLE users_new (userid TEXT, username TEXT, last_update INTEGER, PRIMARY KEY(userid))");
// Copy the data
        database.execSQL(
                "INSERT INTO users_new (userid, username, last_update) SELECT userid, username, last_update FROM users");
// Remove the old table
        database.execSQL("DROP TABLE users");
// Change the table name to the correct one
        database.execSQL("ALTER TABLE users_new RENAME TO users");
    }
};

對(duì)于開發(fā)者還需要編寫大量的sql語句相味,可以說是不友好了。

但是幸運(yùn)的是Roomigrant幫助我們做到了以上的事殉挽,對(duì)于修改表字段丰涉,只需要簡(jiǎn)單的

自定義一個(gè)Rule,通知一下什么表下的哪個(gè)字段需要修改

// version 3 的users表的字段uId
// version 4 相應(yīng)的字段改為了userId
@FieldMigrationRule(version1 = 3, version2 = 4, table = "users", field = "uId")
fun migrate_3_4_Object1Dbo_intVal(): String {
    return "`users`.`userId`"
}

這樣Roomigrant便可以幫助我們生成那些模板式的代碼斯碌。

寫在最后

本項(xiàng)目的github地址:https://github.com/ditclear/MVVM-Android

更多的例子可以查看:https://github.com/ditclear/PaoNet

這是使用Kotlin構(gòu)建MVVM項(xiàng)目的第三部分一死,主要講了怎么在MVVM中進(jìn)行數(shù)據(jù)的持久化以及為ViewModel層提供Repository作為唯一的數(shù)據(jù)源。

總結(jié)一下前三篇的內(nèi)容便是:

使用Retrofit提供來自服務(wù)端的數(shù)據(jù)傻唾,使用Room來進(jìn)行持久化投慈,然后提供一個(gè)Repository來為ViewModel提供數(shù)據(jù),ViewModel層利用RxJava來進(jìn)行數(shù)據(jù)的轉(zhuǎn)換冠骄,配合DataBinding引起View層的變化伪煤。

邏輯很清晰了,但唯一的遺憾便是為了提供一個(gè)ViewModel我們需要寫太多模板化的代碼了

//////model
val remote=Retrofit.Builder()
        .baseUrl(Constants.HOST_API)
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .addConverterFactory(GsonConverterFactory.create())
        .build().create(PaoService::class.java)
val local=AppDatabase.getInstance(applicationContext).paoDao()
val repo = PaoRepo(remote, local)
/////ViewModel
mViewMode= PaoViewModel(repo)

如果能不寫該多好凛辣。

上帝說:可以抱既。

所以下一篇的內(nèi)容便是依賴注入—Dagger2,從入門到放棄到恍然大悟到愛不釋手蟀给。蝙砌。。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末跋理,一起剝皮案震驚了整個(gè)濱河市择克,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌前普,老刑警劉巖肚邢,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡骡湖,警方通過查閱死者的電腦和手機(jī)贱纠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來响蕴,“玉大人谆焊,你說我怎么就攤上這事∑忠模” “怎么了辖试?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)劈狐。 經(jīng)常有香客問我罐孝,道長(zhǎng),這世上最難降的妖魔是什么肥缔? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任莲兢,我火速辦了婚禮,結(jié)果婚禮上续膳,老公的妹妹穿的比我還像新娘改艇。我一直安慰自己,他們只是感情好坟岔,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布遣耍。 她就那樣靜靜地躺著,像睡著了一般炮车。 火紅的嫁衣襯著肌膚如雪舵变。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天瘦穆,我揣著相機(jī)與錄音纪隙,去河邊找鬼。 笑死扛或,一個(gè)胖子當(dāng)著我的面吹牛绵咱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播熙兔,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼悲伶,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了住涉?” 一聲冷哼從身側(cè)響起麸锉,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎舆声,沒想到半個(gè)月后花沉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柳爽,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年碱屁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了磷脯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡娩脾,死狀恐怖赵誓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情柿赊,我是刑警寧澤架曹,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站闹瞧,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏展辞。R本人自食惡果不足惜奥邮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望罗珍。 院中可真熱鬧洽腺,春花似錦、人聲如沸覆旱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扣唱。三九已至藕坯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間噪沙,已是汗流浹背炼彪。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留正歼,地道東北人辐马。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像局义,于是被迫代替她去往敵國(guó)和親喜爷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359