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.
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!