Issue
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? :)
Solution
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)
}
Answered By - Henry Twist
Answer Checked By - Marilyn (JavaFixing Volunteer)