LiveData로 일회성 이벤트 처리하기

안드로이드 애플리케이션을 개발할 때, 아키텍처를 구성하면서 LiveData를 이용하여 일회성 이벤트를 처리해야 하는 경우가 생긴다. 경고문 출력, 액티비티 이동, 혹은 프래그먼트 전환 등. 아마 그런 경우 대개 이런 형태를 취할 것이다.

데이터바인딩 – 뷰모델 – LiveData – Activity or Fragment 에서 옵저빙

그러나 문제가 있다. LiveData는 ViewModel의 데이터를 핸들링하기 위해 사용하는 동시에, 기기의 상태 변화 (디바이스 가로세로 변화, 혹은 액티비티가 메모리 부족 등의 이유로 재실행 되는 경우)에 대응하기 위해 사용한다. 때문에 일회성으로 출력돼야 하는 데이터들이 뷰가 리프레시될 때마다 의도치 않게 작업을 반복 수행 하게 된다. 이 문제를 고려해서 단순하게 얘외를 처리하는 코딩을 하면 이렇게 된다.

단순하게 예외 처리

viewModel.liveData.observe(this, Observer {
     it?.let {
         // 작업 수행
         viewModel.liveData.value = null
     }
})

지금은 아무도 이렇게 쓰는 사람은 없다. 그냥 급하게 해결해야겠다는 생각으로 적었던 코드이다.

물론 이렇게 코딩하면 의도한 대로 일회성으로 이벤트를 처리하고, LiveData가 가지는 데이터 값을 null로 초기화해서, 다음번에는 작업이 발화되지 않도록 할 수 있다. 그러나 데이터가 null 인 것인지, 작업이 이미 발화되어 null이 된 것인지 구별하기가 힘들다.

고민하다가 꽤 괜찮아 보이는 방법을 발견했고 아래 방법은 해당 웹페이지에서 발췌한 방법이다.

Event Wrapper 만들어 사용하기

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
}

Event 클래스는 각 값을 가지면서, 해당 데이터가 사용되었는지 여부를 기억하게 하여, 중복 사용을 막을 수 있도록 한다. 첫 번째 방법으로 제시한 단순하게 예외 처리한 것과 다른 점은, peekContent 함수를 통해 이벤트가 처리 된 이후에도 데이터 값이 필요한 경우 사용 가능하다. 또한 데이터의 출처가 명확하기 때문에 훨씬 안전하다고 생각된다.

사용은 단순히 이렇게 하면 된다.

val messageLiveData = MutableLiveData<Event<String>>()
mssageLiveData.value = "something"
viewModel.messageLiveData.observe(this, Observer {
     it.getContentIfNotHandled()?.let{
         Toast.makeText(requireContext(), it, Toast.LENGTH_SHORT).show()
     }
})

Leave a comment

Your email address will not be published. Required fields are marked *