iT邦幫忙

2022 iThome 鐵人賽

DAY 3
0
Mobile Development

從開發瀏覽器 APP 學習 Android 實戰技巧,並搭上 Jetpack Compose 的列車系列 第 3

[Day3] 從開發瀏覽器 APP 學習實戰技巧 -- Android 必備組合技: Room + Flow + ViewModel

  • 分享至 

  • xImage
  •  

EinkBro App 中的實作大都是用很舊很舊的技術。雖然隨著功能不斷增加,我有逐漸把一些檔案翻新成 Kotlin,和盡量把相關的邏輯抽出到獨立的 class 或檔案中,不過整體來說,架構還是很老派(其實就是沒有什麼架構,全部的邏輯幾乎都塞在同一個 Activity 中)。

去年提到導入了 DI library Koin,讓部分元件可以用注入的方式在程式中的各個地方能夠存取得到。今天要介紹的是如何利用 Room + Flow 的組合,讓畫面上的書籤列表可以自動更新,不用在每個有 CRUD 的場景手動呼叫。

步驟

  1. 首先要在 build.gradle 中加入 ViewModel 的支援
implementation ‘androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1’
  1. 在 Dao 類別中,加入會回傳 Flow 的函式。原本雖然已經有回傳 List 的函式,其型式是 suspend,可以透過 coroutine 拿到非同步的結果;但拿完之後,並不會事後再收到任何更新。如果想要再次拿到資料庫中最新的資料,得要再呼叫一次才行。但改成回傳 Flow 的話,當資料庫有修改時,利用 Flow 的人都還是會收到通知。
@Query("SELECT * FROM bookmarks WHERE parent = :parentId ORDER BY title COLLATE NOCASE ASC")
fun getBookmarksByParentFlow(parentId: Int): Flow<List<Bookmark>>
  1. 建立 ViewModel,以便資料傳遞給 UI 層
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")                             
  }                         
}
  1. BrowserActivity.kt 中建立 bookmarkViewModel,以利後面使用
private val bookmarkViewModel: BookmarkViewModel by viewModels {                            BookmarkViewModelFactory(bookmarkManager.bookmarkDao)                             
}
  1. 當要開啟書籤列表時,把 bookmarkViewModel 傳進去,讓它可以跟 adapter 串在一起。

https://ithelp.ithome.com.tw/upload/images/20220915/20140260ZhjDx12Gq6.png

  1. 再來就是 adapter 的初始化部分。193 行到 197 行是這個重構最主要的關鍵。bookmarkViewModel.bookmarksByParent() 會回傳 Flow 物件;在這邊利用 collect 取得結果,並將它代入 adapter 中。當資料庫有改變時,collect 會再次呼叫 195 行,讓 adapter 會再次取得新的資料,從而達到更新畫面的效果。當 submitList 送來新的資料時,BookmarkAdapter 因為有實作 DiffUtil.ItemCallback<Bookmark>(),它會只針對有更新的部分做變化。

https://ithelp.ithome.com.tw/upload/images/20220915/20140260esChyLPuQN.png

經過上述的修改,書籤列表的呈現就會在資料庫有改變時,自動更新畫面。之前在加新書籤,刪除書籤,或是修改書籤時實作的手動改變 adapter 內容的實作,就可以全部拿掉啦。

目前 EinkBro App 中還有其他地方有使用到資料庫,像是瀏覽記錄,擋廣告白名單等,但大都還沒有改成用 Room 儲存到資料庫中,希望以後有空時,可以把所有資料庫相關的處理和呈現也都利用這種方式重構,減少不必要的手動更新畫面的邏輯,程式也可以看起來更加地簡潔。

參考資料

Android 官方的教學
比我的說明清楚許多。一步一步照著作就可以完成我上面的那些內容。

EinkBro App 的修正 commit


上一篇
[Day2] 從開發瀏覽器 APP 學習實戰技巧 -- 將網頁畫面由黑轉白
下一篇
[Day4] 從開發瀏覽器 APP 學習實戰技巧 -- 雙開:同時顯示兩個網頁
系列文
從開發瀏覽器 APP 學習 Android 實戰技巧,並搭上 Jetpack Compose 的列車31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言