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)
}
}
Regarding Safe Args, an upcoming release of it will support parsing args from a SavedStateHandle. That should help with regards to arguments type safety.
Reacted by Patrick Millais, Alexander Ruhland and Oleksandr Berezhnyi
@mhernand40 I think the difference is that example you linked does not use @HiltViewModel which means it does not work with the ViewModelComponent. I believe it is the same as the workaround mentioned in #2287 (comment) which I have advised people not to use in #2287 (comment) due to leaking the fragment/activity instance.
@mhernand40 I think the difference is that example you linked does not use @HiltViewModel which means it does not work with the ViewModelComponent. I believe it is the same as the workaround mentioned in #2287 (comment) which I have advised people not to use in #2287 (comment) due to leaking the fragment/activity instance.
I've read the explanation of the issue, using simple stuff like passing simple strings, ints or anything not related to a fragment/activity should be fine with the workaround suggested in the OP?
@FunkyMuse No, you still risk leaking the fragment/activity (even by just having a Provider<> somewhere in a transitive dep), which is not based on what you assist the factory with if you use the workaround where you inject the assisted factory into the fragment.
Passing simple strings, ints and things are okay if you pass those through the Bundle/SavedStateHandle though.
@mhernand40 I think the difference is that example you linked does not use @HiltViewModel which means it does not work with the ViewModelComponent. I believe it is the same as the workaround mentioned in #2287 (comment) which I have advised people not to use in #2287 (comment) due to leaking the fragment/activity instance.
I've read the explanation of the issue, using simple stuff like passing simple strings, ints or anything not related to a fragment/activity should be fine with the workaround suggested in the OP?
Agreed. Sometimes there are cases where an Intent argument is first passed to an Activity that needs to be propagated through to the ViewModel. During a configuration change, the Intent arguments should remain intact, hence there is probably no need to re-bind the argument(s) to the ViewModel?
@FunkyMuse No, you still risk leaking the fragment/activity (even by just having a Provider<> somewhere in a transitive dep), which is not based on what you assist the factory with if you use the workaround where you inject the assisted factory into the fragment.
Passing simple strings, ints and things are okay if you pass those through the Bundle/SavedStateHandle though.
That works for fragment/activity, would the same work for the compose world?
Since there you'd still have arguments to pass but usually they're passed inside a compose function and then you create the view model
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).
No, you still risk leaking the fragment/activity (even by just having a Provider<> somewhere in a transitive dep), which is not based on what you assist the factory with if you use the workaround where you inject the assisted factory into the fragment.
@Chang-Eric Trying to get a deeper understanding where the potential memory leak comes from with OP's workaround.
Going through your comments, the Activity/Fragment could be leaked when:
"injecting the wrong thing into the ViewModel"
"having a Provider<> somewhere in a transitive dep"
Even if those points are handled, you mention there's also the issue of multibinding contributions installed in the FragmentComponent, implying there's something inherently wrong with the very idea of "injecting assisted factory from the FragmentComponent". Could you elaborate? 🙏🏼
@drinkthestars I think the issue is just that even if you address 1 and 2 at the time you first introduce the ViewModel injection, it just isn't going to be durable as code changes without a lot of care and effort. Generally, a binding in the FragmentComponent should be free to inject the Fragment as a dependency, but if you inject your ViewModels using the workaround from the FragmentComponent, then every time you change a FragmentComponent dependency you will need to check all of the users up the chain.
This is what Dagger components actually do for you. It is why it is nice to split say the ApplicationComponent from the ActivityComponent so that if you use the Activity from an @Singleton binding, you get a missing binding error instead of a memory leak. In Hilt, the components are also set up to prevent this for ViewModels (in https://dagger.dev/hilt/components, you can see how the ViewModelComponent branches off before the ActivityComponent and FragmentComponent). If you use the workaround, then you'll subvert this.
Gotcha, thank you for clarifying @Chang-Eric. So the case seems to be:
@HiltViewModel's ensures that there is an associated with ViewModelComponent which reflects the lifetime of the single ViewModel. If ViewModels are injected using the AssistedFactory workaround from the FragmentComponent, you end up associating a ViewModel related thing with a FragmentComponent which has a shorter lifecycle than a ViewModel/ViewModelComponent. The workaround is mixing two hierarchies of components where one should outlive the other, and hence it is not recommended.
Reacted by Eric Chang, Ralph Luckernst N., Simelus, FunkyMuse, pratclot, kefasjw, shiita0903, PunitD, TuanPhamQuoc, Trubnikov Dima, Levi Melamed and 1 more
This absolutely should be supported. Injecting ViewModels/Presenters is one of the primary use cases of AssistedInjection on Android.
Reacted by Michał Janowicz, Andrew, Nicolás Martín González Casas, Edgar Miró, Konstantin Zolotov, João Silva, Nobuya Oshiro, Mathew Sachin, Marco RS, crusom and 43 more
If the main reason for assisted injection is just passing arguments, then as others pointed, SavedStateHandle has them, and starting with version 2.4.0 of the navigation library, we'll get a way to retrieve safe args from it.
As a temporary workaround, I'm using this extension:
inlinefun <reifiedArgs : NavArgs> SavedStateHandle.navArgs() =NavArgsLazy(Args::class) {
val bundle =Bundle()
keys().forEach {
val value = get<Any>(it)
if (value isSerializable) {
bundle.putSerializable(it, value)
} elseif (value isParcelable) {
bundle.putParcelable(it, value)
}
}
bundle
}
to create the corresponding Args class, and I use it in the viewmodel like this:
If the main reason for assisted injection is just passing arguments, then as others pointed, SavedStateHandle has them, and starting with version 2.4.0 of the navigation library, we'll get a way to retrieve safe args from it.
As a temporary workaround, I'm using this extension:
inlinefun <reifiedArgs : NavArgs> SavedStateHandle.navArgs() =NavArgsLazy(Args::class) {
val bundle =Bundle()
keys().forEach {
val value = get<Any>(it)
if (value isSerializable) {
bundle.putSerializable(it, value)
} elseif (value isParcelable) {
bundle.putParcelable(it, value)
}
}
bundle
}
to create the corresponding Args class, and I use it in the viewmodel like this:
val args:YourFragmentArgs by savedState.navArgs()
where savedState is the inject SavedStateHandle
This approach has a caveat, with bundle it would not be possible to unit test viewModels that have args
Is there any plan to support AssistedInject in ViewModel?
Reacted by Omer Karakose, kefasjw, Michael Ferguson, takumi-saito, Jannis Veerkamp, Rodrigo José Madeira Lourenço, Fedor Sokolov, mukai_masaki, Julio Ernesto Rodríguez Cabañas, StefanNestorov and 3 more
Is there any plan to support AssistedInject in ViewModel?
We've looked into it, but it isn't trivial and so far the API wouldn't be that great. This doesn't mean it is off the table, but it also isn't a high priority compared to other things so I wouldn't expect it anytime in the near future.
Reacted by Omer Karakose, Nathan Johnson, Tobias Kreß, Artur Artikov, mukai_masaki, Gerardo Epitacio, Julio Ernesto Rodríguez Cabañas, StefanNestorov, vrubinets and amansaryal
Is there any plan to support AssistedInject in ViewModel?
We've looked into it, but it isn't trivial and so far the API wouldn't be that great. This doesn't mean it is off the table, but it also isn't a high priority compared to other things so I wouldn't expect it anytime in the near future.
according to requests in this topic I think assisted inject in viewmodel is a common use case
Reacted by Omer Karakose, Michael Ferguson, kefasjw, Jannis Veerkamp, David, caeserfave, nedarokt, Rodrigo José Madeira Lourenço, goldenreign, Nathan Johnson and 19 more
@hichamboushaba That is nice, but makes testing the ViewModel difficult. Did you find a way to write unit tests for a ViewModel with this?
I really prefer the ViewModel to take in real arguments in the constructor but this is apparently not possible to do correctly with Hilt currently.
For tests, you can always pass your copy of SavedStateHandle that you can use for the logic, either by manually creating it using argument keys, or by leveraging the toBundle method, the issue is that like others mentioned, we will depend on Bundle for the tests, so for now, and until the version 2.4.0 of navigation component is released, we depend on Robolectric for the unit tests, and as far as it's temporary, we as a team agreed it's not a blocker.
To init SavedStateHandle from our NavArgs class, we use the following extensions in tests:
fun NavArgs.initSavedStateHandle(): SavedStateHandle {
val toBundleMethod = javaClass.getDeclaredMethod("toBundle")
val bundle = toBundleMethod.invoke(this) asBundleval argumentsMap = bundle.keySet().map { it to bundle[it] }.toMap()
returnSavedStateHandle(argumentsMap)
}
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).
I get you have other priorities, but I believe AssistedInject is important.
Is there any plan to support AssistedInject in ViewModel?
We've looked into it, but it isn't trivial and so far the API wouldn't be that great. This doesn't mean it is off the table, but it also isn't a high priority compared to other things so I wouldn't expect it anytime in the near future.
You mentioned it is trivial to do. I don't think it's trivial to most people using Hilt / Dagger.
May I ask you to provide examples of both of these?
Thanks.
@danielesegato It is half priorities, half that we haven't found the right API for this. We plan to look into the API again after https://issuetracker.google.com/188541057 is resolved as that will solve some of the API problems we're running into. This is lower priority because there are workarounds like using the SavedStateHandle (which is probably a best practice over AssistedInject because it cannot leak the activity/fragment) and for those things that cannot be put in the bundle, you can use a setter method (obviously not ideal from a code cleanliness perspective, but this case should be rarer). Do you have a case that cannot be addressed with these workarounds?
We've looked into it, but it isn't trivial and so far
You mentioned it is trivial to do
I don't think I said it was trivial unless you're referring to something else that you didn't quote. In fact I thought I said it is not trivial?
This is lower priority because there are workarounds like using the SavedStateHandle (which is probably a best practice over AssistedInject because it cannot leak the activity/fragment) and for those things that cannot be put in the bundle, you can use a setter method (obviously not ideal from a code cleanliness perspective, but this case should be rarer).
I still think I should be able to use @HiltViewModel with @AssistedInject.
the @AssistedFactory could than be used with by viewModelFactory(Factory::class).viewModel(...parameters...) or by viewModelFactory(Factory::class).activityViewModel(...parameters...)`
there should a lambda somewhere there to avoid re-executing parameter creation if not needed
Thanks.
Reacted by Julio Ernesto Rodríguez Cabañas, Nathan Reline, Ian Roberts and Markus Schottenhammer
Sorry, I don't have any real update at this time. We're focusing on KSP migrations for Dagger so that is taking a lot of our time right now so this is still a lower priority. Also, I think that necessary fix is still only in alpha, so we can't depend on it yet since Hilt is stable.
Also, there's still some uncertainty about the direction we want to take the API. ViewModels do not compose together well, so there are some questions about if use cases would be better served with more things like ActivityRetainedComponent but for other lifetimes since then Dagger objects in there can compose better and don't have the same leaking risks that @AssistedInject into ViewModels presents. That's not to commit one way or another, but just to let you know that even if all the immediate blockers are resolved, this isn't a clear cut case where we just need to find the time to put some code together.
Hope that makes sense, and sorry this probably isn't the update you were looking for.
Reacted by Andrey Mischenko, Исрапил Ахмедов, IhorʼOk, Nika Zurbaevi, Melih Aksoy, FunkyMuse, Alex Egin, Danni, Maciej Pigulski, Nicolas Fesquet and 2 more
@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.
Man, you saved my life. I'm using compose navigation and I send runtime parameters throw SavedSateHandle like you said. Nothing more is need. Thanks a lot!
Now that we can inject multiple ViewModel instances of the same type (#2328) inside the same Composable nav-destination/screen (for example for a View-Pager) I really miss the Assisted Injection to provide unique ids to each ViewModel.
Unfortunately, SavedStateHandle solution can't help in this situation because the navArgs only work at the Composable nav-destination/screen level, and in this use case the multiple ViewModel ids are only available after the Composable nav-destination/screen is loaded (probably through a screen level ViewModel that loads the ids of the View-Pager ViewModels).
Reacted by Gerardo Epitacio, BaoNguyen, Tiago Araujo and Hong
@manuelvicnt This is getting a little confusing given the length of the thread, lets see if I have this correctly.
We are trying to 'inject' the parameters passed during navigation directly to the view model whilst still having the goodness provided to us with the @HiltViewModel annotation yes?
If so then we do not need the hassle of creating a factory etc nor do we need the @Assisted for the injection. Further we also do not need to pass in the parameter to the screen/fragment which then passes it to the view model. Hilt already gives us this functionality.
I'm also looking forward to assisted injection for ViewModels. Given the stance "favour composition over inheritance", I would like to inject some "behavioural" pattern into the constructor of a VM which is only known at instantiation time. SavedStateHandle doesn't help here because this class cannot be put into a Bundle.
Reacted by BaoNguyen, Aleksander Mielczarek, Arben Maloku, kefasjw, Olli, Marek Macko, Allison Carter, Johannes Ptaszyk and Kelly Merrell
Really missing this feature, I tried to use the new ViewModelLifecycleScope {} with hiltViewModel() but unfortunately it crashes. Would help a lot for having different instances in a ViewPager with the same screen working independently.
Is there any plan to support AssistedInject in ViewModel?
We've looked into it, but it isn't trivial and so far the API wouldn't be that great. This doesn't mean it is off the table, but it also isn't a high priority compared to other things so I wouldn't expect it anytime in the near future.
You mean like for the next 15 years?
Reacted by Tiago Araujo, Sindre Moen and FunkyMuse
Just updating this. We have a plan for supporting this now, however, it is competing with other higher priority projects for development time like the migration to KSP, so there's no real timeline here yet.
FWIW, now that CreationExtras is supported, you can use the DEFAULT_ARGS_KEY key for setting the default args passed to a ViewModel's SavedStateHandle, so you don't have to only pass things through the Fragment arguments. So something like:
val myVM by viewModels<MyViewModel>(extrasProducer = {
MutableCreationExtras(defaultViewModelCreationExtras).apply {
set(DEFAULT_ARGS_KEY, bundleOf("someId" to someId))
}
})
will let you pass a value to be retrieved in the ViewModel through the SavedStateHandle.
Reacted by Sven Jacobs, Hicham Boushaba, Marek Macko, FunkyMuse, Necati Sözer, NegreaVlad, Yaroslav Shuliak, Levon, Daniele Segato, Dogan Kilic and 9 more
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:
A similar thing can already be done using annotation classes which use the navigation parameters to "assist" the additional parameters into the viewmodel constructor when using HiltViewModel() This gives the same behaviour as doing an @assisted on the view model. This works for something like compose navigation though could be extended to other screen creation/fragment creation.
@nvkleban@ubuntudroid This should be out in the next release, and hiltViewModel() will be updated next. In the meantime you can give HEAD-SNAPSHOT a try to see if it works for you!
Reacted by Sven Bendel, hoharald, Karol Kamiński, Saurabh, Rémi Latapy and Melih Aksoy
@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
danysantiago commentedon Feb 19, 2021
Regarding Safe Args, an upcoming release of it will support parsing args from a
SavedStateHandle. That should help with regards to arguments type safety.FunkyMuse commentedon Feb 27, 2021
How would this be done in the compose world? @manuelvicnt
mhernand40 commentedon Mar 12, 2021
Isn't the example in the issue summary already possible with the latest version of Dagger and Hilt?
Please see https://proandroiddev.com/whats-new-in-hilt-and-dagger-2-31-c46b7abbc64a#33b4
Chang-Eric commentedon Mar 12, 2021
@mhernand40 I think the difference is that example you linked does not use
@HiltViewModelwhich means it does not work with theViewModelComponent. I believe it is the same as the workaround mentioned in #2287 (comment) which I have advised people not to use in #2287 (comment) due to leaking the fragment/activity instance.mhernand40 commentedon Mar 12, 2021
@Chang-Eric ah I see. I missed that subtle, yet significant detail. Thanks for bringing it to my attention. 🙂
FunkyMuse commentedon Mar 12, 2021
I've read the explanation of the issue, using simple stuff like passing simple strings, ints or anything not related to a fragment/activity should be fine with the workaround suggested in the OP?
Chang-Eric commentedon Mar 12, 2021
@FunkyMuse No, you still risk leaking the fragment/activity (even by just having a
Provider<>somewhere in a transitive dep), which is not based on what you assist the factory with if you use the workaround where you inject the assisted factory into the fragment.Passing simple strings, ints and things are okay if you pass those through the Bundle/SavedStateHandle though.
mhernand40 commentedon Mar 12, 2021
Agreed. Sometimes there are cases where an Intent argument is first passed to an
Activitythat needs to be propagated through to theViewModel. During a configuration change, theIntentarguments should remain intact, hence there is probably no need to re-bind the argument(s) to theViewModel?FunkyMuse commentedon Mar 12, 2021
That works for fragment/activity, would the same work for the compose world?
Since there you'd still have arguments to pass but usually they're passed inside a compose function and then you create the view model
Currently this is how I'm doing it
Since the factory has to be injected in the activity, i assume activity has a chance of leaking?
Any alternative solution?
drinkthestars commentedon Mar 14, 2021
@Chang-Eric Trying to get a deeper understanding where the potential memory leak comes from with OP's workaround.
Going through your comments, the
Activity/Fragmentcould be leaked when:Even if those points are handled, you mention
there's also the issue of multibinding contributions installed in the FragmentComponent, implying there's something inherently wrong with the very idea of "injecting assisted factory from the FragmentComponent". Could you elaborate? 🙏🏼ReginFell commentedon Mar 15, 2021
Do you have any ideas when it will be shipped?
FunkyMuse commentedon Mar 15, 2021
It's already there, you pass the State handle with
@HiltViewModelAnd you have access to the
SavedStateHandle
And
Application
Chang-Eric commentedon Mar 15, 2021
@drinkthestars I think the issue is just that even if you address 1 and 2 at the time you first introduce the ViewModel injection, it just isn't going to be durable as code changes without a lot of care and effort. Generally, a binding in the
FragmentComponentshould be free to inject theFragmentas a dependency, but if you inject your ViewModels using the workaround from theFragmentComponent, then every time you change aFragmentComponentdependency you will need to check all of the users up the chain.This is what Dagger components actually do for you. It is why it is nice to split say the
ApplicationComponentfrom theActivityComponentso that if you use theActivityfrom an@Singletonbinding, you get a missing binding error instead of a memory leak. In Hilt, the components are also set up to prevent this for ViewModels (in https://dagger.dev/hilt/components, you can see how theViewModelComponentbranches off before theActivityComponentandFragmentComponent). If you use the workaround, then you'll subvert this.drinkthestars commentedon Mar 15, 2021
Gotcha, thank you for clarifying @Chang-Eric. So the case seems to be:
@HiltViewModel's ensures that there is an associated withViewModelComponentwhich reflects the lifetime of the single ViewModel. IfViewModels are injected using theAssistedFactoryworkaround from theFragmentComponent, you end up associating aViewModelrelated thing with aFragmentComponentwhich has a shorter lifecycle than aViewModel/ViewModelComponent. The workaround is mixing two hierarchies of components where one should outlive the other, and hence it is not recommended.ottne commentedon Mar 17, 2021
This absolutely should be supported. Injecting ViewModels/Presenters is one of the primary use cases of AssistedInjection on Android.
hichamboushaba commentedon Apr 21, 2021
If the main reason for assisted injection is just passing arguments, then as others pointed, SavedStateHandle has them, and starting with version 2.4.0 of the navigation library, we'll get a way to retrieve safe args from it.
As a temporary workaround, I'm using this extension:
to create the corresponding
Argsclass, and I use it in the viewmodel like this:where
savedStateis the injectSavedStateHandleReginFell commentedon Apr 30, 2021
This approach has a caveat, with
bundleit would not be possible to unit test viewModels that have argsalizarei95 commentedon May 31, 2021
Is there any plan to support AssistedInject in ViewModel?
Chang-Eric commentedon Jun 1, 2021
We've looked into it, but it isn't trivial and so far the API wouldn't be that great. This doesn't mean it is off the table, but it also isn't a high priority compared to other things so I wouldn't expect it anytime in the near future.
alizarei95 commentedon Jun 2, 2021
according to requests in this topic I think assisted inject in viewmodel is a common use case
nathan815 commentedon Jul 12, 2021
@hichamboushaba That is nice, but makes testing the ViewModel difficult. Did you find a way to write unit tests for a ViewModel with this?
I really prefer the ViewModel to take in real arguments in the constructor but this is apparently not possible to do correctly with Hilt currently.
hichamboushaba commentedon Jul 12, 2021
For tests, you can always pass your copy of
SavedStateHandlethat you can use for the logic, either by manually creating it using argument keys, or by leveraging thetoBundlemethod, the issue is that like others mentioned, we will depend onBundlefor the tests, so for now, and until the version 2.4.0 ofnavigationcomponent is released, we depend onRobolectricfor the unit tests, and as far as it's temporary, we as a team agreed it's not a blocker.To init
SavedStateHandlefrom ourNavArgsclass, we use the following extensions in tests:patriotic commentedon Aug 3, 2021
Getting
ViewModel constructor should be annotated with @Inject instead of @AssistedInjectwith version 2.38.1Chang-Eric commentedon Aug 5, 2021
@patriotic That's expected.
@HiltViewModelonly works with@Injectright now as this currently open issue is for adding support for@AssistedInject.danielesegato commentedon Oct 8, 2021
@Chang-Eric
I get you have other priorities, but I believe AssistedInject is important.
You mentioned it is trivial to do. I don't think it's trivial to most people using Hilt / Dagger.
May I ask you to provide examples of both of these?
Thanks.
Chang-Eric commentedon Oct 8, 2021
@danielesegato It is half priorities, half that we haven't found the right API for this. We plan to look into the API again after https://issuetracker.google.com/188541057 is resolved as that will solve some of the API problems we're running into. This is lower priority because there are workarounds like using the
SavedStateHandle(which is probably a best practice over AssistedInject because it cannot leak the activity/fragment) and for those things that cannot be put in the bundle, you can use a setter method (obviously not ideal from a code cleanliness perspective, but this case should be rarer). Do you have a case that cannot be addressed with these workarounds?I don't think I said it was trivial unless you're referring to something else that you didn't quote. In fact I thought I said it is not trivial?
danielesegato commentedon Feb 2, 2022
Hi @Chang-Eric do you have any update? the issue has been marked fixed in October of 2021 and this github issue is linked in the official doc on hilt on how to "officially" handle assisted parameters in viewmodels: https://developer.android.com/training/dependency-injection/hilt-jetpack
I still think I should be able to use
@HiltViewModelwith@AssistedInject.the
@AssistedFactorycould than be used withby viewModelFactory(Factory::class).viewModel(...parameters...)orby viewModelFactory(Factory::class).activityViewModel(...parameters...)`there should a lambda somewhere there to avoid re-executing parameter creation if not needed
Thanks.
Chang-Eric commentedon Feb 3, 2022
Sorry, I don't have any real update at this time. We're focusing on KSP migrations for Dagger so that is taking a lot of our time right now so this is still a lower priority. Also, I think that necessary fix is still only in alpha, so we can't depend on it yet since Hilt is stable.
Also, there's still some uncertainty about the direction we want to take the API. ViewModels do not compose together well, so there are some questions about if use cases would be better served with more things like
ActivityRetainedComponentbut for other lifetimes since then Dagger objects in there can compose better and don't have the same leaking risks that@AssistedInjectinto ViewModels presents. That's not to commit one way or another, but just to let you know that even if all the immediate blockers are resolved, this isn't a clear cut case where we just need to find the time to put some code together.Hope that makes sense, and sorry this probably isn't the update you were looking for.
danielesegato commentedon Feb 3, 2022
I see what you mean, I don't really like that you can't inject a ViewModel into another viewModel, makes it harder to create separation of concerns.
But Hilt can't really fix that: something has to be done from the Androidx side with ViewModel replacement / upgrade.
syt0r commentedon Jun 21, 2022
For the first time I've decided to use Hilt instead of Koin and it's very disappointing that support of already existing feature takes so long...
EllenMady commentedon Jul 1, 2022
Man, you saved my life. I'm using compose navigation and I send runtime parameters throw SavedSateHandle like you said. Nothing more is need. Thanks a lot!
sebaslogen commentedon Aug 26, 2022
Now that we can inject multiple ViewModel instances of the same type (#2328) inside the same Composable nav-destination/screen (for example for a View-Pager) I really miss the Assisted Injection to provide unique ids to each ViewModel.
Unfortunately,
SavedStateHandlesolution can't help in this situation because thenavArgsonly work at the Composable nav-destination/screen level, and in this use case the multiple ViewModel ids are only available after the Composable nav-destination/screen is loaded (probably through a screen level ViewModel that loads the ids of the View-Pager ViewModels).mkalitis commentedon Sep 29, 2022
@manuelvicnt This is getting a little confusing given the length of the thread, lets see if I have this correctly.
We are trying to 'inject' the parameters passed during navigation directly to the view model whilst still having the goodness provided to us with the
@HiltViewModelannotation yes?If so then we do not need the hassle of creating a factory etc nor do we need the
@Assistedfor the injection. Further we also do not need to pass in the parameter to the screen/fragment which then passes it to the view model. Hilt already gives us this functionality.I think I might be missing something here??
Thanks,
Martin
svenjacobs commentedon Sep 29, 2022
I'm also looking forward to assisted injection for ViewModels. Given the stance "favour composition over inheritance", I would like to inject some "behavioural" pattern into the constructor of a VM which is only known at instantiation time.
SavedStateHandledoesn't help here because this class cannot be put into aBundle.Tgo1014 commentedon Jan 27, 2023
Really missing this feature, I tried to use the new
ViewModelLifecycleScope {}withhiltViewModel()but unfortunately it crashes. Would help a lot for having different instances in a ViewPager with the same screen working independently.mecoFarid commentedon Feb 13, 2023
You mean like for the next 15 years?
JohannesPtaszyk commentedon Feb 14, 2023
Are there any updates/plans? :)
Would be really handy to get this!
Chang-Eric commentedon Mar 8, 2023
Just updating this. We have a plan for supporting this now, however, it is competing with other higher priority projects for development time like the migration to KSP, so there's no real timeline here yet.
FWIW, now that CreationExtras is supported, you can use the DEFAULT_ARGS_KEY key for setting the default args passed to a ViewModel's SavedStateHandle, so you don't have to only pass things through the Fragment arguments. So something like:
will let you pass a value to be retrieved in the ViewModel through the SavedStateHandle.
mtwalli commentedon Mar 10, 2023
How possibly this solution could be working in unit tests?
mkalitis commentedon Mar 10, 2023
A similar thing can already be done using annotation classes which use the navigation parameters to "assist" the additional parameters into the viewmodel constructor when using HiltViewModel() This gives the same behaviour as doing an @assisted on the view model. This works for something like compose navigation though could be extended to other screen creation/fragment creation.
More info here: https://medium.com/@i.write.code/android-compose-navigation-view-models-and-hilt-c824541bd8e
Example code of doing this with compose navigation: https://github.com/mkalitis/HiltNavigationInjection
Just an thought
nvkleban commentedon Nov 9, 2023
Dagger documentation shows currently not released APIs like
HiltViewModelExtensions.withCreationCallbackis this the solution we were waiting for?ubuntudroid commentedon Nov 9, 2023
Exciting! Hopefully this will also work with
hiltViewModel()in Compose.kuanyingchou commentedon Nov 9, 2023
@nvkleban @ubuntudroid This should be out in the next release, and
hiltViewModel()will be updated next. In the meantime you can give HEAD-SNAPSHOT a try to see if it works for you!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.