Search code examples
androiddagger-2

Dagger2 - Multi Module - A binding with matching key exists in component


Background

I am trying to use dagger in a multi module setup. One of my aim is to reduce the number of components being used. So basically aiming for 1 component per feature module.

Setup core->app->feature

Problem

Dagger fails with the exception A binding with matching key exists in component: which refers to that I have bound a dependency somewhere in my entire object graph but it cannot be reached.

But for my scenario I am creating the sub-component in my activity and calling inject to make sure the component has the access to my activity. This atleast in my understanding should be accessible but it's still not able to provide the dependency of my viewmodel.

Here is the sample/multi-module in case someone wants to try out.

Stacktrace

/Users/feature1/build/**/FeatureComponent.java:8: error: [Dagger/MissingBinding]
com.**.FeatureActivity cannot be provided without an @Inject constructor or an @Provides-annotated 
method. This type supports members injection but cannot be implicitly provided.
public abstract interface FeatureComponent {
                ^
  A binding with matching key exists in component: com.**.FeatureComponent
      com.**.FeatureActivity is injected at
          com.**.FeatureModule.provideVM(activity)
      com.**.FeatureViewModel is injected at
          com.**.FeatureActivity.vm
      com.**.FeatureActivity is injected at
          com.**.FeatureComponent.inject(com.**.FeatureActivity)

AppComponent

@AppScope
@Component(dependencies = [CoreComponent::class])
interface AppComponent {

    fun inject(app: MainApp)

    @Component.Factory
    interface Factory {
        fun create(
            coreComponent: CoreComponent
        ): AppComponent
    }
}

CoreComponent

@Singleton
@Component
interface CoreComponent {

    fun providerContext(): Context

    @Component.Factory
    interface Factory {
        fun create(
            @BindsInstance applicationContext: Context
        ): CoreComponent
    }
}

FeatureComponent

@Component(
    modules = [FeatureModule::class],
    dependencies = [CoreComponent::class]
)
@FeatureScope
interface FeatureComponent {

    // Planning to use this component as a target dependency for the module.
    fun inject(activity: FeatureActivity)
}

Feature Module

@Module
class FeatureModule {

    @Provides
    fun provideVM(activity: FeatureActivity): FeatureViewModel {
        val vm by activity.scopedComponent {
            FeatureViewModel()
        }
        return vm
    }
}

Feature VM

class FeatureViewModel @Inject constructor(): ViewModel() 

Solution

  • Since I'm using activity to provide my viewModel I will have to use @BindsInstance to bind the instance of any activity or fragment that I want to inject.

    In short if I change my feature component to the following code it starts to work where I bind the instance of the activity at the creation of the component.

    PS: If anyone knows a better to inject the fragment at later stage with just using one component, please feel free to improve this answer.

    FeatureComponent

    @Component(
        modules = [FeatureModule::class],
        dependencies = [CoreComponent::class]
    )
    @FeatureScope
    interface FeatureComponent {
    
        fun inject(activity: FeatureActivity)
    
        @Component.Factory
        interface Factory {
            fun create(
                @BindsInstance applicationContext: FeatureActivity,
                coreComponent: CoreComponent,
            ): FeatureComponent
        }
    }