EinkBro App 中的實作大都是用很舊很舊的技術。雖然隨著功能不斷增加,我有逐漸把一些檔案翻新成 Kotlin,和盡量把相關的邏輯抽出到獨立的 class 或檔案中,不過整體來說,架構還是很老派(其實就是沒有什麼架構,全部的邏輯幾乎都塞在同一個 Activity 中)。
去年提到導入了 DI library Koin,讓部分元件可以用注入的方式在程式中的各個地方能夠存取得到。今天要介紹的是如何利用 Room + Flow 的組合,讓畫面上的書籤列表可以自動更新,不用在每個有 CRUD 的場景手動呼叫。
build.gradle
中加入 ViewModel
的支援implementation ‘androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1’
@Query("SELECT * FROM bookmarks WHERE parent = :parentId ORDER BY title COLLATE NOCASE ASC")
fun getBookmarksByParentFlow(parentId: Int): Flow<List<Bookmark>>
class BookmarkViewModel(
private val bookmarkDao: BookmarkDao
): ViewModel() {
fun bookmarksByParent(parentId: Int): Flow<List<Bookmark>> = bookmarkDao.getBookmarksByParentFlow(parentId)
}
class BookmarkViewModelFactory(
private val bookmarkDao: BookmarkDao
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(BookmarkViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return BookmarkViewModel(bookmarkDao) as T
}
throw IllegalAccessException("Unknown ViewModel class")
}
}
BrowserActivity.kt
中建立 bookmarkViewModel
,以利後面使用private val bookmarkViewModel: BookmarkViewModel by viewModels { BookmarkViewModelFactory(bookmarkManager.bookmarkDao)
}
bookmarkViewModel.bookmarksByParent()
會回傳 Flow 物件;在這邊利用 collect 取得結果,並將它代入 adapter 中。當資料庫有改變時,collect 會再次呼叫 195 行,讓 adapter 會再次取得新的資料,從而達到更新畫面的效果。當 submitList 送來新的資料時,BookmarkAdapter
因為有實作 DiffUtil.ItemCallback<Bookmark>()
,它會只針對有更新的部分做變化。經過上述的修改,書籤列表的呈現就會在資料庫有改變時,自動更新畫面。之前在加新書籤,刪除書籤,或是修改書籤時實作的手動改變 adapter 內容的實作,就可以全部拿掉啦。
目前 EinkBro App 中還有其他地方有使用到資料庫,像是瀏覽記錄,擋廣告白名單等,但大都還沒有改成用 Room 儲存到資料庫中,希望以後有空時,可以把所有資料庫相關的處理和呈現也都利用這種方式重構,減少不必要的手動更新畫面的邏輯,程式也可以看起來更加地簡潔。
Android 官方的教學
比我的說明清楚許多。一步一步照著作就可以完成我上面的那些內容。