Android Jetpack組件學習 Room

一、配置gradle

在build.gradle (Module: app)中

apply plugin: 'kotlin-kapt'
...
dependencies {
// Room components
implementation "androidx.room:room-runtime:$rootProject.roomVersion"
implementation "androidx.room:room-ktx:$rootProject.roomVersion"
kapt "androidx.room:room-compiler:$rootProject.roomVersion"
androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"

// Lifecycle components
implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.archLifecycleVersion"
kapt "androidx.lifecycle:lifecycle-compiler:$rootProject.archLifecycleVersion"
androidTestImplementation "androidx.arch.core:core-testing:$rootProject.androidxArchVersion"

// ViewModel Kotlin support
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.archLifecycleVersion"

// Coroutines
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$rootProject.coroutines"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$rootProject.coroutines"
}

kotlin {
    experimental {
        coroutines "enable"
    }
}

在project的build.gradle 中添加版本號

ext {
   roomVersion = '2.1.0-alpha06'
   archLifecycleVersion = '2.1.0-alpha04'
   androidxArchVersion = '2.0.0'
   coroutines = '1.2.0'
}

2、創建實體

先創建一個簡單的表
數據表
創建一個數據類

data class Word(val word: String)

爲其添加註解,使其對Room數據庫有意義

  • @Entity(tableName = “word_table”)
    每個@Entity類代表一個表。註釋您的類聲明以指示它是一個實體。如果希望它與類的名稱不同,請指定表的名稱。
  • @PrimaryKey
    每個實體都需要一個主鍵。爲了簡單起見,每個單詞都作爲自己的主鍵。
  • @ColumnInfo(name = “word”)
    如果希望它與成員變量的名稱不同,請在表中指定列的名稱。
    存儲在數據庫中的每個字段都必須是公共的或具有“getter”方法。
  • 可以在Room package summary reference找到完整的註釋列表。
@Entity(tableName = "word_table")
class Word(@PrimaryKey @ColumnInfo(name = "word") val word: String)

3、創建DAO

什麼是DAO?

  • DAO(數據訪問對象在編譯時驗證SQL並將其與方法關聯,這樣你就不必再擔心SQL …所有註釋都很簡單,如@insert
  • DAO必須是接口或抽象類。
  • 默認情況下,所有查詢都必須在單獨的線程上執行。
  • Room支持協程。因此,您的查詢可以使用suspend修飾符進行註解,並從協序或其他suspend 函數調用。
  • Room使用DAO爲代碼創建一個乾淨的API。

實現DAO
讓我們編寫一個DAO,它提供查詢以獲取所有單詞,插入單詞和刪除所有單詞。
1.創建一個新接口WordDao並調用它。
2.對類使用註解@Dao將其標識爲Room的DAO類。
3.聲明一個suspend 方法來插入一個單詞:suspend fun insert(word: Word)
4.使用註解@Insert。您不必提供任何SQL!(也有@Delete和@Update註解用於刪除和更新一行,但是這個應用沒有用到他們。)
5.聲明刪除所有單詞的方法:fun deleteAll()。
6.刪除多個實體沒有方便的註解,因此使用泛型註釋該方法@Query。
7.將SQL查詢作爲字符串參數提供給@Query註釋。
@Query(“DELETE FROM word_table”)
8.創建一個方法來獲得所有的返回值。
fun getAllWords(): List
9.使用SQL查詢註釋方法:
@Query(“SELECT * from word_table ORDER BY word ASC”)
這是完成的代碼:

@Dao
interface WordDao {

    @Query("SELECT * from word_table ORDER BY word ASC")
    fun getAllWords(): List<Word>

    @Insert
    suspend fun insert(word: Word)

    @Query("DELETE FROM word_table")
    fun deleteAll()
}

4、LiveData類

當數據發生變化時,您通常需要採取一些措施,例如在UI中顯示更新的數據。這意味着您必須觀察數據,以便在更改時,您可以做出反應。

根據數據的存儲方式,這可能很棘手。觀察應用程序的多個組件之間的數據更改可以在組件之間創建明確,嚴格的依賴關係路徑。這使得測試和調試變得困難等等。
LiveData,一個用於數據觀察的生命週期庫類,解決了這個問題。在方法描述中使用LiveData類型的返回值,當數據庫更新時,room會生成所有必要的代碼來更新LiveData。

如果獨立於Room使用LiveData,您必須管理更新數據,LiveData沒有公開的方法來更新存儲的數據。
如果要更新存儲的數據,必須使用MutableLiveData而不是LiveData,MutableLiveData類有兩個公共方法setValue(T)postValue(T)
,允許您設置LiveData對象的值。通常,ViewModel中使用MutableLiveData,然後ViewModel只向觀察者公開不可變的LiveData對象。

 @Query("SELECT * from word_table ORDER BY word ASC")
 fun getAllWords(): LiveData<List<Word>>

5、添加Room數據庫

什麼是Room

  • Room是一個位於sqlite數據庫之上的數據庫層。
  • Room負責處理以前使用sqliteOpenHelper處理的任務。
  • Room使用DAO向其數據庫發出查詢。
  • 默認情況下,爲了避免較差的UI性能,Room不允許您在主線程上發出查詢。當Room查詢返回時LiveData,查詢將在後臺線程上自動運行。
  • Room提供SQLite語句的編譯時檢查。

您的Room數據庫類必須是抽象的並擴展RoomDatabase。通常,整個應用程序只需要一個Room數據庫實例。
1.創建一個public abstract 類WordRoomDatabase ,繼承RoomDatabase
2.將類註釋爲Room數據庫,聲明屬於數據庫的實體,並設置版本號。列出實體將在數據庫中創建表。
3.定義與數據庫一起使用的DAO。爲每個@Dao提供一個抽象的“getter”方法。

@Database(entities = [Word::class], version = 1)
public abstract class WordRoomDatabase : RoomDatabase() {
   abstract fun wordDao(): WordDao
}

4.將WordRoomDatabase設置爲單例,以防止同時打開數據庫的多個實例。

@Database(entities = arrayOf(Word::class), version = 1)
public abstract class WordRoomDatabase : RoomDatabase() {

   abstract fun wordDao(): WordDao

   companion object {
        @Volatile
        private var INSTANCE: WordRoomDatabase? = null

        fun getDatabase(context: Context): WordRoomDatabase {
            val tempInstance = INSTANCE
            if (tempInstance != null) {
                return tempInstance
            }
            synchronized(this) {
                val instance = Room.databaseBuilder(
                        context.applicationContext,
                        WordRoomDatabase::class.java, 
                        "Word_database"
                    ).build()
                INSTANCE = instance
                return instance
            }
        }
   }
}

修改數據庫架構時,您需要更新版本號並定義遷移策略
作爲一個樣本,銷燬和重新創建策略就足夠了。但是,對於真正的應用程序,您必須實施遷移策略。請參閱瞭解使用Room進行遷移

6、創建Repository

Repository類抽象了對多個數據源的訪問。Repository不是體系結構組件庫的一部分,而是代碼分離和體系結構的建議最佳實踐。Repository類爲應用程序其餘部分的數據訪問提供了API。
Repository管理查詢並允許您使用多個後端。在最常見的示例中,Repository實現了決定是從網絡中獲取數據還是使用本地數據庫中緩存的結果的邏輯。
Repository class
實現Repository
1.創建一個名爲的public class WordRepository。
2.將DAO聲明爲構造函數中的私有屬性
3.將單詞列表添加爲公共屬性並對其進行初始化。Room在單獨的線程上執行所有查詢。當數據發生變化LiveData就會通知觀察者。
4.將單詞列表添加爲公共屬性並初始化它。room在單獨的線程上執行所有查詢。LiveData將在數據更改時通知觀察者。
5.爲insert()方法添加包裝。您必須在非UI線程上調用它,否則您的應用程序將崩潰。Room確保您不會在主線程上執行任何長時間運行的操作,從而阻塞UI。添加@workerthread註釋,以標記需要從非UI線程調用此方法。添加suspend修飾符以告訴編譯器需要從協程或其他suspend 函數調用此修飾符。

class WordRepository(private val wordDao: WordDao) {

    val allWords: LiveData<List<Word>> = wordDao.getAllWords()

    @WorkerThread
    suspend fun insert(word: Word) {
        wordDao.insert(word)
    }
}

7、創建ViewModel

ViewModel向UI提供數據,並在配置更改後繼續運行。充當存儲庫和UI之間的通信中心。還可以使用ViewModel在fragment之間共享數據。視圖模型是lifecycle library的一部分。
ViewModel
ViewModel以一種生命週期意識的方式保存應用程序的UI數據,這種方式可以在配置更改後存活下來。將應用程序的UI數據與活動類和片段類分離可以讓您更好地遵循單一的責任原則:activity和fragment負責將數據繪製到屏幕上,而ViewModel可以負責保存和處理UI所需的所有數據。

//創建一個名爲WordViewModel的類,該類將應用程序作爲參數並擴展AndroidViewModel。
class WordViewModel(application: Application) : AndroidViewModel(application) {

    //添加私有成員變量以保存對存儲庫的引用。
    private val repository: WordRepository
    //使用LiveData並緩存getAlphabetizedWords返回的內容有幾個好處:
    //我們可以將觀察者放在數據上(而不是輪詢更改)並只更新
    //數據實際更改時的UI。
    //存儲庫通過ViewModel與UI完全分離。

    //添加私有的livedata成員變量以緩存單詞列表。
    val allWords: LiveData<List<Word>>

    //創建一個init塊,該塊從WordRoomDatabase獲取對WordDAO的引用,並基於它構造WordRepository。
    init {
        val wordsDao = WordRoomDatabase.getDatabase(application, viewModelScope).wordDao()
        repository = WordRepository(wordsDao)
        //在init塊中,使用來自存儲庫的數據初始化allWords屬性:
        allWords = repository.allWords
    }

    /**
     * 啓動新協程以非阻塞方式插入數據
     * 創建一個包裝insert()方法,該方法調用存儲庫的insert()方法。
     * 這樣,insert()的實現就完全隱藏在UI中。
     * 我們希望從主線程調用insert()方法,
     * 因此我們將基於前面定義的協程範圍啓動一個新的協程。
     * 因爲我們正在執行數據庫操作,所以我們使用的是IO調度程序。
     */
    fun insert(word: Word) = viewModelScope.launch {
        repository.insert(word)
    }
}

重要提示:ViewModel不是OnSaveInstanceState()方法的替代品,因爲ViewModel在進程關閉後無法生存。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章