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
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 @Component
s, 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>
.