Blog Infos
Author
Published
Topics
, , , ,
Published
Photo by Ricardo Cruz on Unsplash

I’ve had a draft about Hilt and assisted injection for years. I’ve never published it mostly because I wasn’t happy with the setup needed in order to achieve assisted injection with it, and because I was really hopeful proper support would land soon.

It took more than 3 years, but Hilt ViewModel assisted injection finally shipped on Dagger 2.49 and this popular issue has finally been closed!

https://github.com/google/dagger/issues/2287?source=post_page—–aca2d6ee581d——————————–

My draft was long, but thankfully this post will be pretty short. You can also read all about it in the docs, except how it works in Compose, which will be covered here.

Assisted injection was introduced in Dagger 2.31. This article assumes you’re familiar with it, otherwise I would recommend reading the (short!) documentation about it.

Let’s start with a humble ViewModel:

@HiltViewModel
class MyViewModel @Inject constructor(
private val savedStateHandle: SavedStateHandle,
// other dependencies
) : ViewModel() {
...
}
view raw MyViewModel.kt hosted with ❤ by GitHub

I’m adding the SavedStateHandle there as a reminder that it can be added as a regular dependency since it’s a binding from the ViewModelComponent. We can actually use it to carry assisted arguments to our ViewModel, but there’s no need to do this anymore now that assisted injection is properly supported, and so it’ll be omitted from here on.

Now let’s say our ViewModel needs to receive a runtime argument (i.e. intent extras, navigation arguments, etc). This is how it should look like:

@HiltViewModel(assistedFactory = MyViewModel.Factory::class)
class MyViewModel @AssistedInject constructor(
@Assisted val runtimeArg: String,
// other dependencies
) : ViewModel() {
@AssistedFactory interface Factory {
fun create(runtimeArg: String): MyViewModel
}
...
}
view raw MyViewModel.kt hosted with ❤ by GitHub

Everything here follows regular assisted injection in Dagger:

  • @Inject turns into @AssistedInject
  • The assisted argument is annotated with @Assisted
  • A factory annotated with @AssistedFactory is introduced

The interesting part is that @HiltViewModel remains and we must pass the factory as an argument. And that’s it! With this in place, this ViewModel can be created in an activity or a fragment like this:

private val viewModel by viewModels<MyViewModel>(
extrasProducer = {
defaultViewModelCreationExtras.withCreationCallback<MyViewModel.Factory> { factory ->
factory.create(runtimeArg = "abc")
}
}
)
view raw MyActivity.kt hosted with ❤ by GitHub
Compose

If you’re using hilt-navigation-composeHilt 1.2.0 added assisted injection support in hiltViewModel(). Creating that same ViewModel is even easier here:

val viewModel = hiltViewModel<MyViewModel, MyViewModel.Factory>(
creationCallback = { factory -> factory.create(runtimeArg = "abc") }
)
view raw MyComposable.kt hosted with ❤ by GitHub

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Migrating to Jetpack Compose – an interop love story

Most of you are familiar with Jetpack Compose and its benefits. If you’re able to start anew and create a Compose-only app, you’re on the right track. But this talk might not be for you…
Watch Video

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer for Jetpack Compose
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engin ...
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer f ...
Google

Jobs

val viewModel = hiltViewModel<MyViewModel, MyViewModel.Factory>(
creationCallback = { factory -> factory.create(runtimeArg = "abc") }
)
view raw MyComposable.kt hosted with ❤ by GitHub

Otherwise, you can use viewModel() from lifecycle-viewmodel-compose. It hasn’t been updated but it already receives an extras argument we can use for this. The API is not great for this case, though, and we basically need to write the same code behind hiltViewModel() ourselves:

val creationCallback: (MyViewModel.Factory) -> MyViewModel = { factory ->
factory.create(runtimeArg = "abc")
}
val viewModel = viewModel<MyViewModel>(
extras = requireNotNull(LocalViewModelStoreOwner.current).run {
if (this is HasDefaultViewModelProviderFactory) {
this.defaultViewModelCreationExtras.withCreationCallback(creationCallback)
} else {
CreationExtras.Empty.withCreationCallback(creationCallback)
}
}
)
view raw MyComposable.kt hosted with ❤ by GitHub

Or we can simplify it if we know our viewModelStoreOwner is of type HasDefaultViewModelProviderFactory (it will be for the usual cases with activities, fragments, NavBackStackEntry, etc):

val viewModelStoreOwner = requireNotNull(
LocalViewModelStoreOwner.current as? HasDefaultViewModelProviderFactory
)
val viewModel = viewModel<MyViewModel>(
extras = viewModelStoreOwner
.defaultViewModelCreationExtras
.withCreationCallback<MyViewModel.Factory> { factory ->
factory.create(runtimeArg = "abc")
}
)
view raw MyComposable.kt hosted with ❤ by GitHub

I’ve filed an issue about this here, but then I was told hiltViewModel() should always be the one used if we’re using Hilt, regardless if the navigation library is being used or not. That would mean potentially bringing (transitively) the navigation library unnecessarily, though, so I’ve opened a separate issue about this here — keep an eye on it if you’re interested in what comes next!

I’m on Twitter and on Mastodon, feel free to reach out if I missed anything or if you have any questions 👋

This article is previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
Using annotations in Kotlin has some nuances that are useful to know
READ MORE
blog
One of the latest trends in UI design is blurring the background content behind the foreground elements. This creates a sense of depth, transparency, and focus,…
READ MORE
blog
Now that Android Studio Iguana is out and stable, I wanted to write about…
READ MORE
blog
The ModalBottomSheet in Jetpack Compose is easy to use, it just pops up at…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu