前言

原文在此

我們在寫 Android app 的時候會遇到一些場景會需要運用 LiveData 這個 DataHolder 來幫助我們跟 activity / fragment 做溝通。用起來方便且快速。

但我們有時候會遇到一個問題,某些 data set 已經被消費過了之後,遇到 activity / fragment recreated 後相同的 data set 會被再度 trigger 。

所以我們會使用 SingleLiveEvent 來輔助我們。了解更多 SingleLiveEvent

但有沒有其他解決方式呢?

重點摘要

先確認我們遇到上述情況的需求

  1. 如果有新的 Events 他不能 overwrite 還沒被消費掉的 Event
  2. 如果沒有人消費該 Event 需要有辦法 Hold 住該 Event 直到該 Event 被消費掉。
  3. 能夠配合 lifecycle

此篇作者是用的是 Kotlin Coroutine 中的 Channel 來處理這種情況

class MainViewModel : ViewModel() {

    sealed class Event {
        object NavigateToSettings: Event()
        data class ShowSnackBar(val text: String): Event()
        data class ShowToast(val text: String): Event()
    }

    private val eventChannel = Channel<Event>(Channel.BUFFERED)
    val eventsFlow = eventChannel.receiveAsFlow()

    init {
        viewModelScope.launch {
            eventChannel.send(Event.ShowSnackBar("Sample"))
            eventChannel.send(Event.ShowToast("Toast"))
        }
    }

    fun settingsButtonClicked() {
        viewModelScope.launch {
            eventChannel.send(Event.NavigateToSettings)
        }
    }
}

但如何能夠在 activity / fragment 中配合 lifecycle 來結束這段關係呢?

override fun onStart() {
    super.onStart()
    job = viewModel.eventsFlow
            .onEach {
                when (it) {
                    MainViewModel.Event.NavigateToSettings -> {}
                    is MainViewModel.Event.ShowSnackBar -> {}
                    is MainViewModel.Event.ShowToast -> {}
                }
            }
            .launchIn(viewLifecycleOwner.lifecycleScope)
}

override fun onStop() {
    super.onStop()
    job?.cancel()
}

這裡提供了一個最簡單的作法,透過 cancel job 來達到。在你需要的 lifecycle 中取消。

結語

作為 Android 的開發者,SingleLiveEvent 與此方法都使用過。 個人會比較喜歡這個方式,因為 kotlin coroutine 提供了更多的靈活性。 以上面的例子來說,view model 透過 channel 的 receiveAsFlow 將 channel 轉換成 flow 來與 view 層溝通。 這樣如果之後有其他需求,修改起來可能會更方便一些。

原文中其實還有提到,如果你有許多的 job 要 cancel 的話,可以用什麼方式可以讓你的 code 更簡潔。 但我覺得這部分就屬於錦上添花的部分。如果想要知道的人可以去看原文,有更詳細的介紹。 如果文中有任何錯誤或想要討論的部分,就煩請下方留言囉。