dagger: [Hilt] Multiple viewmodel instances with different keys crash
Hello
I have recently swapped to Hilt. Before we used dagger and had the following code which would allow us to have multiple instance of the same viewmodel, but with different keys:
fun <T : ViewModel> getUniqueViewModel(modelClass: Class<T>, key: String): T {
val viewModel = ViewModelProvider.get(key, modelClass)
return viewModel
}
Example of use:
val firstViewModel = getUniqueViewModel(TestViewModel.class, "key1")
val secondViewModel = getUniqueViewModel(TestViewModel.class, "key2")
But when I’m using Hilt I get the following error when I try to get my second instance:
java.lang.IllegalArgumentException: SavedStateProvider with the given key is already registered
at androidx.savedstate.SavedStateRegistry.registerSavedStateProvider(SavedStateRegistry.java:111)
at androidx.lifecycle.SavedStateHandleController.attachToLifecycle(SavedStateHandleController.java:50)
at androidx.lifecycle.SavedStateHandleController.create(SavedStateHandleController.java:70)
at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:67)
at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:84)
at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:108)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:187)
at com.test.BaseActivity.getUniqueViewModel(BaseActivity.kt:162)
Doesn’t Hilt allow multiple viewModels with different keys?
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 14
- Comments: 32 (6 by maintainers)
Commits related to this issue
- update hilt version to solve https://github.com/google/dagger/issues/2328#event-7067168239 — committed to qiuzaiming/gallery by deleted user 2 years ago
Any update about this issue?
Any new about this issue?
Yup - Part of the problem is that
ViewModelProvider.KeyedFactory
is not a public API inandroidx.lifecycle
so we can’t makeHiltViewModelFactory
a keyed factory so that theViewModelProvider
propagates the key.We are working on a long term solution to have ‘extras’ be propagated via
ViewModelProvider
such that during ViewModel creation you get access to theSavedStateRegistryOwner
,Application
and ViewModel keys if available instead of having to create custom factories with tight inheritance.Sadly, I don’t have a workaround recommendation for this issue, I can only say to make distinct ViewModel classes with a shared parent since the ViewModel class is the key used.
We could try to make
HiltViewModelFactory
extendAbstractSavedStateViewModelFactory
like it was in theandroidx.hilt
package but that has its own set of problems, specifically it eagerly creates aSavedStateHandle
that might be thrown out if your ViewModel does not use it and it might cause conflicting key issues too when the ViewModel is not Hilt injected and is just a vanilla ViewModel.Just upgrading to 2.5.0 isn’t enough, it requires a change on the Hilt side. I have a PR to do that, but it has been delayed due to various dependency issues that need sorting out. We’re aiming to get it submitted and then do a release next week, assuming no other issues come up.
Any updates? We really need this.
And the fix is released 🎉 https://github.com/google/dagger/releases/tag/dagger-2.43 According to the release description, it fixes this issue. I’ll test it tomorrow on my library.
Great job 👏
We need it to actually hit the stable release. After that happens (which since it is in RC should be relatively soon hopefully), we should be able to make the change in Hilt and do a release shortly after.
Seems the changes linked in https://github.com/google/dagger/issues/2328#issuecomment-772093299 has been fixed in https://issuetracker.google.com/issues/188541057. But it seems that it hasn’t been released yet.
Lifecycle hit 2.5.0 stable!
Multi-instance is a very important feature for some type of project, Hope it can be fixed soon
Thanks
Until Hilt provides an API to use the key explicitly, you easily create one yourself using the
viewModel(key = "myKey", factory = hiltFactory)
(new API with creation extras) and create an instance of the Hilt factory like this.FYI: I tested this in my own library and it works like a charm https://github.com/sebaslogen/resaca/blob/main/resacahilt/src/main/java/com/sebaslogen/resaca/hilt/ScopedMemoizers.kt#L57
androidx.lifecycle moved to beta: Version 2.5.0-beta01 🥳, meaning the API is now stable enough to proceed with this?
The CreationExtras change that we need for this is unfortunately still in alpha https://developer.android.com/jetpack/androidx/releases/lifecycle#2.5.0-alpha01. Once that is stable, I think we can update Hilt’s ViewModelFactory to pass through CreationExtras and then I think it would just work since the key is now derived from that instead of via that
KeyedFactory
interface mentioned in above comments.now you can basically create fun:
and then usage like:
any update on this ? Thanks
Just a quick summary, we are working on fixing this, but unfortunately just switching back to AbstractSavedStateViewModelFactory will not work due to injection timing issues and the SavedStateHandle. As @danysantiago mentioned above, we’re looking at some long term solutions like that in https://android-review.googlesource.com/c/platform/frameworks/support/+/1535760. Unfortunately, until then, HiltViewModels will not be able to support multiple keys.
Oh… It stopped being an AbstractSavedStateViewModelFactory. I knew it used to be one.
Yeah, I’d consider that a Hilt issue. 🤔
in my current use-case distinct ViewModels won’t work, I’m currently using the ViewModel framework to load additional complex data about individual items in a recycler view. Each item gets its own distinct instance of the ViewModel and the key is based off the unique id of the recycler view item.
I’ll probably just rework my use-case to store all the individual item ViewModels in a single parent ViewModel when I have a chance, until then I’ll hold off on upgrading
Forgot to tag this issue in https://github.com/google/dagger/commit/74ea7653a2a51ccb7246066c548184ce1e899045, but that commit should fix this issue.
Yes, it should now be possible.
I’ve run into the same issue when switching from the AndroidX hilt-lifecycle-viewmodel to the dagger hilt view model.
I believe the core of the issue is that
HiltViewModelFactory
implementsViewModelProvider.Factory
and doesn’t extendViewModelProvider.KeyedFactory
which provides support for defining custom keys for ViewModels.@tommyzat I’m not sure I fully understand your question correctly, but if it’s about my library (https://github.com/sebaslogen/resaca) it’s better to move the discussion out of this Hilt/Dagger issue. You can either open an issue on the Resaca GitHub space or contact me on Twitter @sebaslogen
I have updated to the latest version 2.5.0, but this problem still exists.
Also waiting for this much needed feature
it relies on this new “ViewModel CreationExtras” thingy 😅
This isn’t actually a Hilt issue. If you weren’t using AbstractSavedStateViewModelFactory, only one of your ViewModels would be stored in the store, because the default key in the ViewModelStore is the canonical name of the ViewModel.
IIRC the code says “//TODO: log a warning”.
https://android.googlesource.com/platform/frameworks/support/+/androidx-main/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelProvider.java#181
So N-1 viewModels would actually not even be stored across config changes.
Anyway, the AbstractSavedStateViewModelFactory is a KeyedFactory
https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-main/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/AbstractSavedStateViewModelFactory.java?source=post_page---------------------------%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F#35
So try
ViewModelProvider(this, hiltViewModelFactory).get("viewModel1", MyViewModel::class.java)
andViewModelProvider(this, hiltViewModelFactory).get("viewModel2", MyViewModel::class.java)
Oh.