Search code examples
androiddependency-injectiondagger-2dagger-hilt

Dagger Hilt error injecting ActivityContext


I'm injecting with Dagger-Hilt a class with a dependency on @ActivityContext in a ViewModel, this module is installed in ActivityComponent and scoped to activity and it's throwing me an error whenever I try to compile. For your information I have other modules with ActivityRetainedComponent and SingletonComponent injecting @ApplicationContext.

Now I'm trying to figure out what does this error mean.

error: [Dagger/MissingBinding] com.rober.fileshortcut_whereismyfile.utils.PermissionsUtils cannot be provided without an @Inject constructor or an @Provides-annotated method.
  public abstract static class SingletonC implements App_GeneratedInjector,
                         ^
      com.rober.fileshortcut_whereismyfile.utils.PermissionsUtils is injected at
          com.rober.fileshortcut_whereismyfile.ui.fragments.filefragment.FileViewModel(�, permissionsUtils, �)
      com.rober.fileshortcut_whereismyfile.ui.fragments.filefragment.FileViewModel is injected at
          com.rober.fileshortcut_whereismyfile.ui.fragments.filefragment.FileViewModel_HiltModules.BindsModule.binds(vm)
      @dagger.hilt.android.internal.lifecycle.HiltViewModelMap java.util.Map<java.lang.String,javax.inject.Provider<androidx.lifecycle.ViewModel>> is requested at
          dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.ViewModelFactoriesEntryPoint.getHiltViewModelMap() [com.rober.fileshortcut_whereismyfile.App_HiltComponents.SingletonC ? com.rober.fileshortcut_whereismyfile.App_HiltComponents.ActivityRetainedC ? com.rober.fileshortcut_whereismyfile.App_HiltComponents.ViewModelC]

Here's the code (I don't think there's anything wrong here)

@Module
@InstallIn(ActivityComponent::class)
object PermissionModule {

    @ExperimentalCoroutinesApi
    @Provides
    @ActivityScoped
    fun providePermissionsHelper(
        @ActivityContext context: Context,
    ) = PermissionsUtils(context)
}
@HiltViewModel
@ExperimentalCoroutinesApi
class FileViewModel @Inject constructor(
    private val class1: Class1,
    private val class2: Class2,
    private val class3: Class3,
    private val permissionsUtils: PermissionsUtils //Here's the one I'm injecting
)

My guesses: It's telling me that I can't inject in ActivityComponent because FileViewModel is injected in ActivityRetainedComponent/SingletonComponent/ViewModelComponent, because other dependencies provided to viewmodel are installed in this component?

Question: What is really going here? What am I missing and is there any solution for using ActivityComponent? I really need the @ActivityContext for that class!

Edit: This works when I change to @InstallIn(ActivityRetainedComponent::class) and the context to @ApplicationContext context: Context, note that it works with SingletonComponent/ViewModelComponent/ActivityRetainedComponent, which makes sense. But still I don't know how to make it work the @ActivityComponent

Edit 2: I've come to the conclusion that is not possible since you are installing in ViewModel and the components that work with ViewModel are Singleton/ActivityRetained/ViewModel, so there's no way to inject ActivityContext in a ViewModel since it requires the component @ActivityComponent and this is 1 level below of ViewModelComponent.

Source: https://dagger.dev/hilt/components#fn:1


Solution

  • so there's no way to inject ActivityContext in a ViewModel since it requires the component @ActivityComponent and this is 1 level below of ViewModelComponent.

    You got it.

    Think of Dagger/Hilt components (and scopes) in terms of lifecycle. In Android, a ViewModel has longer lifecycle than its containing Activity or Fragment. That's the whole point of the class, it's just the name that's unfortunate. It may help to think of those as RetainedInstance or AnObjectThatSurvivesActivityConfigurationChanges. Yeah, these aren't as catchy as ViewModel.

    If you injected a short-lived object (the Activity) into a long-lived one (the ViewModel), the latter would keep a reference to the former. That's a memory leak - the Activity and everything it contains (e.g. views, bitmaps) stays in memory even though it's no longer used. A couple of those and you risk an OutOfMemoryError.

    However, the other way around - i.e. injecting a long-lived object into a short-lived one - is safe. That's why you got it working with Singleton/ActivityRetained/ViewModel components.

    Hilt's component hierarchy makes it harder for you to create memory leaks. And all of that happens at compile-time, which gives you faster feedback as opposed to runtime-based solutions. Much better than your users finding out by crashing!