[Google Course] Android Basics in Kotlin(第9篇) — ViewModel & LiveData

Phoebe Huang
7 min readDec 3, 2022

--

今天要來介紹 Android 專案裡一個很重要也很實用的元件- ViewModel,ViewModel 就是在鼎鼎有名的 MVVM 設計模式中的 VM 容器元件,主要負責的是邏輯層面的程式,而透過ViewModel 元件來執行邏輯層面程式的好處有一個就是ViewModel 能夠與 ViewModelStoreOwner 的生命週期建立關聯,當畫面旋轉或切換時能夠保存UI 狀態、資料以及存取商業邏輯程式

首先我們必須知道在 MAD 的架構裡,將整體APP 架構可以分為三個角色:Data, Domain, UI,每個APP 都至少要有Data 和UI 層:

UI 層負責顯示資料於螢幕上
Data 層包含簡單邏輯以及儲存資料並提供給UI層
Domain 層負責封裝一些較為複雜的商業邏輯

UI 層包括了我們所熟悉的Activity、 Fragment、一些客製化的UI元件及xml,主要是顯示畫面以及接收使用者的觸發事件

Data 層就是我們APP 所需要商業邏輯的值,我們APP 可以同時有許多的資料來源,我們應該為每種目的的商業邏輯提供特定的Data repository (資料倉庫?)來做管理

角色介紹完了就進入我們這篇文章的重點,以前我們都會把Data 也都寫在Activity、Fragment 裏面,現在Data 與 UI 分離了,UI 如何能夠知道 Data 的變化呢?就是使用 Android LiveData 的類別,他是一種可以在給定的lifecycle 被觀察的資料儲存類別

所以 LiveData 需要被觀察其實還需要一個角色 — 擁有lifecycle的物件,我們可以直接把LiveData 建立在Activity,使它依附在Activity的lifecycle,但是這樣就沒有達到UI、Data 分層的效果

因此ViewModel被引進了,ViewModel 可以想像成是一個依附在UI 層的巨大容器,他的lifecycle 是根據UI 元件,而他又可以同時建立/儲存多個LiveData,而ViewModel也適合放置一些較簡易的商業邏輯,透過ViewModel 我們能夠輕鬆的將UI 和Data 做分離

那接下來我們就來做一個小練習,學習ViewModel 和 LiveData 的寫法,文章中使用到的參考內容在這邊列出給大家:
Android Studio 安裝
Google Course 的 Android Basics in Kotlin
Modern Android App Architecture

這邊我們直接用之前的DiceRoller 的專案應用來做MVVM 的練習!!沒看過之前教學文章可以到這邊看一下 DiceRoller所需要功能:[Google Course] Android Basics in Kotlin (第2篇)

首先先開啟一個新專案,在activity_main.xml新增一個button,並將textview 的 ID 命名為 tvRollString,button 的 ID 命名為 btnRoll,待會更新 UI 會使用到

新增一個名稱為 Dice的檔案,我們的骰子有邊以及一個翻轉的功能

object Dice {
var side = 6
fun roll(): Int {
return (1..side).random()
}
}

接下來建立一個名稱為 DiceViewModel 的類別檔案,讓他繼承 android 原生的 ViewModel 類別

class DiceViewModel: ViewModel() {

}

在 DiceViewModel 裡面我們建立一個 private 的 MutableLiveData一個public 的 LiveData,用意有兩個,確保 MutableLiveData 的值只會被 ViewModel 內邏輯修改到,而 UI 層也只能監聽 LiveData

private val _rollString = MutableLiveData<String>()
val rollString: LiveData<String> = _rollString

而透過將_rollString 指定給 rollString 可以達到 修改 MutableLiveData 時進而使 LiveData 的value 被修改=> 觸發監聽者

再來加上 roll() 的事件邏輯程式到 DiceViewModel ,當 roll()被呼叫時,修改_rollString 的值

fun roll() {
when(val num = Dice.roll()) {
1 -> { _rollString.value = "You roll the smallest number $num" }
Dice.side -> { _rollString.value = "You roll the biggest number $num" }
else -> { _rollString.value = "You roll $num"}
}
}

接下來我們到MainActivity 將ViewModel 套進來,建立 Button 事件以及LiveData 觀察者

class MainActivity : AppCompatActivity() {
lateinit var viewModel: DiceViewModel /// DiceViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
/// create ViewModel
viewModel = DiceViewModel()

/// Create button event
val btn = this.findViewById(R.id.btnRoll) as? Button?
btn?.setOnClickListener {
viewModel.roll()
}

/// create roll number observer
viewModel.rollString.observe(this) {
val textView = this.findViewById(R.id.tvRollNumber) as? TextView?
textView?.text = it
}
}
}

透過這樣的架構,我們能夠減少UI 層的邏輯程式,將簡易邏輯一致ViewModel 執行,UI 層只需要處理使用者事件以及data 修改後更新至UI
我們在維護也能夠確認當我要修改邏輯就到ViewModel,相當清楚!!

喜歡我的文章的人也記得幫我按個拍手、分享,覺得很不錯的可以幫我拍個50下!
也要快點追蹤我的 FB粉絲專頁 — 飛比尋常的程式設計世界 ,不會太頻繁出現在你的塗鴉牆騷擾你,好文章生產需要一點時間,有錯誤或想討論的都歡迎留言給我唷!那就下次見拉!

--

--

Phoebe Huang
Phoebe Huang

Written by Phoebe Huang

A software engineer from Taiwan, use free time to learn more about computer science.

No responses yet