I am trying to write a sample app using Android architecture components and but even after trying for days I could not get it to work. It gives me the above exception.
I got this after migrating to AndroidX.
There's a bug in androidx.lifecycle:lifecycle-viewmodel:2.0.0-beta01
where Proguard removes the constructor.
https://issuetracker.google.com/issues/112230489
Fix by upgrading to 2.0.0, and remember to update your proguard rules if needed.
My error message looked like this:
java.lang.RuntimeException: Cannot create an instance of class my.custom.viewmodel.CustomViewModel
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:202)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:135)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:103)
......
Caused by: java.lang.NoSuchMethodException: <init> [class android.app.Application]
at java.lang.Class.getConstructor0(Class.java:2204)
at java.lang.Class.getConstructor(Class.java:1683)
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:200)
... 34 more
androidx.test.espresso.PerformException: Error performing 'single click - At Coordinates: 539, 1167 and precision: 16, 16' on view 'with id: my.test:id/button_return_to_main_menu'.
at androidx.test.espresso.PerformException$Builder.build(PerformException.java:82)
at androidx.test.espresso.base.DefaultFailureHandler.getUserFriendlyError(DefaultFailureHandler.java:79)
.....
Caused by: java.lang.RuntimeException: Unable to start activity ComponentInfo{my.custom.domain.MainActivity}: java.lang.RuntimeException: Cannot create an instance of class my.custom.viewmodel.CustomViewModel
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2646)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2707)
In my case I needed to use a ListItemViewModelFactory to pass in a parameter to my view model.
It was not completely obvious to me, but when getting this error I resolved it by creating a public constructor. My constructor was derived from the Android Developer examples and contained the Repository as a parameter. Creating an additional constructor that was empty with no params and having it public solved the issue.
i.e., in your case
public PostViewModel() {}
if you are using Hilt
, ensure your activity/fragment is having @AndroidEntryPoint
annotation
In my case the reason was that I was trying to get shared instance of the ViewModel in my fragment to soon - before the activity was created. That happens when application is restoring its state after being killed.
Preconditions:
Code in activity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//factory is constructed using Dagger
val factory = App.get().components().appComponent.getMapViewModelFactory()
//activity creates the instance of MapViewModel
viewModel = ViewModelProviders.of(this, factory)[MapViewModel::class.java]
}
Code in fragment:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//fragment receives the instance of MapViewModel
viewModel = ViewModelProviders.of(activity!!)[MapViewModel::class.java]
...
}
When I open the app for the first time, everything works fine: activity creates instance of ViewModel; I open Fragment, which get the instance of ViewModel. But when when application is trying to restore its state after being killed, first it calls the body of onCreate of the Fragment and then the body of onCreate of the Activity. At that point fragment can't get the ViewModel as Activity had not created it yet.
Solution 1: Move the code when fragment get the ViewModel from onCreate to onViewCreated. Which is fine as I observe all liveDatas in onViewCreated as well.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = activity?.run { ViewModelProviders.of(this)[MapViewModel::class.java] } ?: throw Exception("Invalid Activity")
viewModel.getSurveyDateLiveData().observe(viewLifecycleOwner, Observer<String> { dateTextView.text = it })
...
}
Solution 2: Create the instance of ViewModel in Activity.onCreate before super.onCreate is called. In this case you can get the ViewModel in your fragment's onCreate.
override fun onCreate(savedInstanceState: Bundle?) {
val factory = App.get().components().appComponent.getMapViewModelFactory()
viewModel = ViewModelProviders.of(this, factory)[MapViewModel::class.java]
super.onCreate(savedInstanceState)
Timber.d("cc: onCreate: $this ")
}
Solution 3:
If you are injecting repository instance in your ViewModel, Check that you are not using @Inject constructor(...):ViewModel()
to inject your repository, but rather **@ViewModelInject constructor(...):ViewModel()**