You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[AssistedInject] Integration with @HiltViewModel #2287
It'd be nice to have Assisted Injection support for Hilt ViewModels.
A nice API would be something like the following:
@HiltViewModel
class PlantDetailViewModel @AssistedInject constructor(
savedStateHandle: SavedStateHandle,
plantRepository: PlantRepository,
@Assisted private val plantId: String
) : ViewModel() { ... }
@AssistedFactory
interface PlantDetailViewModelFactory {
fun create(plantId: String): PlantDetailViewModel
}
And use it from the View like:
@AndroidEntryPoint
class PlantDetailFragment : Fragment() {
private val args: PlantDetailFragmentArgs by navArgs()
@Inject
lateinit var plantDetailViewModelFactory: PlantDetailViewModelFactory
private val plantDetailViewModel: PlantDetailViewModel by viewModels {
plantDetailViewModelFactory.create(args.plantId)
}
}
Trying to use @HiltViewModel with AssistedInject gives a ViewModel constructor should be annotated with @Inject instead of @AssistedInject using Dagger/Hilt 2.31
Reacted by feivur, Majid Ahmadi Jebeli, Yosif, Vengatesh M, dk-3cx, Levi Melamed, Matej Hlatký, Rodrigo, Martin Mose Facondini, IsakTheHacker and 2 more
In Dagger 2.31, it's possible to achieve the above without using @HiltViewModel and passing everything manually
class PlantDetailViewModel @AssistedInject constructor(
plantRepository: PlantRepository,
@Assisted private val savedStateHandle: SavedStateHandle,
@Assisted private val plantId: String
) : ViewModel() {
@AssistedFactory
interface PlantDetailViewModelFactory {
fun create(handle: SavedStateHandle, plantId: String): PlantDetailViewModel
}
companion object {
fun provideFactory(
assistedFactory: PlantDetailViewModelFactory,
owner: SavedStateRegistryOwner,
defaultArgs: Bundle? = null,
plantId: String
): AbstractSavedStateViewModelFactory = object : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(key: String, modelClass: Class<T>, handle: SavedStateHandle): T {
return assistedFactory.create(handle, plantId) as T
}
}
}
}
And consume it in the Fragment as
@AndroidEntryPoint
class PlantDetailFragment : Fragment() {
private val args: PlantDetailFragmentArgs by navArgs()
@Inject
lateinit var plantDetailViewModelFactory: PlantDetailViewModelFactory
private val plantDetailViewModel: PlantDetailViewModel by viewModels {
PlantDetailViewModel.provideFactory(plantDetailViewModelFactory, this, arguments, args.plantId)
}
}
Reacted by Gabor Varadi, Bitlinker, Bryan Dela Cruz, drinkthestars, Alexander Ruhland, Dmytro Ivanov, Illia Achour, Hossain Khan, Ajay Singh Dewari, kefasjw and 22 more
In Dagger 2.31, it's possible to achieve the above without using @HiltViewModel and passing everything manually
class PlantDetailViewModel @AssistedInject constructor(
plantRepository: PlantRepository,
@Assisted private val savedStateHandle: SavedStateHandle,
@Assisted private val plantId: String
) : ViewModel() {
@AssistedFactory
interface PlantDetailViewModelFactory {
fun create(handle: SavedStateHandle, plantId: String): PlantDetailViewModel
}
companion object {
fun provideFactory(
assistedFactory: PlantDetailViewModelFactory,
owner: SavedStateRegistryOwner,
defaultArgs: Bundle? = null,
plantId: String
): AbstractSavedStateViewModelFactory = object : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(key: String, modelClass: Class<T>, handle: SavedStateHandle): T {
return assistedFactory.create(handle, plantId) as T
}
}
}
}
And consume it in the Fragment as
@AndroidEntryPoint
class PlantDetailFragment : Fragment() {
private val args: PlantDetailFragmentArgs by navArgs()
@Inject
lateinit var plantDetailViewModelFactory: PlantDetailViewModelFactory
private val plantDetailViewModel: PlantDetailViewModel by viewModels {
PlantDetailViewModel.provideFactory(plantDetailViewModelFactory, this, arguments, args.plantId)
}
}
FTR, it's even easier without the SavedStateHandle dependency (although always consider using SavedStateHandle in your VMs)
class PlantDetailViewModel @AssistedInject constructor(
plantRepository: PlantRepository,
@Assisted private val plantId: String
) : ViewModel() {
...
companion object {
fun provideFactory(
assistedFactory: PlantDetailViewModelFactory,
plantId: String
): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return assistedFactory.create(plantId) as T
}
}
}
}
@AssistedFactory
interface PlantDetailViewModelFactory {
fun create(plantId: String): PlantDetailViewModel
}
And consume it in the View like:
@AndroidEntryPoint
class PlantDetailFragment : Fragment() {
private val args: PlantDetailFragmentArgs by navArgs()
@Inject
lateinit var plantDetailViewModelFactory: PlantDetailViewModelFactory
private val plantDetailViewModel: PlantDetailViewModel by viewModels {
PlantDetailViewModel.provideFactory(plantDetailViewModelFactory, args.plantId)
}
}
Reacted by Tobenna Ezike, Mikhalchuk Grigoriy, Abdelraouf Sabri, Anton Kovalov, saied89, Euan Rochester, Mike, Farrukh Khusainov, Fikky Ardianto, Trubnikov Dima and 3 more
Reacted by Gabor Varadi, glureau-betclic, Carlos Guerrero, Marco RS, Roman Tikonov and Fabio Santo
@manuelvicnt Since we're not able to add @HiltViewModel for a ViewModel having Assisted Injection, can't install modules using ViewModelComponent. So currently using ActivityRetainedComponent for a repository which is going to be injected with that ViewModel. Is it correct to use it like this?
Reacted by Yogesh Choudhary, Tan Jun Rong, Mario Bat and Vikram Singh
A good improvement on top of the workaround is to define a few extensions to generalize the solution so we don't need to repeat that boilerplate in each ViewModel. Here's the example for the fragment scoped ViewModel case:
It would definitely be better to have Hilt support this out of the box, though, and I really like the API suggested here.
Reacted by Shreyas Patil, Riccardo Ciovati, Tobenna Ezike, Nicolas Gouzy, Artur Artikov, Okonji Emmanuel , Michael Nimbs, FunkyMuse, Osip Fatkullin, Ash Davies and 36 more
I'm using Jetpack compose in a project and here's a single activity I'm using in it. It's using Jetpack navigation for various screens. NoteDetailViewModel is using Assisted Injection so I want to access the factory when I only need it i.e. in Composable function. So I've achieved right now like this...
So my question - Is it good to use this approach for getting ViewModel factory in Composable functions? Or is there any other workaround for getting Assisted Injection ViewModel factory in such functions?
This is preventing me from sharing a binding between a ViewModel annotated with @HiltViewModel and one that's using @AssistedInject
@manuelvicnt Since we're not able to add @HiltViewModel for a ViewModel having Assisted Injection, can't install modules using ViewModelComponent. So currently using ActivityRetainedComponent for a repository which is going to be injected with that ViewModel. Is it correct to use it like this?
This seems to be the only way to share it as far as I can tell. 🙁
At this moment SavedStateHandle is the only @assisted injection I'm using which works fine with @ViewModelInject and no workarounds needed. @ViewModelInject is deprecated now, so please don't remove it before this feature request has been implemented :)
@wbervoets With the new @HiltViewModel you can have the SavedStateHandle as a dependency without having to annotate it with @Assisted. The SavedStateHandle is now a binding from the new ViewModelComponent, so it doesn't need to be assist-injected anymore.
Reacted by Wim Bervoets, Agung Subastian, Shohei Kawano, Reza Najafi, Norris Aboagye Boateng, Abdelraouf Sabri, s12u, tcqq, Ricardo Sousa, Ricard and 25 more
Reacted by Ricard, Radoslav Backovsky, Hossain Khan, Chandan Kumar Mandal, Jeremy Walker, Martin M., santu01, ThinkDeeper, Mustafa Berkay Mutlu, IsakTheHacker and 1 more
Reacted by Radoslav Backovsky, Hossain Khan, matteofabris, IsakTheHacker and furkanayaz
Sorry for being late to this thread, but I should clarify some issues with the workaround described in #2287 (comment). The high-level is though that people should not use this workaround.
The issue with the workaround is that the assisted factory is injected from the FragmentComponent (since it is injected directly into the fragment). This is a problem because it basically all but guarantees you're going to leak your activity/fragment instance into the ViewModel. This can happen simply by injecting the wrong thing into the ViewModel, but even if you are diligent about that, there's also the issue of multibinding contributions installed in the FragmentComponent. Finally, if you use fastInit mode (which is the default in Hilt), any reference to a Provider<> in the transitive deps of your ViewModel will leak the component, which will include the fragment instance (https://dagger.dev/dev-guide/compiler-options).
The main workaround we suggest is to use the arguments bundle in your fragment which should be accessible via the SavedStateHandle injected in the ViewModel. This should handle most data types.
For other object types, hopefully rarer, I think the options are passing them as arguments when calling methods on the ViewModel or using a setter method (though similarly, be careful of leaks in this case, especially with any function closures as those may reference the fragment).
Reacted by Alexander Ruhland, FunkyMuse, Daniel Kim, drinkthestars, Osaigbovo Odiase, Ricardo Costeira, trietbui85, Gabor Varadi, Farrukh Khusainov, Seyyed davud hosseiny and 5 more
... use the arguments bundle in your fragment which should be accessible via the SavedStateHandle injected in the ViewModel
@Chang-Eric Could you point to where this is documented? When reading up on ViewModels, my impression had been that nothing would be prepopulated for "fresh" instances. This could likely cover most cases where we'd otherwise require @AssistedInject support (depending on how ViewModels are instantiated & reused for Fragment instances with different arguments).
The main workaround we suggest is to use the arguments bundle in your fragment which should be accessible via the SavedStateHandle injected in the ViewModel. This should handle most data types.
@Chang-Eric There's a big downside with this, though: we lose type safety. We've come a long way with Navigation Safe Args, so losing that here isn't great. I understand the technical limitations, but I thought it would still be relevant to bring this up -- I would much rather have lint checks helping with my diligence when it comes to ViewModels (e.g. preventing me to inject Provider<>) than to give up on type safety.
@carstenhag Yes! Assisted injection with ViewModels was added in Dagger 2.49 and overloads for functions like hiltViewModel() were added to the AndroidX part of Hilt in 1.2.0. Thanks for the reminder!
Reacted by Carsten Hagemann, Sven Jacobs, Karol Kamiński, Kakeru Nakabachi, Danni and Wojciech Rozwadowski
@myounis97 As it's mentioned here, SavedStateHandle is a binding from ViewModelComponent so you can add it as a regular non-assisted dependency to your ViewModel if you're using Hilt.
@myounis97 As it's mentioned here, SavedStateHandle is a binding from ViewModelComponent so you can add it as a regular non-assisted dependency to your ViewModel if you're using Hilt.
Unfortunately it doesn't work with @hiltviewmodel(assistedFactory) it gives compile time error cannot inject savedStateHandle without @provide
Facing Dagger does not support providing @AssistedFactory types. while writing Fragment/Activity test. What's the right way to provide factory ? @manuelvicnt@tfcporciuncula
@HiltAndroidTest
class FragmentTest {
@RelaxedMockK
lateinit var viewmodel:HubViewModel
@BindValue
val hubViewModelAssistedFactory = object : HubViewModelAssistedFactory {
override fun create(flowProvider: Provider): HubViewModel {
return viewmodel
}
}
@iamanbansal You can't bind over the AssistedFactory binding, even in tests, as that is something Hilt is passing to your callback. In general, we don't support providing a fake ViewModel, but rather encourage changing the bindings the ViewModel depends on in tests.
Thanks @Chang-Eric ! I can change other bindings of VM but how would I change the binding of assisted one (provider in my case) in test case? Currently real object is being passed from the Fragment.
It would be great if there is any example of Fragment/Activity Test for such Assisted Injection
@iamanbansal Assisted parameters are passed from your code and not from Dagger/Hilt so there's nothing we really provide here. Those parameters are kind of outside of dependency injection so you'd have to figure out your own strategy to handle the arguments like any other argument you pass into a normal method.
Activity
manuelvicnt commentedon Jan 17, 2021
Trying to use
@HiltViewModelwithAssistedInjectgives aViewModel constructor should be annotated with @Inject instead of @AssistedInjectusing Dagger/Hilt 2.31manuelvicnt commentedon Jan 17, 2021
In Dagger 2.31, it's possible to achieve the above without using
@HiltViewModeland passing everything manuallyAnd consume it in the Fragment as
Ezike commentedon Jan 17, 2021
thanks for the workaround :) @manuelvicnt
manuelvicnt commentedon Jan 18, 2021
FTR, it's even easier without the
SavedStateHandledependency (although always consider usingSavedStateHandlein your VMs)And consume it in the View like:
PatilShreyas commentedon Jan 18, 2021
@manuelvicnt Since we're not able to add
@HiltViewModelfor a ViewModel having Assisted Injection, can't install modules usingViewModelComponent. So currently usingActivityRetainedComponentfor a repository which is going to be injected with that ViewModel. Is it correct to use it like this?tfcporciuncula commentedon Jan 18, 2021
A good improvement on top of the workaround is to define a few extensions to generalize the solution so we don't need to repeat that boilerplate in each ViewModel. Here's the example for the fragment scoped ViewModel case:
And then in the fragment:
Other extensions can then be added to cover the other cases.
It would definitely be better to have Hilt support this out of the box, though, and I really like the API suggested here.
PatilShreyas commentedon Jan 18, 2021
I'm using Jetpack compose in a project and here's a single activity I'm using in it. It's using Jetpack navigation for various screens.
NoteDetailViewModelis using Assisted Injection so I want to access the factory when I only need it i.e. in Composable function. So I've achieved right now like this...@Composablefunction I wrote this and it's perfectly working fineSo my question - Is it good to use this approach for getting ViewModel factory in Composable functions? Or is there any other workaround for getting Assisted Injection ViewModel factory in such functions?
cc: @manuelvicnt
luis-cortes commentedon Jan 26, 2021
This is preventing me from sharing a binding between a
ViewModelannotated with@HiltViewModeland one that's using@AssistedInjectThis seems to be the only way to share it as far as I can tell. 🙁
wbervoets commentedon Feb 2, 2021
At this moment SavedStateHandle is the only @assisted injection I'm using which works fine with @ViewModelInject and no workarounds needed. @ViewModelInject is deprecated now, so please don't remove it before this feature request has been implemented :)
tfcporciuncula commentedon Feb 2, 2021
@wbervoets With the new
@HiltViewModelyou can have theSavedStateHandleas a dependency without having to annotate it with@Assisted. TheSavedStateHandleis now a binding from the newViewModelComponent, so it doesn't need to be assist-injected anymore.Chang-Eric commentedon Feb 19, 2021
Sorry for being late to this thread, but I should clarify some issues with the workaround described in #2287 (comment). The high-level is though that people should not use this workaround.
The issue with the workaround is that the assisted factory is injected from the
FragmentComponent(since it is injected directly into the fragment). This is a problem because it basically all but guarantees you're going to leak your activity/fragment instance into the ViewModel. This can happen simply by injecting the wrong thing into the ViewModel, but even if you are diligent about that, there's also the issue of multibinding contributions installed in theFragmentComponent. Finally, if you usefastInitmode (which is the default in Hilt), any reference to aProvider<>in the transitive deps of your ViewModel will leak the component, which will include the fragment instance (https://dagger.dev/dev-guide/compiler-options).The main workaround we suggest is to use the arguments bundle in your fragment which should be accessible via the
SavedStateHandleinjected in the ViewModel. This should handle most data types.For other object types, hopefully rarer, I think the options are passing them as arguments when calling methods on the ViewModel or using a setter method (though similarly, be careful of leaks in this case, especially with any function closures as those may reference the fragment).
dandc87 commentedon Feb 19, 2021
@Chang-Eric Could you point to where this is documented? When reading up on ViewModels, my impression had been that nothing would be prepopulated for "fresh" instances. This could likely cover most cases where we'd otherwise require
@AssistedInjectsupport (depending on how ViewModels are instantiated & reused for Fragment instances with different arguments).tfcporciuncula commentedon Feb 19, 2021
@Chang-Eric There's a big downside with this, though: we lose type safety. We've come a long way with Navigation Safe Args, so losing that here isn't great. I understand the technical limitations, but I thought it would still be relevant to bring this up -- I would much rather have lint checks helping with my diligence when it comes to ViewModels (e.g. preventing me to inject
Provider<>) than to give up on type safety.@dandc87 You can look directly at the code:
FragmentViewModelProvider.Factorycreation, where theFragmentpassesgetArguments()as thedefaultArgsofSavedStateViewModelFactory.Activity, but instead ofgetArguments()it'sgetIntent().getExtras().43 remaining items
armichaud commentedon Jan 19, 2024
For those who might be reading this thread looking for how in the end this works with
hiltViewModel(), here's a guide.cgaisl commentedon Jan 20, 2024
I've shared my journey of trying to pass runtime arguments to a @hiltviewmodel here.
carstenhag commentedon Feb 26, 2024
@kuanyingchou This ticket can also be closed, right? It is mentioned in the release notes as fixed
kuanyingchou commentedon Feb 27, 2024
@carstenhag Yes! Assisted injection with ViewModels was added in Dagger 2.49 and overloads for functions like
hiltViewModel()were added to the AndroidX part of Hilt in 1.2.0. Thanks for the reminder!myounis97 commentedon Feb 29, 2024
How can I inject SavedStateHandle with the new approach I used to inject it like in the screenshot is there a way to migrate to the new approach

?
tfcporciuncula commentedon Feb 29, 2024
@myounis97 As it's mentioned here,
SavedStateHandleis a binding fromViewModelComponentso you can add it as a regular non-assisted dependency to your ViewModel if you're using Hilt.myounis97 commentedon Feb 29, 2024
Unfortunately it doesn't work with @hiltviewmodel(assistedFactory) it gives compile time error cannot inject savedStateHandle without @provide
tfcporciuncula commentedon Feb 29, 2024
This compiles fine for me:
And I'm able to create the ViewModel with:
As it's described in the docs.
myounis97 commentedon Feb 29, 2024
@tfcporciuncula My bad i didn't remove the factory from the Singleton Scoped Entry point after removing it everything worked fine thanks
Zhuinden commentedon Feb 17, 2025
Just making sure these two issues are related via links #3523 (comment)
iamanbansal commentedon Jun 19, 2025
Facing
Dagger does not support providing @AssistedFactory types.while writing Fragment/Activity test. What's the right way to provide factory ?@manuelvicnt @tfcporciuncula
Chang-Eric commentedon Jun 20, 2025
@iamanbansal You can't bind over the AssistedFactory binding, even in tests, as that is something Hilt is passing to your callback. In general, we don't support providing a fake ViewModel, but rather encourage changing the bindings the ViewModel depends on in tests.
iamanbansal commentedon Jun 20, 2025
Thanks @Chang-Eric ! I can change other bindings of VM but how would I change the binding of assisted one (provider in my case) in test case? Currently real object is being passed from the Fragment.
It would be great if there is any example of Fragment/Activity Test for such Assisted Injection
Chang-Eric commentedon Jun 23, 2025
@iamanbansal Assisted parameters are passed from your code and not from Dagger/Hilt so there's nothing we really provide here. Those parameters are kind of outside of dependency injection so you'd have to figure out your own strategy to handle the arguments like any other argument you pass into a normal method.