Search code examples
androiddagger-2

Dagger2: Dependencies vs Modules


The implementation below gives and error suggesting that DataStore cannot be provided. I assumed that the UserPreferencesModule would provide this when the UserPreferencesComponent was included as a dependency for CreateAccountComponent. The fix for this for me was to omit the component and include the module directly.

@Module
class UserPreferencesModule {

    @Provides
    @Singleton
    fun provideUserPreferencesDataStore(context: Context): DataStore<UserPreferences> = context.userPreferences

    @Provides
    @Singleton
    fun provideUserPreferencesManager(userPreferences: DataStore<UserPreferences>): UserPreferencesManager = UserPreferencesManager(userPreferences)

}


@Component(modules = [UserPreferencesModule::class], dependencies = [CommonProvider::class])
interface UserPreferencesComponent

@Component(
    modules = [
        CreateAccountModule::class,
    ],
    dependencies = [
        AuthDomainComponent::class,
        UserDomainComponent::class,
        UserPreferencesComponent::class
        CommonProvider::class
    ]
)
@Singleton
interface CreateAccountComponent {
    val createAccountViewModel: CreateAccountViewModel
}

I am looking for an explanation why my assumption is wrong


Solution

  • Dagger component dependencies have a very specific behavior: every "provision method" (possibly-qualified zero-arg method) becomes available within the graph of the component declaring the dependency. The types you list in dependencies can be arbitrary interfaces and do not have to be generated or annotated with Dagger annotations, though they're design to allow Dagger-generated components to work easily this way.

    Because they're both top-level @Components, the code-generation step for UserPreferencesComponent and CreateAccountComponent will happen separately, with UserPreferencesComponent unaware of the dependency CreateAccountComponent has on it. Dagger removes unused bindings, and because the UserPreferencesComponent interface does not declare any provision or injection methods, then the generated implementation DaggerUserPreferencesComponent will be effectively empty: there'd be nothing for CreateAccountComponent to call.

    To use component dependencies the way you're trying to, you will need UserPreferencesComponent to declare a method like fun getUserPreferencesDataStore(): DataStore<UserPreferences>. The name doesn't matter, but the signature of the function is very important, as that will tell any Dagger component that expresses a dependency on UserPreferencesComponent that it will have access to a DataStore<UserPreferences> as if it were specified in a module's @Provides method. Declaring that method on your @Component simultaneously makes it an entry point into UserPreferencesComponent, so then Dagger has a reason to implement that method by invoking UserPreferencesModule.provideUserPreferencesDataStore(). Note that you will have to supply an implementation of UserPreferencesComponent (probably a DaggerUserPreferencesComponent you instantiate) in your CreateAccountComponent.Builder or CreateAccountComponent.Factory since Dagger can't call a no-arg new on UserPreferencesComponent.

    If you don't need the decoupling provided by component dependencies, you can just reuse Modules as you have. You can also consider subcomponents for encapsulation, but if you do that you would still need to explicitly make the subcomponent's selected bindings available explicitly in the parent component through @Provides methods.

    As a side note, you may need to use @JvmSuppressWildcards when injecting generics like DataStore<UserPreferences>.