In the summer of last year I started refactoring my Android application with Android's architecture components (Room, ViewModel, LiveData).
I have two Room repositories, one of them is accessed by multiple views (fragments) of the application. Because of that I used an AndroidViewModel
, which has access to this repository and which is initialized in my MainActivity
.
new ViewModelProvider(this).get(CanteensViewModel.class);
In my two fragments I accessed this ViewModel by
new ViewModelProvider(getActivity()).get(CanteensViewModel.class);
Until yesterday that worked perfectly. But then I updated my dependencies and since androidx.lifecycle
version 2.2.0 this does not work anymore. I always get an exception (siehe EDIT 2):
Caused by: java.lang.InstantiationException: java.lang.Class<com.(...).CanteensViewModel> has no zero argument constructor
So I checked the docs and as I understood right I should/could now use
ViewModelProvider.AndroidViewModelFactory.getInstance(this.getApplication()).create(CanteensViewModel.class);
to get my ViewModel. But with this approach I can't add the owner
(parameter of ViewModelProvider
s constructor), which results in the problem, that I can't really access the ViewModel I created in the Activity from inside my fragments.
Is there a way I can access the Activity's ViewModel from inside the fragments? Or would it be better to recreate the ViewModel in each fragment by
ViewModelProvider.AndroidViewModelFactory.getInstance(getActivity().getApplication()).create(CanteensViewModel.class);
instead of creating it inside the Activity?
EDIT:
It seems to work, when I use the other constructor of ViewModelProvider
, where a AndroidViewModelFactory
is the second parameter.
new ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(this.getApplication())).get(CanteensViewModel.class);
Doing this in my MainActivity
I can access the CanteensViewModel
in my Fragment
via
new ViewModelProvider(requireActivity()).get(CanteensViewModel.class);
EDIT 2 Stacktrace for above mentioned exception:
2020-02-28 14:30:16.098 25279-25279/com.pasta.mensadd E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.pasta.mensadd, PID: 25279
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.pasta.mensadd/com.pasta.mensadd.ui.MainActivity}: java.lang.RuntimeException: Cannot create an instance of class com.pasta.mensadd.ui.viewmodel.CanteensViewModel
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2795)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2873)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1602)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6543)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
Caused by: java.lang.RuntimeException: Cannot create an instance of class com.pasta.mensadd.ui.viewmodel.CanteensViewModel
at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:221)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:187)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
at com.pasta.mensadd.ui.MainActivity.onCreate(MainActivity.java:70)
at android.app.Activity.performCreate(Activity.java:7023)
at android.app.Activity.performCreate(Activity.java:7014)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1215)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2748)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2873)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1602)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6543)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
Caused by: java.lang.InstantiationException: java.lang.Class<com.pasta.mensadd.ui.viewmodel.CanteensViewModel> has no zero argument constructor
at java.lang.Class.newInstance(Native Method)
at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:219)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:187)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
at com.pasta.mensadd.ui.MainActivity.onCreate(MainActivity.java:70)
at android.app.Activity.performCreate(Activity.java:7023)
at android.app.Activity.performCreate(Activity.java:7014)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1215)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2748)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2873)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1602)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6543)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
```
So I checked the docs and as I understood right I should now use
ViewModelProvider.AndroidViewModelFactory.getInstance( this.getApplication()).create(CanteensViewModel.class);
Please share a link to this "docs" you mentioned, because this is NOT the first time I see this code, and yet it was equally wrong in both cases.
The code you actually should be using is
new ViewModelProvider(this).get(CanteensViewModel.class);
Is there a way I can access the Activity's ViewModel from inside the fragments? Or would it be better to recreate the ViewModel in each fragment by
new ViewModelProvider(requireActivity()).get(CanteensViewModel.class);
Consider also receiving a SavedStateHandle
as an argument in your AndroidViewModel
, and not only Application
.
If you ask me, apparently the removal of ViewModelProviders.of()
was an API mistake, but this is what we have now.
EDIT: With the help of the provided stack trace, I can finally somewhat figure out what's going on.
at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:219)
We are using NewInstanceFactory
as the default. What does default NewInstanceFactory
do? It just calls no-arg constructor if available.
Wait, what? Isn't it supposed to fill in the Application
for an AndroidViewModel
?
Theoretically yes, as long as you got the original default ViewModelProvider.Factory
, but this is not the one!
Why is it not the one that can fill in AndroidViewModel?
See this commit
Add default ViewModel Factory interface Use a marker interface to allow instances of ViewModelStoreOwner, such as ComponentActivity and Fragment, to provide a default ViewModelProvider.Factory that can be used with a new, concise ViewModelProvider constructor. This updates ComponentActivity and Fragment to use that new API to provide an AndroidViewModelFactory by default. It updates the 'by viewModels' Kotlin extensions to use this default Factory if one isn't explicitly provided.
Also
ComponentActivity: + @NonNull + @Override + public ViewModelProvider.Factory getDefaultViewModelProviderFactory() { + if (getApplication() == null) { + throw new IllegalStateException("Your activity is not yet attached to the " + + "Application instance. You can't request ViewModel before onCreate call."); + } + return ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication()); + } +
And most importantly
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) { this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory() : NewInstanceFactory.getInstance()); }
This means that you get the default view model provider factory that can properly set up AndroidViewModel if the ViewModelStoreOwner implements HasDefaultViewModelProviderFactory
.
Theoretically, ComponentActivity
is indeed a HasDefaultViewModelProviderFactory
; and AppCompatActivity
extends from ComponentActivity
.
In your case however, that doesn't seem to be the case. For some reason, your AppCompatActivity
is not HasDefaultViewModelProviderFactory
.
I think the solution to your problem is to update Lifecycle to 2.2.0, and ALSO update implementation 'androidx.core:core-ktx
to at least 1.2.0. (specifically at least AndroidX-Activity 1.1.0, and AndroidX-Fragment 1.2.0).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With