본문 바로가기
개발 시행착오 정리

[안드로이드] 코틀린 ViewModel + LiveData 이벤트 발생 이슈 해결

by 문톰 2022. 4. 27.

LiveData 사용시 발생했던 이슈에 대해서 정리했습니다.

 

 

 

LoginViewModel.kt 

LiveData의 PostValue에 값을 응답값을 받아서 지역선택 프래그먼트에 Event 전달

ChoiceAddressFrag.kt(지역선택 프레그먼트)

위의 코드를 보시면 지역선택 프래그먼트에서 ViewModel의 LiveData를 구독 후 관찰하는 값의 변경이 생기면 옵저버가 콜백메서드를 실행시켜서 로그인 화면으로 이동하는 코드를 작성했습니다. 

 

위의 Gif파일을 보시면 처음에는 지역선택 프래그먼트에서 가입완료 버튼을 누르면 로그인화면으로 이동하지만 

그 이후에는 이메일인증 프래그먼트에서 다음 버튼을 눌렀을 때 바로 로그인화면으로 이동하는 것을 확인할 수

있습니다.

 

 

원인

ChoiceAddressFrag.kt(지역선택 프레그먼트)

원인은 이메일인증 프래그먼트에서 지역선택 프래그먼트로 이동하자마자 이전에 구독했던 ViewModel의 LiveData에서 옵저버가 이벤트를 발생시켜 등록된 콜백 메서드가 실행되어 로그인 화면으로 이동했기 때문입니다. 

 

 

 

 

그러면 왜 LiveData에서 관찰하는 값이 바뀐것이 아닌데 이벤트가 실행되었는지 확인해봅시다.

 

ViewModel의 라이프사이클

ViewModel LifeCycle의 특징

-위의 그림처럼 AAC의 ViewModel은 Activity의 UI와 관련된 데이터를 보관하기 위해 설계되었습니다.

그러므로 Activity가 화면회전에 의해 onDestory되어도 ViewModel에 있는 데이터는 소멸하지 않습니다.

-ViewModel에 있는 LiveData는 위와 같은 ViewModel의 LifeCycle에 의해 소멸되지 않고 InActive(비활성화) 상태가

됩니다

 

LiveData의 InActive(비활성화) -> Active(활성화)

-일반적으로 LiveData는 관찰하는 값이 변경되었을 때에만 이벤트를 발생시켜 등록된 콜백메서드를 실행시키지만 

예외적으로 LiveData가 InActive -> Active 상태로 변경될 때도 콜백 메서드를 실행시킵니다.

 

-그러므로 다시 지역선택 프래그먼트로 이동 시 비활성화 되었던 LiveData가 Active상태로 변경되면서 

예외적으로 콜백메서드를 실행시키게 된 것 입니다.

 

 

 

EventWrapper를 사용한 해결방법

 

Event.kt

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
}

LoginViewModel.kt 

//쓰기, 읽기, 수정 가능
private val _LiveRegister = MutableLiveData<Event<User>>()

//읽기만 가능
val liveRegister: LiveData<Event<User>>
    get() = _LiveRegister
fun requestRegisterUser(user: User) {
    //최종적인 API 통신 응답값을 LiveData에 입력
    //-Invoke fun 사용
    registerUseCase(user, viewModelScope) {
        _LiveRegister.postValue(Event(it))
    }
}

 

ChoiceAddressFrag.kt(지역선택 프레그먼트)

private fun subscribeToLiveData() {
    //로그인 화면으로 이동
    viewModel.liveRegister.observe(viewLifecycleOwner) { event ->
        event.getContentIfNotHandled()?.let {
            val action =
                ChoiceAddressFragDirections.actionChoiceAddressToLogin()
            findNavController().navigate(action)
        }
    }

-위의 코드처럼 Event클래스를 하나 만들어서 LiveData에 Event클래스를 한번 더 넣은 후에 

-프래그먼트에서 Event를 받을 때는 event.getContentIfNotHandled()메서드를 사용해서 콜백메서드를 실행시키면 된다.

 

 

참고자료

https://seosh817.tistory.com/9

 

[Android] 안드로이드 SignleLiveEvent 와 Event Wrapper 클래스 (AAC ViewModel, LiveData 이슈)

지인에게 왜 화면 회전을 시키면 한번 띄워졌던 토스트메시지 혹은 Dialog가 왜 다시 띄워지는지 모르겠다는 질문을 받았습니다. 그 지인의 ViewModel 부분 코드입니다. 액티비티에서는 아래의 라이

seosh817.tistory.com

https://woochan-dev.tistory.com/86

 

Event용 LiveData 적용하기

MVVM 아키텍에서 LiveData를 쓰다보면 흔하게 마주할 수 있는 상황이 바로 Event 일회성 처리에 대한 문제이다. private val _eventStartSettingActivity = MutableLiveData () val eventStartSettingActivity: L..

woochan-dev.tistory.com

https://jaeryo2357.tistory.com/94

 

[Android] LiveData의 Data를 한번만 관찰

안녕하세요. 점냥입니다:) 이번 포스팅 주제로는 SingleLiveData 입니다. Android AAC LiveData의 사용법 중 하나로 LiveData를 아직 잘 모르신다면 링크를 먼저 읽어와 주세요! LiveData의 변경된 Data를 오직 한

jaeryo2357.tistory.com

https://medium.com/prnd/mvvm%EC%9D%98-viewmodel%EC%97%90%EC%84%9C-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EB%A5%BC-%EC%B2%98%EB%A6%AC%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-6%EA%B0%80%EC%A7%80-31bb183a88ce  

(깊이 있게 잘 설명되어 있음)

 

MVVM의 ViewModel에서 이벤트를 처리하는 방법 6가지

지금 개발하시는 코드에서 ViewModel의 이벤트 처리를 어떻게 하고 계신가요? 헤이딜러에서 LiveData -> SingleLiveData -> SharedFlow -> EventFlow로 이벤트 처리 방법을 변화 하기까지 과정을 소개합니다…

medium.com

 

댓글