본문 바로가기
안드로이드(kotlin)

MVVM에서 viewModel 이벤트를 받을 수 있는 방법-1

by 기계공학 주인장 2023. 3. 26.
반응형

MVVM에서 viewModel 이벤트를 받을 수 있는 방법

  1. LiveData만 사용해서 이벤트 처리하기
  2. LivieData에 EventFlow를 래핑해서 처리하기
  3. SingleLiveData로 이벤트 처리하기
  4. StateFlow, SharedFlow로 이벤트 처리하기
  5. SharedFlow, Sealed class로 이벤트 처리하기
  6. SharedFlow & Sealed class & LifeCycle로 이벤트 처리하기
  7. EventFlow & Sealed class Lifecyle로 이벤트 처리하기

프로젝트 셋업

간단하게 어떻게 프로젝트를 셋업했는지 설명하겠습니다.

 

UI를 다음과 같이 구성하고

 

다음과 같은 동작을 실시하게 했습니다.

  1. 각 버튼을 클릭한다.
  2. 각각의 liveData 또는 flow 변수에 데이터를 삽입한다.
  3. 2번 변수에서 observe를 실시하고 observe가 되었을 때 빈 액티비티를 연다.
  4. 다시 이전 액티비티로 돌아와서 2번 변수에 저장되어 있는 값을 확인한다.

LiveData만 사용해서 데이터 처리하기

ViewModel 구성

class LiveDataViewModel: ViewModel() {
    private val _someData = MutableLiveData<Int>()
    val someData: LiveData<Int> = _someData

    fun setData() {
        if (_someData.value == null) {
            _someData.value = 0
        } else {
            _someData.value = _someData.value!! + 1
        }
    }
}

 

테스트용 Activity 구성

class FirstActivity : AppCompatActivity() {
    private lateinit var binding: ActivityFirstBinding
    private val liveDataViewModel: LiveDataViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityFirstBinding.inflate(layoutInflater)
        setContentView(binding.root)

        lifecycleScope.launch {
            liveDataViewModel.someData.observe(this@FirstActivity) {
                Timber.d("data observed: $it")
                startActivity(Intent(this@FirstActivity, SecondActivity::class.java))
            }
        }

        binding.liveDataButton.setOnClickListener {
            liveDataViewModel.setData()
        }
    }

    override fun onResume() {
        super.onResume()
        Timber.d("liveDataViewModel Data: ${liveDataViewModel.someData.value}")
    }
}

 

테스트 결과

액티비티에서 돌아왔지만 기존의 데이터가 그대로 남아있는 것을 확인할 수 있다.

 

여기서 데이터가  남아있게 하지 않기 위해 EventWrapper를 사용해 본다

LiveData에 EventWrapper를 적용하기

EventWrapper 클래스를 정의하여 화면으로 돌아왔을 때 다시 한번 Observe 되지 않게 하거나

 

값을 초기화하는 방법은 한 때 Google에서도 사용하는 방법이 있다.

 

여기서 언급한 EventWrapper 클래스는 다음과 같은 방법으로 정의할 수 있다.

 

/**
 * 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
}

 

위 EventWrapper를 사용할 ViewModel은 다음과 같이 정의한다.

 

class EventLiveDataViewModel: ViewModel() {
	// LiveData에 EventWrapper를 적용한다.
    private val _someData = MutableLiveData<Event<Int>>()
    val someData: LiveData<Event<Int>> = _someData

    fun setData() {
        if (_someData.value?.getContentIfNotHandled() == null) {
            _someData.value = Event(0)
        } else {
            _someData.value = Event(_someData.value?.getContentIfNotHandled() as Int + 1)
        }
    }
}

 

그리고 테스트할 Activity는 다음과 같이 정의한다. (앞으로는 이 부분 생략)

 

class FirstActivity : AppCompatActivity() {
    private lateinit var binding: ActivityFirstBinding
    private val liveDataViewModel: LiveDataViewModel by viewModels()
    private val eventLiveDataViewModel: EventLiveDataViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityFirstBinding.inflate(layoutInflater)
        setContentView(binding.root)

        lifecycleScope.launch {
            liveDataViewModel.someData.observe(this@FirstActivity) {
                Timber.d("data observed: $it")
                startActivity(Intent(this@FirstActivity, SecondActivity::class.java))
            }
        }

        lifecycleScope.launch{
            eventLiveDataViewModel.someData.observe(this@FirstActivity) {
                Timber.d("data observed: ${it.getContentIfNotHandled()}")
                startActivity(Intent(this@FirstActivity, SecondActivity::class.java))
            }
        }

        binding.liveDataButton.setOnClickListener {
            liveDataViewModel.setData()
        }

        binding.liveEventDataButton.setOnClickListener {
            eventLiveDataViewModel.setData()
        }
    }

    override fun onResume() {
        super.onResume()
        Timber.d("liveDataViewModel Data: ${liveDataViewModel.someData.value}")
        Timber.d("eventLiveDataViewModel Data: ${eventLiveDataViewModel.someData.value?.getContentIfNotHandled()}")
    }
}

 

그리고 eventLiveDataViewModel 버튼을 클릭하면 다음과 같은 로그가 출력된다.

 

 

처음 화면을 열었을 때는 null이 출력되고

 

Observe 된 후 다른 Activity로 갔다가 돌아온 후에도 값이 다시 null이 되어있다.

 

이는 EventWrapper로 인해 값을 한 번 Observe 했다면 null을 반환하게 하기 때문이다.

 

getContentIfNotHandled 이 아니라 peekContent을 사용해서 값을 받아온다면 일반적인 LiveData를 사용했을 때와 똑같이 사용할 수 있다.

 


MVVM에서 viewModel 이벤트를 받을 수 있는 방법 요약

  1. LiveData만 사용해서 이벤트 처리하기
  2. LivieData에 EventFlow를 래핑해서 처리하기

 

다음 포스팅에서 SingleLiveData와 SharedFlow에 대해 알아보겠습니다.


 

 

MVVM에서 viewModel 이벤트를 받을 수 있는 방법-2

MVVM에서 viewModel 이벤트를 받을 수 있는 방법 SingleLiveData로 이벤트 처리하기 StateFlow, SharedFlow로 이벤트 처리하기 SharedFlow, Sealed class로 이벤트 처리하기 SharedFlow & Sealed class & LifeCycle로 이벤트 처리

android-developer.tistory.com

 

반응형


"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."


댓글