MVVM 是將程式的架構分成 ViewModel-View-Model, View 層是負責繪製畫面, ViewModel 是資料與畫面中間的橋樑,在 ViewModel 中會使用 LiveData 存放資料,View 則會 Observe 在 ViewModel 層中的 LiveData,當資料有更新的時候,有在 View 中被 Observe 的 值就會被更新。也就是說, 在View 層中不用考慮值是如何修改的,只要考慮畫面上需要關注哪些值,當這些值更新之後,我們要做什麼樣的動作。最後是 Model ,所有我們的資料來源都是在 Model 這一層中, 一般會使用 Repository 這一層來決定資料來源(遠端資料、本地資料)。
架構圖如下:
問題來了,今天如果我們想要透過 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
,我們必須還是要讓 Fragment
、 Activity
自行去呼叫 registerReceiver
。
思路是這樣的
在 Fragment
中呼叫 registerReceiver
註冊 BroadcastReceiver
,當 BroadcastReceiver
的 onReceive
被呼叫時,更新在 BroadcastReceiver
類別中的一個 MutableLiveData
並且更新 Repository
裏面的 LiveData
,最後因為 ViewModel
的 LiveData 是由 Repository
所提供,這樣就可以完成更新。
最後的流程圖簡化如下:
程式碼如下:
MyRepository.kt
在 MyRepository.kt (Model 層) 的動作如下
- 宣告 MyRepository 為
object
,讓 MyRepository 成為一個靜態類別,原因是我們想要在 BroadcastReceiver 以及 ViewModel 層都使用到相同的 Repository。 - 宣告一個
MutableLiveData
, 是用來儲存可以改變的LiveData
,當我們每次的更新,都會寫入這個屬性中。 - 呈上,將
MutableLiveData
所得到的值,傳入LiveData
中,這麼做的好處是,採用寫入與讀取為分開的兩個屬性讓同一個屬性不會同時做兩件事。 - 提供一個函式
update
用來更新 _result 內的值。
MyReceiver.kt
在 MyReceiver 的動作如下:
- 新增一個類別,並且繼承
BroadcastReceiver
。 - 當 BroadcastReceiver 的值回傳回來時,利用
MyRepository
提供的update
函式更新 Repository 的 內容。
MyViewModel.kt
在 MyViewModel (ViewModel 層) 動作如下:
- 只要將 MyRepository 中的 result 指定在 ViewModel 內的 result。
- 這邊的 result 的型別為 LiveData ,目的是要讓 Fragment 能夠 observe 它。
MyFragment.kt
在 Fragment (View 層) 的動作如下:
- 宣告 ViewModel。
- 宣告 Receiver。
- 在 Fragment 的
onStart()
註冊 BroadcastReceiver。 - 在 Fragment 的
onStop()
取消註冊 BroadcastReceiver。 - 在
onVierCreated
中, observe ViewModel 的 result,當 ViewModel 的 result 有更新時,就會同時更新到這邊。
小結
MVVM 的目的就是關注點分離,在這樣的架構中,我們應該要將畫面歸畫面,商業邏輯歸商業邏輯。在研究之前,我一直想說不要在 Fragment 中呼叫 registerReceiver
,因為這樣子更新資料的動作似乎就在 Fragment 中,違反了關注點分離。所以我原本是想要將 Activity 的 context 傳給 ViewModel ,並在 ViewModel 中去呼叫 registerReceiver
。但是,這樣是不被允許的, ViewModel 不可以持有對 Activity context 引用的參考(Reference)。
後來看到 StackOverflow 這篇,他使用了 Repository
這一層來跟 BroadcastReceiver
的資料做互動,將 BroadcastReceiver
的 LiveData
加入至 Repository
的 MediatorLiveData
中,並將 Repository
的 LiveData
與 ViewModel 的 LiveData
綁定,如此一來,只要 BroadcastReceiver
更新,Repository
就會更新,ViewModel 也跟著更新。
不過看到下方的留言有人提到,他在 View 將 BroadcastReceiver
的 LiveData
加入至 Repository
的MediatorLiveData
是違反 Clean Architecture 的,因為 View 不應該看得到 Repository
所以我改成直接在 BroadcastReceiver
中呼叫 Repository
的 update
函式,這樣子就不會在 Fragment 使用到 Repository
的函式了。
要讓程式好維護,關注點分離是不可少的,要讓程式碼更強健,使用 Clean Architecture 也是必須的。
如果對本篇內容有任何意見,歡迎與我討論,謝謝。
歡迎按讚鼓勵,你的每一個讚,就是支持我寫更多文章的動力。