iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 25
0
Mobile Development

大一之 Android Kotlin 自習心路歷程系列 第 25

[Day 25] Android in Kotlin: Room Database 操作示範

room 是一個簡單好用的本地資料庫,比起一般的 sqlite,它更為直覺去使用,而且也跟 live data 有著良好的互動性。

在 room database 中分為三個主要的 class 部分

  • Entity:一個 table 要有什麼欄位
  • Dao:所有的 query 方法
  • Database:建立資料庫和資料表

Entity

一個 entity 必須要有 table name 和一個 primary key

@Entity(tableName = "comment_table")
data class DataEntity(
    @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "_id") var id: Int,
    @ColumnInfo(name = "content") var content: String
)

在筆者的專案中,還會常需要用到 foreign key 跟索引,如果加上去,會像這樣子

@Entity(
    tableName = "comment_table",
    foreignKeys = [
        ForeignKey(
            entity = AuthorEntity::class,
            parentColumns = arrayOf("_id"),
            childColumns = arrayOf("author_id"),
            onDelete = ForeignKey.CASCADE),
        ForeignKey(
            entity = DateEntity::class,
            parentColumns = arrayOf("_id"),
            childColumns = arrayOf("date_id"),
            onDelete = ForeignKey.CASCADE),
    indices = [Index(value = ["_id"])]
)
data class CommentEntity(
    @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "_id") var id: Int,
    @ColumnInfo(name = "author_id") var authorId: Int,
    @ColumnInfo(name = "date_id") var dateId: Int,
    @ColumnInfo(name = "content") var content: String
)

透過 foreign key 的建立可以完善資料庫的正規化,
foreign key 填入

  • 來自哪一個 entity
  • 連接自哪一個欄位名稱
  • 外鍵的欄位名稱
  • 如果有資料改變時如何動作
    最後一個選項我都是填入 CASCADE,他會將有關聯的資料進行刪除或更新。

Dao

@Dao
interface CommentDao {
    @Query("SELECT * FROM  comment_table")
    fun getAllComments(): LiveData<List<CategoryEntity>>    

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertComment(comment: CommentEntity)
}

所有的 CRUD 方法,都會放在這裡給其他類別使用。
如果想要新增更多,就加入更多的 function 並且在 @Query() 打入想要的 sqlite 語法。

在 @Insert 裡面填入如果發生資料重複/衝突時的處理方式,筆者常用的只有兩個

  • REPLACE:蓋掉 (最常用)
  • IGONER:忽略 (就還是舊的資料)

如果只想要抓特定的資料也是可以的,用冒號代表其引數

    @Query("SELECT * FROM comment_table WHERE date_id IN (:date)")
    fun getDailyComments(date: Int): LiveData<List<ExpenseEntity>>

只抓 dateId = 傳入的 date 的資料

Database

@Database(
    entities = [
        CommentEntity::class,
        DateEntity::class,
        AuthorEntity::class],
    version = 1
)
abstract class AccountingDatabase : RoomDatabase(){

    //取得 dao 的方法
    abstract fun getAccountingDao(): AccountingDao

    //官方推薦的 Singleton 寫法,因為實體的產生很耗資源,而且也不需要多個資料庫實體
    companion object {
        @Volatile
        private var INSTANCE: AccountingDatabase? = null

        fun getDatabase(context: Context, scope: CoroutineScope): AccountingDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance =
                    Room.databaseBuilder(
                        context.applicationContext,
                        AccountingDatabase::class.java,
                        "accounting_database"
                    ).fallbackToDestructiveMigration()
                        .allowMainThreadQueries()
                        .addCallback(ItemDatabaseCallback(scope))
                        .build()
                INSTANCE = instance
                // return instance
                instance
            }
        }
    }
}

這一段是從 codelab 上複製的
一個繼承於 RoomDatabase 的抽象類別,其中如果 database 為 null 的話就建立一個

Repository

class Repository(private val commentDao: CommentDao) {
    @Suppress("RedundantSuspendModifier")
    @WorkerThread
    suspend fun insertItem(comment: CommentEntity) {
        accountingDao.insertComment(comment)
    }
    
    fun getAllComments(): LiveData<List<CommentEntity>>{
        return commentDao.getAllComments()
    }
}

在 Repository 中加入 public 的方法給外部使用
上述都完成了就可以來讓其他 class 取得資料
例如 view model:

class CommentsViewModel(application: Application): AndroidViewModel(application){
    private val repository: Repository
    
    var allComments: LiveData<List<CommentEntity>>
    
    init {
        val listDao = AccountingDatabase.getDatabase(application, viewModelScope).getAccountingDao()
        repository = Repository(listDao)
        
        allComments= repository.getAllComments
    }
    
    fun insertComment(comment: CommentEntity) = viewModelScope.launch(Dispatchers.IO) {
        repository.insertComment(comment)
    }
}

另外,如上圖所示,要做 insert 時要使用 coroutines 確保安全

其實就是不斷的寫 fun 讓 class 與 class 之間做溝通,多看文章跟多寫幾次不難懂的!


上一篇
[Day 24] Android in Kotlin: 資料庫第一第二正規化入門
下一篇
[Day 26] Android in Kotlin: Data Binding 簡單示範
系列文
大一之 Android Kotlin 自習心路歷程30

尚未有邦友留言

立即登入留言