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

AndroidViewModel 會為對應的 ActivityFragment 提供資料,同時負責業務邏輯處理

而透過下面這張圖可以看到,因為 ViewModelView 是獨立存在的,因此 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 內資料被更新時,所有訂閱這個 LiveDataClass 或者是 View 都會收到資料更新的通知,並且也可以同時取得被更新的資料

LiveData 與 ViewModel 的關聯

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

將 LiveData 加入 dependencies

LiveDataandroidx 中的其中一員,因此我們必須先將他加到 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 內容

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

那首先先來處理 PirateListLiveData

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>>
  • 採用了 liveDataCoroutine 並且會讓任務跑在 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寫APP30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言