iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 19
0
Mobile Development

30天,從0開始用Kotlin寫APP系列 第 19 篇

Day 19 | Kotlin 完成基礎 MVVM 架構

  • 分享至 

  • xImage
  •  

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

View & ViewModel

ViewModel 會負責接收 View 的需求,根據需求向 Model 要資料,再把資料傳回 View,並更新 UI

ViewModel

在 Android 中 ViewModel 會為對應的 Activity 和 Fragment 提供資料,同時負責業務邏輯處理

而透過下面這張圖可以看到,因為 ViewModel 和 View 是獨立存在的,因此 View 的生命周期不會影響 ViewModel ,例如螢幕轉向 View 會重新畫,但是 ViewModel 不會重新去要資料

LiveData

說到 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 都會收到資料更新的通知,並且也可以同時取得被更新的資料

LiveData 與 ViewModel 的關聯

那因為 ViewModel 是處理業務邏輯的地方,因此通常 LiveData 會寫在裏面,當 ViewModel 將資料從 Model 層拿回來時,會放進 LiveData 中,而此時 View 收到 Event 和 Data ,就能夠去更新 UI 上的呈獻

將 LiveData 加入 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 內容

那說完 ViewModel 和 LiveData 之間的關係後,我們就可以回頭開始寫 Code 了

那首先先來處理 PirateList 的 LiveData

1. 建立 PirateListViewModel.kt

先到 ui/viewmodel 下建立 PirateListViewModel.kt

2. 繼承 AndroidViewModel

在 Android 中要使用的 ViewModel 分成兩種

  • ViewModel
  • AndroidViewModel

兩者之間有一點差別就是 AndroidViewModel 會攜帶 Context ,具體可以參考這個討論串

那這邊我選擇繼承AndroidViewModel ,並宣告 pirateListRepository ,那實作的部份我採用 by lazy 的方式去處理,這樣做的話,會在程式中第 1 次使用到 pirateListRepository 才去實作 PirateListRepository() ,我個人滿喜歡的

class PirateListViewModel(application: Application) : 
    AndroidViewModel(application) {
    
    private val pirateListRepository: PirateListRepository by lazy { PirateListRepository() }
    
}

3. 取得 Pirates 資料並放到 LiveData 中

那這邊有幾點需要注意

  • pirateListLiveData() 的回傳直會是 LiveData 並且裏面存的會是 <List<Pirate>>
  • 採用了 liveData 的 Coroutine 並且會讓任務跑在 IO thread 上
  • emitSource 會等到 api 資料回來後才會接著做下去
  • 在第 11 行把回來的資料轉成 LiveData 的型態
  • 那 onSuccess 和 onError callback 目前還沒有要做事,因此我就先留一個洞在那邊,之後有需要再填上
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()
            )
        }
}

把 View - ViewModel - Model 串起來

那最後的最後當然是要把整個架構完全串起來才能叫作 MVVM ,因此來到 PiratesFragment ,在這邊把行為 Pass 給 ViewModel,在把資料取回來更新 UI

1. 初始化 PirateListViewModel

首先是把 PirateListViewModel 初始化起來

class PiratesFragment : Fragment() {
    val TAG: String = tag.toString()
    val pirateListViewModel: PirateListViewModel by lazy {
        ViewModelProvider(this).get(PirateListViewModel::class.java)
    }
    
    ...
    
}

2. 建立 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
    }
    
    ...
    
}

3. 建立 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 裏面會帶兩個參數,前者是 LiveData 要綁的生命周期,後者則是當觀察到資料有改動時,要做出的反應
  • Activity 和 Fragment 的生命周期是不同的,
    • 如果 observe 是在 Activity 中,則綁定的生命周期會是 this
    • 如果 observe 是在 Activity 中,則綁定的生命周期會是 viewLifecycleOwner

兩者的差異可以參可這篇

另外,這邊資料拿回來後我先簡單的處理,把 name 的部份取出來,組成比較好讀的字串,然後塞在一個 Text View 裏面,但之後會變成 Recycle View 去呈獻

Demo

在 Demo 中可以看到在點下 My Pirtes Tab 時,因為 View 剛被建立起來,才剛送 Request 去 Server,資料還沒回來,因此 TextView 中會先呈獻的是 Pirates Fragment,但過了差不多 1 秒,資料回來了,因此 UI 也順勢的被更新

總結

今天已經算是完成基本 MVVM 架構,主體大概都會是這樣在做操作,但還是有很多地方其實是可以被優化的,比如用 dagger 或是 ViewBinding 等等,都會讓事情變得更單純,因此之後還有很長一段要進行喔,加油加油~
image alt

Reference


上一篇
Day 18 | Kotlin 中處理異步的好伙伴 - Coroutine
下一篇
Day 20 | Kotlin 實作 Material Card View 與動態更換圖片
系列文
30天,從0開始用Kotlin寫APP 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言