정상에서 IT를 외치다

[Android] 한눈에 보는 ViewModel 초기화 방법 A to Z (feat Koin, Hilt) 본문

안드로이드

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

Black-Jin 2021. 7. 1. 15:34
반응형

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

 

이전 시간에는 안드로이드에서 제공해주는 기본 기능을 사용해 ViewModel을 초기화 하는 방법을 살펴보았습니다. 이번 시간에는 DI 라이브러리인 KoinHilt을 사용한 뷰모델 초기화 방법에 대해 정리해 보겠습니다.

 

여기서는 각 라이브러리의 기본 사용법은 제외하고 오직 ViewModel을 초기화하는 방법에 대해서만 정리했습니다.

 

정리 순서는 다음과 같습니다.

 

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

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

3. SavedStateHandle을 사용하는 경우

 

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

 

Koin


0. 모듈 생성

val appModule = module {

    viewModel { CountViewModel() }

    viewModel { CountViewModelWithParam(it[0]) }

    viewModel { CountSavedStateViewModel(it[0]) }

    viewModel { CountSavedStateViewModelWithParam(it[0], it[1]) }
}

Koin을 사용하기 위해서는 모듈에 ViewModel을 등록해주어야 합니다. 또한 it[n] 으로 표현된 부분은 런타임 중에 외부로 부터 직접 파라미터를 주입하겠다는 의미입니다. 

 

 

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 mainViewModel3 by viewModel<CountViewModel>()

Activity와 Fragment 모두 by viewModel 키워드를 사용해 초기화합니다. 참고로 Android KTX에서는 by viewModels를 사용해 초기화 해주었는데요. 뒤에 s가 붙었는가 안붙었는가의 차이가 있습니다. 

 

 

1-2 SharedViewModel 초기화

//Fragment 에서 실행
private val sharedCountViewModel3 by sharedViewModel<CountViewModel>()

Koin에서는 by sharedViewModel 키워드를 사용해 초기화합니다. 

 

 

2. 생성자에 파라미터가 있는 경우 (런타임시 주입)

예제로 사용할 ViewModel

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

    private var count = 0

    fun plusCount() {
        ++count
    }

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

 

 

Activity, Fragment 초기화

private val mainViewModelWithParam3 by viewModel<CountViewModelWithParam> {
        parametersOf("name")
    }

Activity와 Fragment 모두 by viewModel 키워드를 사용해 초기화합니다. 여기서 외부로 부터 필요한 파라미터가 있는 경우 parametersOf()를 사용해서 데이터를 주입해주면 됩니다. 

 

 

3. SavedStateHandle을 사용하는 경우

3-1 생성자에 파라미터가 없는 경우 (SavedStateHandle 하나만을 파라미터로 가진 경우)

예제로 사용할 ViewModel

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

    //..
}

 

Activity, Fragment 초기화

 private val savedStateViewModel3 by stateViewModel<CountSavedStateViewModel>()

by stateViewModel 키워드를 사용해 초기화해줄 수 있습니다. 만일 위 방식이 아닌 parametersOf()를 사용해 직접 SavedStateHandle을 넘겨주고 싶은 경우 아래와 같이 작업할 수 있습니다.

 

private val savedStateViewModel3_1 by viewModel<CountSavedStateViewModel> {
        val savedStateHandle = SavedStateViewModelFactory(application, this, null)
            .create(SavedStateViewModel::class.java).savedStateHandle

        parametersOf(savedStateHandle)
    }

internal class SavedStateViewModel(val savedStateHandle: SavedStateHandle) : ViewModel()

SavedStateViewModelFactory 생성해서 SavedStateHandle를 가져와 파라미터로 넘기는 방법입니다. 이 경우 두개의 뷰모델이 만들어지게 되지만 동작은 동일하게 이뤄집니다.

 

 

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

예제로 사용할 ViewModel

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

 

Activity, Fragment 초기화

 private val savedStateViewModelWithParam3 by stateViewModel<CountSavedStateViewModelWithParam> {
        parametersOf("name")
    }

by stateViewModel 키워드를 사용할 뿐 2-1의 경우처럼 parametersOf()를 사용해서 원하는 값을 넣어주면 됩니다.

 

 

Hilt


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

예제로 사용할 ViewModel

class HiltViewModel @ViewModelInject constructor() : ViewModel() {

    private var count = 0

    fun plusCount() {
        ++count
    }

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

Hilt의 경우 @ViewModelInject 어노테이션을 사용해 ViewModel을 초기화해 줄 수 있습니다.

 

Activity, Fragment 초기화

  private val mainViewModel4 by viewModels<HiltViewModel>()

Hilt는 ViewModel에 어노테이션을 추가해줄 뿐 안드로이드에서 기본으로 제공해주는 방식과 동일하게 초기화 할 수 있습니다. 위는 Android KTX를 사용해서 초기화한 방법입니다.

 

 

SharedViewModel 초기화

private val sharedCountViewModel4 by activityViewModels<HiltViewModel>()

위는 Android KTX를 사용해서 SharedViewModel 초기화한 방법으로 동일하게 동작합니다.

 

 

2. SavedStateHandle을 사용하는 경우

예제로 사용할 ViewModel

class HiltSavedStateViewModel @ViewModelInject constructor(
    @Assisted private val handle: SavedStateHandle,
) : ViewModel() {
   	//..
}

SavedStateHandle 같은 경우에는 @Assisted 어노테이션을 통해 주입받을 수 있습니다.

 


Activity, Fragment 초기화

private val savedStateViewModel4 by viewModels<HiltSavedStateViewModel>()

Hilt는 ViewModel에 어노테이션을 추가해줄 뿐 안드로이드에서 기본으로 제공해주는 방식과 동일하게 초기화 할 수 있습니다. 위는 Android KTX를 사용해서 초기화한 방법입니다.

 

 

3. 생성자에 파라미터가 있는 경우 (런타임시 주입)

런타임에서 Hilt를 사용해 파라미터를 주입하는 일은 보통의 방법으로는 안됩니다. 이는 Hilt 2.31 이상 버전에서 AssistedInject를 사용해야합니다. 이에 대한 자세한 설명은 아래 링크를 통해 확인해주세요. 

 

What's new in Hilt and Dagger 2.31

 

 

정리


ViewModel의 생성자에 파라미터가 필요할 경우 ViewModelProvider.Factory, SavedStateViewModelFactory, AbstractSavedStateViewModelFactory 등을 생성해서 초기화 해주어야 했습니다. 매번 팩토리를 생성해야 되고 필요한 파라미터를 일일히 관리해주어야 하는 번거로운 일을 Koin과 Hilt가 위와 같은 방법으로 해결해 줄 수 있습니다.

 

Koin과 Hilt 모두 편리하게 사용할 수 있지만 Koin의 경우가 런타임시 데이터를 유동적으로 주입해 주기가 더욱 편리한 점이 있는 것 같습니다. 하지만 Koin은 런타임시에 객체가 생성되기 때문에 의존관계가 잘못되었을 경우 문제를 사전에 찾기 힘듭니다. 이에 비해 Hilt는 컴파일 타임에서 의존관계를 모두 확인하기 때문에 문제를 사전에 찾아 해결할 수 있습니다. 하지만 런타임시에 데이터를 주입하기 위해서는 AssistedInject를 사용해야되는 약간의 번거로움이 남아 있습니다. 두 라이브러리 모두 장단점이 있다고 생각합니다. 

 

나중에는 Koin과 Hilt 둘 중 결국 하나의 라이브러리가 우세해질까 궁금하네요?

 


 

반응형
Comments