Dagger: Hilt ViewModel integration doesn't do field injection

Created on 20 Nov 2020  路  1Comment  路  Source: google/dagger

We're migrating to use the Hilt ViewModel integration and are hitting a possible edge case. We have a BasePresenter class that uses field injection to avoid our presenters having to pass through a bunch of args into the super constructor:

open class BasePresenter {
  @Inject lateinit var foo: Foo
}

class SomeScreenPresenter @Inject constructor(val dependency: Dependency) : BasePresenter() {...}

When we inject these using vanilla dagger/hilt, the SomeScreenPresenter_Factory does field injection in its get() method:

@Override
public SomeScreenPresenter get() {
  SomeScreenPresenter instance = newInstance(dependencyProvider.get())
  BasePresenter_MembersInjector.injectFoo(instance, fooProvider.get());
  return instance;
}

But when migrating to use the Hilt ViewModel integration, the generated ViewModelAssistedFactory doesn't do any field injection:

@Override
@NonNull
public SomeScreenPresenter create(SavedStateHandle arg0) {
  return new SomeScreenPresenter(dependency.get());
}

This may be a weird use-case, but thought I'd see if there's a technical reason this isn't supported, or if maybe it was just an omission.

Thanks!

P2 hilt bug

Most helpful comment

This is not a weird case and its actually very useful for libraries for the reason you mentioned (not having to pass deps through a bunch of args into constructor). This happens because @ViewModelInject has its own assisted factory that is created via an @Provides methods and those don't do member injection.

We are in the process of supporting this along with a ViewModelComponent but it will require an API change, the idea is to have developers use familiar Dagger APIs along with an additional Hilt one, something like this:

@HiltViewModel // Name TBD
class MyViewModel @Inject constructor(SavedStateHandle handle, FooRepository foo):  BaseViewModel() {
  // ...
}

Under the hood Dagger will generate the factory that will do member injection instead of having a dedicate one for ViewModels that replicate's Dagger. You won't need to annotate SavedStateHandle with an assisted annotation since it'll be a @BindInstance in the ViewModelComponent and we can have additional checks to disallow accidental injections of the ViewModel (i.e. @Inject vm: MyViewModel).

WorkManager's Workers will have a similar change.

I'll leave this issue open until this is supported.

>All comments

This is not a weird case and its actually very useful for libraries for the reason you mentioned (not having to pass deps through a bunch of args into constructor). This happens because @ViewModelInject has its own assisted factory that is created via an @Provides methods and those don't do member injection.

We are in the process of supporting this along with a ViewModelComponent but it will require an API change, the idea is to have developers use familiar Dagger APIs along with an additional Hilt one, something like this:

@HiltViewModel // Name TBD
class MyViewModel @Inject constructor(SavedStateHandle handle, FooRepository foo):  BaseViewModel() {
  // ...
}

Under the hood Dagger will generate the factory that will do member injection instead of having a dedicate one for ViewModels that replicate's Dagger. You won't need to annotate SavedStateHandle with an assisted annotation since it'll be a @BindInstance in the ViewModelComponent and we can have additional checks to disallow accidental injections of the ViewModel (i.e. @Inject vm: MyViewModel).

WorkManager's Workers will have a similar change.

I'll leave this issue open until this is supported.

Was this page helpful?
0 / 5 - 0 ratings