---
# System prepended metadata

title: (Android) MVVM架構如何從viewModel發送event給view

---

# (Android) MVVM架構如何從viewModel發送event給view

> 方法1. 單純使用LiveData (不好)
> 問題1. View生命週期重置時,會收到之前的event
```kotlin=
class DemoViewModel {

    val event = MutableLiveData<String>()

    fun sendEvent(){

        event.postValue("new event post")
    }
}

class DemoFragment : Fragment() {

    private val viewModel = DemoViewModel()

    fun main(){

        viewModel.event.observe(viewLifecycleOwner, Observer {

            println("received event : $it")
        })
    }
}
```


---

> 方法2. 用變數判斷有無處理過(還是不好)
> 問題1. 忘記重置handled變數
> 問題2. View不應該處理邏輯
```kotlin=
class DemoViewModel {

    var handled = false

    val event = MutableLiveData<String>()
    
    //...
}

class DemoFragment : Fragment() {

    private val viewModel = DemoViewModel()

    fun main(){

        viewModel.event.observe(viewLifecycleOwner, Observer {

            if(!viewModel.handled)
                println("received event : $it")

            viewModel.handled = true
        })
    }
}
```


---

> 方法3. 使用SingleLiveEvent(尚可)
> 使用Hack作法讓LiveData只發送一次更新
> Google已放棄此種做法

https://github.com/android/architecture-samples/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SingleLiveEvent.java


---

> 方法4. 封裝Event(Google推薦)
> 對Event取值時一併處理Handled狀態
```kotlin
/**
 * Used as a wrapper for data that is exposed via a LiveData that represents an event.
 */
open class Event<out T>(private val content: T) {

    var hasBeenHandled = false
        private set // Allow external read but not write

    /**
     * Returns the content and prevents its use again.
     */
    fun getContentIfNotHandled(): T? {
        return if (hasBeenHandled) {
            null
        } else {
            hasBeenHandled = true
            content
        }
    }

    /**
     * Returns the content, even if it's already been handled.
     */
    fun peekContent(): T = content
}

```

```kotlin=
class DemoViewModel {
    
    val event = MutableLiveData<Event<String>>()
    
    //...
}

class DemoFragment : Fragment() {

    private val viewModel = DemoViewModel()

    fun main(){

        viewModel.event.observe(viewLifecycleOwner, Observer {

            it.getContentIfNotHandled()?.let { data ->
                println("received event : $data")
            }
        })
    }
}
```
仔細看,View還是處理了邏輯
View在判斷getContentIfNotHandled()是否為空值,只是換了個寫法

正當我心想Google終究還是違反了原則
發現Google還搭配了一個EventObserver
```kotlin=
/**
 * An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has
 * already been handled.
 *
 * [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled.
 */
class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T>> {
    override fun onChanged(event: Event<T>?) {
        event?.getContentIfNotHandled()?.let { value ->
            onEventUnhandledContent(value)
        }
    }
}
```
改用EventObserver去觀察Event的話,View的邏輯就被抽離了

最後View程式碼的如下
```kotlin=
class DemoFragment : Fragment() {

    private val viewModel = DemoViewModel()

    fun main(){

        viewModel.event.observe(viewLifecycleOwner, EventObserver {
            println("received event : $it")
        })
    }
}
```

