在 ViewModel 處理 BroadcastReceiver 的資料

MVVM 是將程式的架構分成 ViewModel-View-Model, View 層是負責繪製畫面, ViewModel 是資料與畫面中間的橋樑,在 ViewModel 中會使用 LiveData 存放資料,View 則會 Observe 在 ViewModel 層中的 LiveData,當資料有更新的時候,有在 View 中被 Observe 的 值就會被更新。也就是說, 在View 層中不用考慮值是如何修改的,只要考慮畫面上需要關注哪些值,當這些值更新之後,我們要做什麼樣的動作。最後是 Model ,所有我們的資料來源都是在 Model 這一層中, 一般會使用 Repository 這一層來決定資料來源(遠端資料、本地資料)。

架構圖如下:

MVVM 架構圖

問題來了,今天如果我們想要透過 BroadReceiver 來取得資料,那麼要怎麼在這個架構上更新呢?

我們知道使用 registerReceiver 的時候,需要 Activity 的 Context,但是在 MVVM 的架構之下,ViewModel 是不可以持有對 Activity Context 引用的類別。

Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context. Ref

所以我們不能在 ViewModel 中呼叫 registerReceiver ,我們必須還是要讓 FragmentActivity 自行去呼叫 registerReceiver

思路是這樣的

Fragment 中呼叫 registerReceiver 註冊 BroadcastReceiver ,當 BroadcastReceiveronReceive 被呼叫時,更新在 BroadcastReceiver 類別中的一個 MutableLiveData 並且更新 Repository 裏面的 LiveData ,最後因為 ViewModel 的 LiveData 是由 Repository 所提供,這樣就可以完成更新。

最後的流程圖簡化如下:

MVVM with BroadcastReceiver

程式碼如下:

MyRepository.kt

MyRepository.kt

在 MyRepository.kt (Model 層) 的動作如下

  1. 宣告 MyRepository 為 object ,讓 MyRepository 成為一個靜態類別,原因是我們想要在 BroadcastReceiver 以及 ViewModel 層都使用到相同的 Repository。
  2. 宣告一個 MutableLiveData , 是用來儲存可以改變的 LiveData ,當我們每次的更新,都會寫入這個屬性中。
  3. 呈上,將 MutableLiveData 所得到的值,傳入 LiveData 中,這麼做的好處是,採用寫入與讀取為分開的兩個屬性讓同一個屬性不會同時做兩件事。
  4. 提供一個函式 update 用來更新 _result 內的值。

MyReceiver.kt

MyReceiver.kt

在 MyReceiver 的動作如下:

  1. 新增一個類別,並且繼承 BroadcastReceiver
  2. 當 BroadcastReceiver 的值回傳回來時,利用 MyRepository 提供的 update 函式更新 Repository 的 內容。

MyViewModel.kt

MyViewModel.kt

在 MyViewModel (ViewModel 層) 動作如下:

  1. 只要將 MyRepository 中的 result 指定在 ViewModel 內的 result。
  2. 這邊的 result 的型別為 LiveData ,目的是要讓 Fragment 能夠 observe 它。

MyFragment.kt

MyFragment.kt

在 Fragment (View 層) 的動作如下:

  1. 宣告 ViewModel。
  2. 宣告 Receiver。
  3. 在 Fragment 的 onStart() 註冊 BroadcastReceiver。
  4. 在 Fragment 的 onStop() 取消註冊 BroadcastReceiver。
  5. onVierCreated 中, observe ViewModel 的 result,當 ViewModel 的 result 有更新時,就會同時更新到這邊。

小結

MVVM 的目的就是關注點分離,在這樣的架構中,我們應該要將畫面歸畫面,商業邏輯歸商業邏輯。在研究之前,我一直想說不要在 Fragment 中呼叫 registerReceiver ,因為這樣子更新資料的動作似乎就在 Fragment 中,違反了關注點分離。所以我原本是想要將 Activity 的 context 傳給 ViewModel ,並在 ViewModel 中去呼叫 registerReceiver。但是,這樣是不被允許的, ViewModel 不可以持有對 Activity context 引用的參考(Reference)。

後來看到 StackOverflow 這篇,他使用了 Repository 這一層來跟 BroadcastReceiver 的資料做互動,將 BroadcastReceiverLiveData加入至 RepositoryMediatorLiveData中,並將 RepositoryLiveData 與 ViewModel 的 LiveData 綁定,如此一來,只要 BroadcastReceiver更新,Repository 就會更新,ViewModel 也跟著更新。

不過看到下方的留言有人提到,他在 View 將 BroadcastReceiverLiveData加入至 RepositoryMediatorLiveData是違反 Clean Architecture 的,因為 View 不應該看得到 Repository 所以我改成直接在 BroadcastReceiver 中呼叫 Repositoryupdate 函式,這樣子就不會在 Fragment 使用到 Repository 的函式了。

要讓程式好維護,關注點分離是不可少的,要讓程式碼更強健,使用 Clean Architecture 也是必須的。

如果對本篇內容有任何意見,歡迎與我討論,謝謝。

歡迎按讚鼓勵,你的每一個讚,就是支持我寫更多文章的動力。

--

--

Android/Flutter developer, like to learn and share.

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Andy Lu

Andy Lu

Android/Flutter developer, like to learn and share.