I am experimenting with Dagger subcomponents and got an error that I am having some difficulties understanding.
So basically I have a Subcomponent
and a Component
.
// FeatureComponent.kt, @Subcomponent
@Scope
annotation class Feature
@Subcomponent(modules = [FeatureModule::class])
@Feature
interface FeatureComponent {
fun inject(loginActivity: LoginActivity)
@Subcomponent.Builder
interface Builder {
fun build(): FeatureComponent
}
}
@Module
class FeatureModule {
@Provides
@Feature
fun provideFeatureStorage(): FeatureStorage {
return FeatureStorage()
}
}
@Feature
class FeatureStorage
and the Component
:
@Component(modules = [LoginModule::class])
@Singleton
interface LoginComponent {
fun loginComponent(): LoginComponent
fun inject(loginActivity: LoginActivity)
@Component.Builder
interface Builder {
fun build(): LoginComponent
}
fun featureComponent(): FeatureComponent.Builder // declare the subcomponent
}
@Module(subcomponents = [FeatureComponent::class])
class LoginModule {
@Provides
@Singleton
fun provideSingletonInstance(): Storage {
return Storage()
}
@Provides
fun provideNotSingletonInstance(): UserSession {
return UserSession()
}
}
class Storage
class UserSession
And I am trying to inject the FeatureStorage
, which is provided by the @Subcomponent, in an activity like this:
class LoginActivity : AppCompatActivity() {
@Inject
lateinit var featureStorage: FeatureStorage
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
loginComponent.inject(this)
loginComponent.featureComponent().build().inject(this)
}
}
But Dagger compilation fails with:
[Dagger/MissingBinding] com.vgrec.daggerkurs.components.FeatureStorage cannot be provided without an @Inject constructor or an @Provides-annotated method.
A binding with matching key exists in component: com.vgrec.daggerkurs.components.FeatureComponent com.vgrec.daggerkurs.components.FeatureStorage is injected at com.vgrec.daggerkurs.components.LoginActivity.featureStorage com.vgrec.daggerkurs.components.LoginActivity is injected at com.vgrec.daggerkurs.components.LoginComponent.inject(com.vgrec.daggerkurs.components.LoginActivity)
This part: FeatureStorage cannot be provided without an @Inject constructor or an @Provides-annotated method
seems strange, because FeatureStorage
IS provided using the @Provides
annotation.
Any ideas what could potentially be wrong in my setup?
Dagger can't partially inject a class; FeatureComponent knows how to inject FeatureStorage, but you've instructed LoginComponent to try to inject LoginActivity, so LoginComponent is going to unsuccessfully search for a @Provides FeatureStorage
method.
Since you can create a FeatureComponent as a subcomponent of LoginComponent, there should be no bindings that LoginComponent can inject that FeatureComponent cannot. Therefore, I'd drop the inject
method from LoginComponent and allow your created FeatureComponent to be the sole class that injects LoginActivity.
As an alternative, you can drop the @Inject
from featureStorage
in LoginActivity and instead expose a featureStorage()
method on FeatureComponent. In that case, rather than instantiating FeatureStorage via inject(LoginActivity)
, you can save and instantiate it yourself.
However, in all cases I have to admit that I find you graph confusing, and I expect other readers would too: The relationship between FeatureComponent and LoginComponent is unclear, and I wouldn't know why those lifecycles would be separate or really what kind of lifecycle FeatureComponent would be expected to have in the first place. In Android, it is much more common to set up a Dagger graph structure that matches Application and Activity lifecycles, and systems like Hilt make those components built-in scopes in the framework.