一樣用這張圖當作開場,今天會完成除了 Room
之外的 MVVM 架構
ViewModel
會負責接收 View
的需求,根據需求向 Model
要資料,再把資料傳回 View
,並更新 UI
在 Android
中 ViewModel
會為對應的 Activity
和 Fragment
提供資料,同時負責業務邏輯處理
而透過下面這張圖可以看到,因為 ViewModel
和 View
是獨立存在的,因此 View
的生命周期不會影響 ViewModel
,例如螢幕轉向 View
會重新畫,但是 ViewModel
不會重新去要資料
說到 ViewModel
就是必要提到 LiveData
,要理解 LiveData
之前要先知道 Observer Pattern
Observer 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
等等,都會讓事情變得更單純,因此之後還有很長一段要進行喔,加油加油~