Programming/Android

MVVM에서 ViewModel의 Event 전달하기2(Event Wrapper)

YK Choi 2021. 4. 28. 00:21

지난 포스팅의 외국인 개발자분에게 보낸 메일에 답장은 오지 않았다.

 

 

하지만 답장 받기도 전에 내가 Event Wrapper에 대해 오해하고 있다는 것을 알게 되었다.

 

 

이벤트 발생시 마다 Event객체를 생성하는게 아닐 것이라 생각했지만 그건 큰 오산이었다.

viewModel이 생성된 이후 단 한번만 발생시키는 이벤트에 사용되는 것이 Event Wrapper가 아니라

하나의 이벤트에 대해 liveData를 observing하는 View가 이벤트를 중복으로 처리하지 못하게 하는 것이다. 

한 마디로 Event Wrapper의 용도에 대해 착각하고 있었다.

 

 

이벤트 발생시 마다 Event 객체를 만들어 LiveData에 넣어야한다.

역시 외국인 개발자 아저씨가 맞았다!!

Event Wrapping은 특수한 상황에서만 쓰이는 것이 아니라

LiveData를 통한 이벤트 전달에서 범용적으로 사용될 것 같다.

 

 

아래 영상은 A라는 Activity에서 Event Wrapper를 사용하지 않고

LiveData로 이벤트 전달을 통해 B라는 Activity를 실행시킨 뒤

다시 A로 돌아와서 화면을 회전시켰을 때 발생하는 이슈다. 

 

LiveData의 중복 observing

영상 상황 설명

1. A Activity(첫 화면) 에서 우측 상단의 '+' 버튼을 누른다.

 이때 ViewModel의 writePostEvent의 LiveData에 x 라는 값이 들어간다.

 writePostEvent를 관찰하는 Activity가 변화를 observe하며 B Activity를 부른다

 

2. B Activity에서 뒤로가기를 눌러 다시 A Activity로 돌아온다.

 이때 A Activity의 writePostEvent는 그대로 x 데이터를 가지고 있다.

 

3. 돌아온 A Activity에서 화면 회전을 한다.

 회전으로 인해 A Activity가 재생성되지만 ViewModel은 보존된다.

 Activity는 writePostEvent의 LiveData를 observe한다 -> 자동으로 B Activity를 부른다.

 

4. 한번 더 회전을 한다.(화면 각도 원상 복귀)

 A Activity에서 B Activity를 또 부른다.

(B Activity는 특별한 intent Flag나 launchMode를 설정하지 않은 상태)

 

5. 뒤로가기 버튼을 2번 눌러야 다시 A Activity로 돌아온다.

 

 

 

이제 아래 영상은 정상적으로 Event Wrapper 를 적용해서

동일한 실행을 시킨 모습이다.

 

정상적인 LiveData의 Event 처리

LiveData를 Event Wraper로 감쌌기 때문에 hasBeenHandled: Boolean 변수로

이미 호출한 Event를 구분한 것이다.

 

 

아래와 같이 처음 Event를 호출했을 때에만

writePostEvent before -> 가 false이고

그 이후부터는 true가 출력되는 것을 확인할 수 있다.

 

정상적인 LiveData Event 처리시 Logcat

[로그 설명]

1,2번째 줄 : 처음 '+' 버튼을 눌러 B Activity로 들어갔을 때

4,5번째 줄 : 화면 회전으로 디바이스가 옆으로 누웠을 때

6,7번째 줄 : 디바이스를 다시 회전시켜 세웠을 때

 

 

결론

외국인 개발자 아저씨의 말대로 하면 된다.

 

 

Event.kt

open class Event<out T>(private val content: T) {

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

    fun getContentIfNotHandled(): T? {
        return if (hasBeenHandled) {
            null
        } else {
            hasBeenHandled = true
            content
        }
    }

    fun peekContent(): T = content
}

 

ViewModel.kt의 LiveData 선언

private val _writePostEvent = MutableLiveData<Event<String>>()
val writePostEvent : LiveData<Event<String>>
        get() = _writePostEvent

 

 

Activity.kt의 observe

boardViewModel.writePostEvent.observe(binding.lifecycleOwner!!,{ event ->
        Log.e("event handled tag","writePostEvent before -> ${event.hasBeenHandled}")
        
        event.getContentIfNotHandled()?.let {
            val intent = Intent(this, WriteActivity::class.java)
            intent.putExtra("type",it)
            startActivity(intent)
        }
        Log.e("event handled tag","writePostEvent after -> ${event.hasBeenHandled}")
            
})

 

 

도움된 사이트

vagabond95.me/posts/live-data-with-event-issue/

 

[안드로이드] MVVM 과 LiveData 조합 시 겪을 수 있는 이슈와 해결책 - 기록은 기억을 지배한다

이번 포스트에서는 MVVM 아키텍처에서 LiveData 를 사용하면서 겪었던 어려움과 여러 해결방법에 대해 적어보려한다. MVVM 에 대한 좋은 글은 이미 많이 있으므로 해당 포스트에서는 생략하고 넘어

vagabond95.me