정상에서 IT를 외치다

[Android] 한눈에 보는 ViewModel 초기화 방법 A to Z 본문

안드로이드

[Android] 한눈에 보는 ViewModel 초기화 방법 A to Z

Black-Jin 2021. 6. 23. 20:57
반응형

 

안녕하세요. 블랙진입니다.

 

이전 포스팅에서는 ViewModel이 무엇인지 그리고 왜 사용하는지에 대해 정리했습니다. 아직 AAC의 ViewModel이 무엇인지 모르신 분은 이전 포스팅 AAC의 ViewModel 사용 방법 정리을 참고해주세요.

 

이번 시간에는 두 포스팅으로 나눠 진행하겠습니다. 첫 번째는 안드로이드에서 제공하는 기본 기능만을 사용한 ViewModel 초기화 방법과 SavedStateHandle 사용법을 정리하겠습니다. 두 번째 포스팅은 대표적인 DI 라이브러리인 Koin과 Hilt를 사용한 ViewModel 초기화 및 사용 방법을 정리하겠습니다.

 

먼저 ViewModel 을 초기화 하는데는 크게 2가지 경우가 있습니다. 

 

1. 생성자에 파라미터가 없는 경우

2. 생성자에 파라미터가 있는 경우

 

여기서 각 경우 별로 아래 2가지 상황으로 나뉩니다.

 

1. Acitvity, Fragment에서 초기화 하는 경우

2. SharedViewModel을 사용하는 경우

 

이렇게 총 4가지 경우와 더불어 추가로 SavedStateHandle을 사용한 초기화 방법까지 정리해 보겠습니다.

 

🚀 SavedStateHandle에 대한 자세한 설명은 Pluu Dev님의 블로그를 참고해주세요. 여기서는 생략하겠습니다.

 

1. 생성자에 파라미터가 없는 경우


우리가 사용할 ViewModel 예제는 다음과 같습니다.

class CountViewModel : ViewModel() {

    private var count = 0

    fun plusCount() {
        ++count
    }

    fun showCountLog() {
        Log.d("MyTag", "count : $count")
    }
}

 

 

1-1 Activity, Fragment 초기화

private val countViewModel1 by lazy {
        ViewModelProvider(this).get(CountViewModel::class.java)
    }

ViewModelProvider를 사용하여 초기화 해줍니다. Activity와 Fragment 모두 동일한 방법으로 초기화 합니다.

 

 

1-2 SharedViewModel 초기화

//Fragment 에서 실행
private val sharedCountViewModel1 by lazy {
        ViewModelProvider(requireActivity()).get(CountViewModel::class.java)
    }

SharedViewModel은 Activity의 ViewModel을 Fragment와 공유하여 사용할 수 있게 해줍니다. 코드에서 보시는 바와 같이ViewModelProvider의 인자로 this (Framgnet에서 사용하는 것이니 여기서 this는 Fragment 이겠죠)가 아닌 activity를 변수로 넣어줍니다. 

 

 

Android KTX 사용

Android KTX는 다양한 코틀린 확장함수들을 모아놓은 라이브러리입니다. 

 

 

1-0 [KTX] 라이브러리 추가

implementation "androidx.fragment:fragment-ktx:1.3.4"

build.gradle에 라이브러리를 추가합니다. fragment-ktx 최신버전은 문서를 확인해 주세요.

 

 

1-1 [KTX] Activity, Fragment 초기화

private val countViewModel2 by viewModels<CountViewModel>()

by viewModels 키워드를 사용하여 간편하게 초기화해 줄 수 있습니다.

 

 

1-2 [KTX] SharedViewModel 초기화

//Fragment 에서 실행
private val sharedCountViewModel2 by activityViewModels<CountViewModel>()

by activityViewModels 키워드를 사용해 초기화 해줍니다. KTX를 사용하니 좀더 직관적인 이름이라고 생각합니다.

 

 

2. 생성자에 파라미터가 있는 경우


우리가 사용할 ViewModel 예제는 다음과 같습니다. name이라는 파라미터를 가지고 있습니다.

class CountViewModelWithParam(
    private val name: String
) : ViewModel() {

    private var count = 0

    fun plusCount() {
        ++count
    }

    fun showCountLog() {
        Log.d("MyTag", "name : $name, count : $count")
    }
}

 

 

2-1 Activity, Fragment 초기화

private val countViewModelWithParam1 by lazy {
        ViewModelProvider(this, object : ViewModelProvider.Factory {
            override fun <T : ViewModel?> create(modelClass: Class<T>): T {
                return CountViewModelWithParam("name") as T
            }
        }).get(CountViewModelWithParam::class.java)
    }

Activity와 Fragment 둘 다 동일하며 ViewModelProvider.Factory를 사용해서 초기화 해줍니다.

 

 

2-2 SharedViewModel 초기화

//Fragment 에서 실행    
private val sharedCountViewModelWithParam1 by lazy {
		ViewModelProvider(requireActivity()).get(CountViewModelWithParam::class.java)
	}

Activity의 ViewModel을 가져오는 것이기 때문에 따로 생성자에 인자를 추가해줄 필요 없이 1-2와 같은 방식으로 초기화합니다.

 

Android KTX 사용

Android KTX는 다양한 코틀린 확장함수들을 모아놓은 라이브러리입니다. 

 

2-1 [KTX] Activity, Fragment 초기화

private val countViewModelWithParam2 by viewModels<CountViewModelWithParam> {
        object : ViewModelProvider.Factory {
            override fun <T : ViewModel?> create(modelClass: Class<T>): T {
                return CountViewModelWithParam("name") as T
            }
        }
    }

by viewModels 키워드의 인자로 Factory 무명 객체를 생성해 전달합니다.

 

 

2-2 [KTX] SharedViewModel 사용

private val sharedCountViewModelWithParam2 by activityViewModels<CountViewModelWithParam>()

Activity의 ViewModel을 가져오는 것이기 때문에 따로 생성자에 인자를 추가해줄 필요 없이 2-2와 같은 방식으로 초기화합니다.

 

 

3. SavedStateHandle 사용하는 경우


ViewModel의 경우 화면 회전구성 변경 (언어 변경 등)에서도 데이터를 유지합니다. 하지만 시스템 프로세스가 죽을 경우 ViewModel 또한 새로 생성되므로 데이터는 유실됩니다. 이 경우 onSaveInstanceState를 통해 데이터를 복구 해야합니다. 이를 해결하기 위해 나온 기능이 Saved State Module 입니다. ViewModel의 생성자에서 SavedStateHandle을 받아 시스템 프로세스가 죽었을 경우에도 데이터를 다시 가져와 사용할 수 있게 해줍니다. 여기서는 SavedStateHandle에 대해 자세히 다루지 않고 ViewModel을 초기화 하는 방법에 대해서만 다루겠습니다.

 

 

3-1. 생성자에 파라미터가 없는 경우

 

우리가 사용할 ViewModel 예제는 다음과 같습니다.

class CountSavedStateViewModel(
    private val handle: SavedStateHandle
) : ViewModel() {

    //..
}

생성자에는 SavedStateHandle만 존재합니다.

 

 

1) Activity, Fragment 초기화

private val savedStateViewModel1 by lazy {
        ViewModelProvider(
            this,
            SavedStateViewModelFactory(application, this, null)
        ).get(CountSavedStateViewModel::class.java)
    }

SavedStateViewModelFactory를 사용해 초기화해줍니다. 

 

 

2) [KTX] Activity, Fragment 초기화

private val savedStateViewModel2 by viewModels<CountSavedStateViewModel> {
        SavedStateViewModelFactory(application, this, null)
    }

SavedStateViewModelFactory를 사용해 초기화해줍니다. 

 

 

 

3-2. 생성자에 파라미터가 있는 경우

 

우리가 사용할 ViewModel 예제는 다음과 같습니다.

class CountSavedStateViewModelWithParam(
    private val handle: SavedStateHandle,
    private val name: String
) : ViewModel() {

    //..
}

생성자에는 SavedStateHandleName이 존재합니다.

 

 

1) Activity, Fragment 초기화

private val savedStateViewModelWithParam1 by lazy {
        ViewModelProvider(this, object : AbstractSavedStateViewModelFactory(this, null) {
            override fun <T : ViewModel?> create(key: String, modelClass: Class<T>, handle: SavedStateHandle): T {
                return CountSavedStateViewModelWithParam(handle, "name") as T
            }
        }).get(CountSavedStateViewModelWithParam::class.java)
    }

AbstractSavedStateViewModelFactory를 사용해 초기화해줍니다. 

 

 

2) [KTX] Activity, Fragment 초기화

private val savedStateViewModelWithParam2 by viewModels<CountSavedStateViewModelWithParam> {
        object : AbstractSavedStateViewModelFactory(this, null) {
            override fun <T : ViewModel?> create(key: String, modelClass: Class<T>, handle: SavedStateHandle): T {
                return CountSavedStateViewModelWithParam(handle, "name") as T
            }
        }
    }

AbstractSavedStateViewModelFactory를 사용해 초기화해줍니다

 

 

정리


안드로이드에서 제공하는 ViewModelProviderKTX를 사용한 ViewModel 초기화 방법에 대해서 총 정리 해보았습니다. KTX를 사용한다 하더라고 ViewModel을 초기화 하는데 많은 보일러플레이트 코드가 보입니다. 

 

매번 이러한 반복적인 코드를 우리는 작성해야 될까요?

 

정답은 No! 입니다. 이러한 보일러플레이트 코드를 줄이기 위한 DI 라이브러리가 있습니다. 안드 진영에서 많이 사용하고 있는 KoinHilt가 대표적인데요. 이러한 반복적인 코드를 없애고 쉽게 ViewModel을 초기화 해주는 기능을 이미 제공하고 있습니다. 다음 포스팅 에서는 Koin과 Hilt의 ViewModel 초기화 방법에 대해서 비교 정리해보았습니다. 궁금하신 분은 아래 링크를 통해 확인해 주세요.

 

👉 한눈에 보는 ViewModel 초기화 방법 A to Z (feat Koin, Hilt) 👈

 

 


 

반응형
Comments