Search code examples
androiddagger-2

Dagger/MissingBinding error when creating a subcomponent


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?


Solution

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