Hilt, ViewModels & Assisted Injection

Exploring the new built-in support

Fred Porciúncula
ProAndroidDev

--

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!

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:

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:

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:

Compose

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

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:

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):

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 👋

--

--