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

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

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

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

단순하게 예외 처리

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

데이터 값을 처리한 후에 바로바로 비워주는 방식으로 코딩 해보았습니다. 물론 이렇게 코딩하면 의도한 대로 일회성으로 이벤트를 처리하고, LiveData가 가지는 데이터 값을 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 *