How to use Hilt to inject a safe-args argument into a viewmodel?
U

3

23

I have found a similar question here. At the time of writing this question there is only this answer avaliable, which does not provide any help to me, and I believe also to the person who asked the question.

I checked the repo which is linked in the answer and it "solves" the problem by creating an init method in the viewmodel and calling it in the Activity/Fragment.

Since the viewmodel has already been injected, this solution does not seem like ideal to me, and I would like to know if there are other options available when using hilt.

Unimpeachable answered 1/5, 2021 at 19:54 Comment(1)
Keep an eye on this github issue that tracks this feature request. Here's a workaround that is in alpha I think twitter.com/manuelvicnt/status/1273699171078475780 gist.github.com/manuelvicnt/437668cda3a891d347e134b1de29aee1 You basically have to use @AssistedInject to pass in runtime arguments to the viewmodelYentai
K
67

As per this comment and the release of AndroidX Hilt 1.0.0-alpha03, Hilt has supported ViewModels that take a SavedStateHandle as a parameter (right alongside your other injected parameters).

This SavedStateHandle is automatically, without you doing anything, populated with the arguments passed to your fragment (i.e., the same arguments you get from requireArguments() and the same arguments that are read by Safe Args).

Therefore in your ViewModel's constructor, you can immediately access those arguments from the SavedStateHandle, without having to do any manual passing of arguments to your ViewModel.

@HiltViewModel
class MainViewModel @Inject constructor(
    val userDataManager: UserDataManager,
    savedStateHandle: SavedStateHandle
) : ViewModel() {
    init {
        // Use the same argName as in your navigation graph
        val yourArgument: String = savedStateHandle["argName"]
        // Now use that argument to load your data, etc.
    }
}

The feature request for Safe Args integration with SavedStateHandle is already fixed and will be part of the upcoming Navigation 2.4.0-alpha01 release. Once that is released, you'd be able to do something like MainFragmentArgs.fromSavedStateHandle(savedStateHandle) to get the same Args class you're currently able to get from by navArgs() within your ViewModel.

Komsomolsk answered 2/5, 2021 at 1:0 Comment(12)
should'nt "yourArgument" be nullable? or should we just use "savedStateHandle["argName"]!!"Abaft
You should take over writing the Android Developer Documentation! This should have been mentioned in the docs! Thanks! :)Neuroma
This function is not available to me. MainFragmentArgs.fromSavedStateHandle(savedStateHandle) using the 2.4.0 Nav and the 2.35.1 com.google.dagger:hilt-android. I see there just a hilt 1.0.0 now did they migrate some of/all of this there?Nejd
@Nejd - what version of Android Studio are you using? You have to be using Arctic Fox Canary 10 or higher.Komsomolsk
I tried this but the savedStateHandle is empty, there is no arguments thereGriqua
Had to read a few more posts before knowing how to use this - with the codes above, from the fragment the ViewModel is set up using val viewModel: MainViewModel by viewModels()Gratulant
@Komsomolsk What about Hilt viewModel with SavedStateHandle in jetpack compose. What will be the solution?Winna
@GuruKarthiR - the hiltViewModel() method already supports SavedStateHandle.Komsomolsk
Don't forget to update versions of the navigation libraries that are in the top level gradle file and module's gradle file to the same value! stackoverflow.com/a/68331336Geordie
@Komsomolsk this works only when viewModel is initialized at first. But what if I want to get the arguments after initialized at any point of time?Simaroubaceous
@JeevanRupacha - you can, of course, make either the SavedStateHandle instance or the Args class Navigation can generate for you into a val in your ViewModel and access it at any time.Komsomolsk
are there any examples how to use this new api? should we inject the Args instance directly into viewModel constructor? should we convert state handle into the Args to get type safe params?Tapes
E
0

For anyone facing the same challenge and using Hilt, the trick is to initialize the SavedStateHandle in the viewModel constructor i.e.

savedStateHandle: SavedStateHandle = SavedStateHandle()

Then you can access the passed argument like:

val id: String? = savedStateHandle["id"]

or

val id = savedStateHandle.get<String?>("id")
Etheline answered 25/11, 2022 at 16:7 Comment(0)
V
0

In case you are in the same situation like me, I've solved the problem using custom extras:

@Composable
inline fun <reified T : ViewModel> savedStateHandleViewModel(extras: Bundle): T {
    val viewModelStoreOwner = LocalViewModelStoreOwner.current
    val initialExtras = if (viewModelStoreOwner is HasDefaultViewModelProviderFactory) {
        viewModelStoreOwner.defaultViewModelCreationExtras
    } else {
        CreationExtras.Empty
    }
    val customExtras = MutableCreationExtras(initialExtras)
    customExtras[DEFAULT_ARGS_KEY] = extras
    return viewModel(extras = customExtras)
}

Using this function you can pass extras to your ViewModel and get it directly from SavedStateHandle. In my case I have parent fragment and I wanted to get rid of two child fragments, migrating them to single compose view, preserving same screen structure.

Virg answered 27/2, 2024 at 21:28 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.