一樣用這張圖當作開場,今天會完成除了 Room 之外的 MVVM 架構

ViewModel 會負責接收 View 的需求,根據需求向 Model 要資料,再把資料傳回 View,並更新 UI
在 Android 中 ViewModel 會為對應的 Activity 和 Fragment 提供資料,同時負責業務邏輯處理
而透過下面這張圖可以看到,因為 ViewModel 和 View 是獨立存在的,因此 View 的生命周期不會影響 ViewModel ,例如螢幕轉向 View 會重新畫,但是 ViewModel 不會重新去要資料

說到 ViewModel 就是必要提到 LiveData ,要理解 LiveData 之前要先知道 Observer PatternObserver Pattern 是一種 Event base 的設計模式,假設 A 元件會依據 B 變數做更新,因此 A 會觀察 B ,透過這樣的模式, A 不需要一直去訪問 B 中的值是多少,而是當 B 中的值被更新時會發 event 通知 A , A 再更新 UI 即可
而 LiveData 就是一個包含可以被 Observe 資料的載體,當 LiveData 內資料被更新時,所有訂閱這個 LiveData 的 Class 或者是 View 都會收到資料更新的通知,並且也可以同時取得被更新的資料
那因為 ViewModel 是處理業務邏輯的地方,因此通常 LiveData 會寫在裏面,當 ViewModel 將資料從 Model 層拿回來時,會放進 LiveData 中,而此時 View 收到 Event 和 Data ,就能夠去更新 UI 上的呈獻
dependencies那 LiveData 是 androidx 中的其中一員,因此我們必須先將他加到 dependencies 中才能夠使用
dependencies {
...
// LiveData
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0"
...
}
那說完 ViewModel 和 LiveData 之間的關係後,我們就可以回頭開始寫 Code 了
那首先先來處理 PirateList 的 LiveData
PirateListViewModel.kt先到 ui/viewmodel 下建立 PirateListViewModel.kt

AndroidViewModel在 Android 中要使用的 ViewModel 分成兩種
兩者之間有一點差別就是 AndroidViewModel 會攜帶 Context ,具體可以參考這個討論串
那這邊我選擇繼承AndroidViewModel ,並宣告 pirateListRepository ,那實作的部份我採用 by lazy 的方式去處理,這樣做的話,會在程式中第 1 次使用到 pirateListRepository 才去實作 PirateListRepository() ,我個人滿喜歡的
class PirateListViewModel(application: Application) :
AndroidViewModel(application) {
private val pirateListRepository: PirateListRepository by lazy { PirateListRepository() }
}
那這邊有幾點需要注意
pirateListLiveData() 的回傳直會是 LiveData 並且裏面存的會是 <List<Pirate>>
liveData 的 Coroutine 並且會讓任務跑在 IO thread 上emitSource 會等到 api 資料回來後才會接著做下去LiveData 的型態class PirateListViewModel(application: Application) :
AndroidViewModel(application) {
private val pirateListRepository: PirateListRepository by lazy { PirateListRepository() }
fun pirateListLiveData(): LiveData<List<Pirate>> =
liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
emitSource(
pirateListRepository.fetchPirateList(
onSuccess = { },
onError = { }
).asLiveData()
)
}
}
那最後的最後當然是要把整個架構完全串起來才能叫作 MVVM ,因此來到 PiratesFragment ,在這邊把行為 Pass 給 ViewModel,在把資料取回來更新 UI
PirateListViewModel首先是把 PirateListViewModel 初始化起來
class PiratesFragment : Fragment() {
val TAG: String = tag.toString()
val pirateListViewModel: PirateListViewModel by lazy {
ViewModelProvider(this).get(PirateListViewModel::class.java)
}
...
}
onViewCreated()在 Android studio 可以用快捷建 ctrl+O ( Linux )建立 override 函式,那這邊我習慣把 LiveData Observe 初始化 寫在 onViewCreated() 裏面,要怎麼處理可以參考 Fragement Lifecycle 的教學
class PiratesFragment : Fragment() {
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setPirateListViewModel() // 初始化 LiveData observe
}
...
}
setPirateListViewModel() 函式那最後就是要去加入 Observe 去觀察我們需要的 LiveData 有沒有變動囉
private fun setPirateListViewModel() {
pirateListViewModel.pirateListLiveData().observe(viewLifecycleOwner, Observer { pirates ->
if (!pirates.isNullOrEmpty()) {
pirate_text_view.text =
pirates.flatMap { (name, _) -> listOf(name) }
.joinToString(prefix = "<", postfix = ">", separator = "•")
}
})
}
這邊可以注意兩個點
observe 是在 Activity 中,則綁定的生命周期會是 this
observe 是在 Activity 中,則綁定的生命周期會是 viewLifecycleOwner
兩者的差異可以參可這篇
另外,這邊資料拿回來後我先簡單的處理,把 name 的部份取出來,組成比較好讀的字串,然後塞在一個 Text View 裏面,但之後會變成 Recycle View 去呈獻
在 Demo 中可以看到在點下 My Pirtes Tab 時,因為 View 剛被建立起來,才剛送 Request 去 Server,資料還沒回來,因此 TextView 中會先呈獻的是 Pirates Fragment,但過了差不多 1 秒,資料回來了,因此 UI 也順勢的被更新
今天已經算是完成基本 MVVM 架構,主體大概都會是這樣在做操作,但還是有很多地方其實是可以被優化的,比如用 dagger 或是 ViewBinding 等等,都會讓事情變得更單純,因此之後還有很長一段要進行喔,加油加油~