Eldar Kurbanov
Eldar Kurbanov

Reputation: 71

java.lang.InstantiationException <com.example.MyViewModel> has no zero argument constructor when use SavedStateViewModelFactory

I'm just try to follow this guide to look at MVVM architecture: https://developer.android.com/jetpack/guide

My problem: When I use two arguments in my ViewModel class "SavedStateHangle" and "UserRepository", then my app crashes with this error:

E/AndroidRuntime: at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1955) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7058) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:965) Caused by: java.lang.InstantiationException: java.lang.Class<ru.itschool.jetpackguide.UserProfileViewModel> has no zero argument constructor at java.lang.Class.newInstance(Native Method) at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:219)

If I remove UserRepository from constructor parameter (and constructor has just SavedStateHandle), then fragment works (without UserRepository, but it just not crash with this error).

I use my ViewModel with SavedStateViewModelFactory and Hilt + Dagger library like in Android guide to application architecture.

I'm create my ViewModel by this code on UserProfileFragment:

private val viewModel : UserProfileViewModel
    by viewModels(
        factoryProducer = {SavedStateViewModelFactory(activity?.application, this, defaultBundle())}
)

and my ViewModel code:

@HiltViewModel
class UserProfileViewModel @Inject constructor(
    savedStateHandle: SavedStateHandle,
    userRepository: UserRepository
) : ViewModel() {
    var userId: String = savedStateHandle["uid"]
            ?: throw IllegalArgumentException("Missing user ID")
    private val _user = MutableLiveData<User>()
    val user : LiveData<User> = _user

    init {
        viewModelScope.launch {
            _user.value = userRepository.getUser(userId)
        }
    }
}

You can find full code of my project here. Maybe someone implemented this guide before me and already knows the answer? :)

Upvotes: 1

Views: 2280

Answers (2)

Kochez
Kochez

Reputation: 757

Though this answer does not exactly apply to @Eldar Kurbanov's case? Any of you might have simply forgotten the @HiltViewModel annotation above your viewmodel.

Upvotes: 1

Henry Twist
Henry Twist

Reputation: 5980

When you provide a factory to a ViewModel, you are giving viewModels a way to create an instance of your ViewModel if it doesn't already exist. The SavedStateViewModelFactory only provides a way to instantiate a ViewModel with a SavedStateHandle and it obviously doesn't know how to provide your custom repository without some work from you.

So to give it the help it needs, you should probably think about creating a custom implementation of ViewModelProvider.Factory, which requires you to implement the create method:

class VMF: ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {}
}

For your example, assuming you wanted the fragment arguments available in the ViewModel, a simplified implementation would be:

class VMF(private val userRepository: UserRepository, private val args: Bundle?): ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {

        if(UserProfileViewModel::class.java.isAssignableFrom(modelClass)) {

            return UserProfileViewModel(userRepository, args)
        }
        ...
    }
}

and then you would use your factory in place of the default one:

private val viewModel : UserProfileViewModel by viewModels {
    VMF(userRepository, arguments)
}

Upvotes: 1

Related Questions