본문 바로가기

문제 해결

AAC ViewModel 생성자 파라미터 넘기기, Cannot create an instance of class ViewModel 해결 방법

문제 발생

Caused by: java.lang.RuntimeException: Cannot create an instance of class ...ViewModel

AAC Lifecycle ViewModel에서 context를 사용하기 위해,  context를 사용할 수 있는 AndroidViewModel을 확장하고 빌드하였더니 위와 같은 에러가 발생하였습니다.

에러 내용을 보면 알 수 있듯이, ViewModel 인스턴스를 생성하지 못하여 에러가 발생했습니다.

 

저의 에러코드는 아래와 같았습니다.

 

/** View Layer */
class MyActivity : AppCompatActivity() {
    ...
    private val viewModel by lazy {
        ViewModelProvider(this).get(MyViewModel::class.java)
    }
    ...
}

/** ViewModel Layer */
class MyViewModel(application: Application) : AndroidViewModel(application) { }

 

먼저 결론부터 말씀드리면, ViewModelProvider()에 파라미터로 Factory 객체를 넘겨주지 않아서, ViewModel 이 제대로 인스턴스화 되지 못했던 이유였습니다. 

 

그래서, 뭐가 문제인지 파악을 해보았습니다!

 

MyViewModel 클래스의 생성자에서 application을 파라미터로 받게끔 하였으니, 분명 ViewModelProvider(this).get(MyViewModel::class.java) 에서 어떤 식으로든 application을 파라미터로 넘겨줘야겠다 싶어, ViewModelProvider의 오버로딩 파라미터를 확인해보았습니다.

 

/**
 * Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
 * {@code Factory} and retain them in a store of the given {@code ViewModelStoreOwner}.
 *
 * @param owner   a {@code ViewModelStoreOwner} whose {@link ViewModelStore} will be used to
 *                retain {@code ViewModels}
 * @param factory a {@code Factory} which will be used to instantiate
 *                new {@code ViewModels}
 */
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
    this(owner.getViewModelStore(), factory);
}

 

위의 파라미터 설명에서 알 수 있듯이, ViewModel을 인스턴스화 하는데에 Factory 객체가 사용되는 것을 확인하였습니다.

그렇다면, Factory객체는 어떻게 생겼는지 확인해보겠습니다!

 

/**
 * Implementations of {@code Factory} interface are responsible to instantiate ViewModels.
 */
public interface Factory {
    /**
     * Creates a new instance of the given {@code Class}.
     * <p>
     *
     * @param modelClass a {@code Class} whose instance is requested
     * @param <T>        The type parameter for the ViewModel.
     * @return a newly created ViewModel
     */
     @NonNull
     <T extends ViewModel> T create(@NonNull Class<T> modelClass);
}

static class OnRequeryFactory {
    void onRequery(@NonNull ViewModel viewModel) {
    }
}

 

아! Factory객체에서 우리가 원하는 파라미터를 전달하면, 새로 생성된 ViewModel 인스턴스를 반환해주는 팩토리 패턴으로 구성이 되어 있었습니다.

 

그렇다면, 우리는 ViewModel에 파라미터를 넘기기 위해서, 파라미터를 포함한 Factory 객체를 생성하여 ViewModelProvider에 같이 넘겨주게 되면, ViewModel에서 우리가 전달한 파라미터를 받을 수 있음을 파악하였습니다.

 

그럼, 우리가 파악한 대로 Factory 객체를 만들어서 제가 원하는 application을 넘겨보도록 하겠습니다.

문제 해결

먼저, ViewModelProvider.Factory를 확장한 Factory 객체를 정의하도록 하겠습니다.

 

/** ViewModel Layer */
class MyViewModel(application: Application) : AndroidViewModel(application) {
    ...
    class Factory(val application: Application) : ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            return MyViewModel(application) as T
        }
    }
}

 

저는 좀 더 효과적으로 사용하기 위해, Factory 객체 생성 코드를 ViewModel 클래스 안에 정의하였습니다.

이렇게 정의한 Factory 객체를 이제 View Layer에서 ViewModelProvider에 파라미터로 전달하도록 하겠습니다.

 

/** View Layer */
class MyActivity : AppCompatActivity() {
    ...
    private val viewModel by lazy {
        ViewModelProvider(this, MyViewModel.Factory(application)).get(MyViewModel::class.java)
    }
    ...
}

 

위와 같이, ViewModelProvider 메소드에 파라미터로 Factory 객체를 같이 전달하게 되면, 우리가 원하는 대로 ViewModel 생성자에서 전달된 파라미터를 수신받아 사용할 수 있게 됩니다. ^^

최종 정리

/** View Layer */
class MyActivity : AppCompatActivity() {
    ...
    private val viewModel by lazy {
        ViewModelProvider(this, MyViewModel.Factory(application)).get(MyViewModel::class.java)
    }
    ...
}

/** ViewModel Layer */
class MyViewModel(application: Application) : AndroidViewModel(application) {
    ...
    class Factory(val application: Application) : ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            return MyViewModel(application) as T
        }
    }
}