'Android: Dagger2 binding with matching key exists in components, existing class that didn't previously need @Provides-annotated method now needs one

We are attempting to develop a new Activity for an existing Fragment, the compiler is throwing errors for an existing FragmentComponentBuilder class that other Dagger modules have had no issues with utilizing in the past, but because of the code we introduced, is now failing to build. The error raised for the FragmentComponentBuilder details not being able to be provided because of a missing @Provides-annotated, also binding with matching key existing in components related to the new Activity.

Here is the error:

    /app/application/AppComponent.java:8: error: [Dagger/MissingBinding] java.util.Map<java.lang.Class<? extends androidx.fragment.app.Fragment>,javax.inject.Provider<app.dagger.fragment.FragmentComponentBuilder<?,?>>> cannot be provided without an @Provides-annotated method.
public abstract interface AppComponent {
                ^
  A binding with matching key exists in component: app.CaComponent
      java.util.Map<java.lang.Class<? extends androidx.fragment.app.Fragment>,javax.inject.Provider<app.dagger.fragment.FragmentComponentBuilder<?,?>>> is injected at
          app.DevicesActivity.fragmentComponentBuilders
      app.DevicesActivity is injected at
          app.application.AppComponent.inject(app.DevicesActivity)
  It is also requested at:
      app.DevicesActivity.fragmentComponentBuilders
  The following other entry points also depend on it:
      dagger.MembersInjector.injectMembers(T) [app.application.AppComponent → app.DevicesActivityComponent]

An important part about the new DevicesActivity we're developing, is that the associated DevicesFragment logic needs to remain relatively unchanged, because the core logic that handles the presentation of this screen is a Fragment -> Fragment interaction. The DevicesActivity handles presentation from another Activity, and contains restricted view properties as opposed to the Fragment -> Fragment presentation.

The newly created DevicesActivity class:

    class DevicesActivity: InjectableActivity(), CaView, HasFragmentSubComponentBuilders{
        @Inject
        lateinit var fragmentComponentBuilders: Map<Class<out Fragment>, @JvmSuppressWildcards Provider<FragmentComponentBuilder<*, *>>>
    
        var caModel: CaModel? = null
        private val devicesFragment = DevicesFragment.newInstance()
        companion object {
            val kCaModel = "CA_MODEL_KEY"
            fun create(context: Context, caModel: CaModel) : Intent {
                val intent = Intent(context, DevicesActivity::class.java)
                intent.putExtra(kCaModel, caModel)
                return intent
            }
        }
    
        override fun injectActivity(hasActivitySubComponentBuilders: HasActivitySubcomponentBuilders): ActivityComponent<DevicesActivity> {
            val builder = hasActivitySubComponentBuilders.getBuilder(DevicesActivity::class.java)
            val componentBuilder = builder as DevicesActivityComponent.Builder
            val component = componentBuilder.activityModule(DevicesActivityModule())?.build()
            component!!.injectMembers(this)
            return component
        }
    
        override fun onStart(){
            super.onStart()
            supportFragmentManager.beginTransaction().replace(android.R.id.content, devicesFragment).commit()
        }
    
        override fun getBuilder(fragmentClass: Class<out Fragment>): FragmentComponentBuilder<*, *> =
                fragmentComponentBuilders.getValue(fragmentClass).get()
    
        override fun onLoggedOut(wasLoggedOutDueToInactivity: Boolean, wasLoggedOutDueToBadCreds: Boolean) {
        }
    }

DevicesActivityComponent for new Activity:

@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class DevicesScope

@DevicesScope
@Subcomponent(modules = [DevicesActivityModule::class])
interface DevicesActivityComponent : ActivityComponent<DevicesActivity> {

    @Subcomponent.Builder
    interface  Builder : ActivityComponentBuilder<DevicesActivityModule, DevicesActivityComponent>
}

The DevicesActivityModule for new Activity:

@Module
class DevicesActivityModule : ActivityModule<DevicesActivity>() {
    @Provides
    fun presenter(caSubject: CaSubject, interactor: ST)
            : DevicesPresenter = DevicesPresenter(caSubject, interactor)

    @Provides
    fun interactor(schedulerProvider: SchedulerProvider) : ST =
           ST(schedulerProvider)
}

The associated DevicesFragment class:

class DevicesFragment : MvpScopedFragment<DevicesView, DevicesPresenter>(), DevicesView {


    lateinit var binding : DevicesBinding
    private lateinit var adapter : DevicesAdapter
    lateinit var devicesDialog: AlertDialog

    companion object {
        fun newInstance() : DevicesFragment = DevicesFragment()
    }

    override fun inject(hasFragmentSubComponentBuilders: HasFragmentSubComponentBuilders): DevicesPresenter {
        val builder = hasFragmentSubComponentBuilders.getBuilder(DevicesFragment::class.java)
        val componentBuilder = builder as DevicesFragmentComponent.Builder
        val component = componentBuilder.module(DevicesFragmentModule(this)).build()
        return component.presenter()
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        binding = DataBindingUtil.inflate(inflater, R.layout.respec_devices, container, false)
        return binding.root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        presenter.caModel.screen = CaScreen.DEVICES
        presenter.save()
        binding.header.caModel = presenter.caModel
        binding.caModel = presenter.caModel
        presenter.setDelegate()
        if(presenter.pm.hasProfile()) {
            presenter.profile = presenter.pm.getProfile()!!
        }
    }

The CaComponent class that the error output references as a class that contains a binding with a matching key to other components:

@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class CaScope

@CaScope
@Subcomponent(modules = [CaModule::class, FragmentBindingModule::class])
interface CaComponent : ActivityComponent<CaActivity> {

    fun presenter() : CaPresenter

    @Subcomponent.Builder
    interface Builder : ActivityComponentBuilder<CaModule, CaComponent>

}

FragmentComponentBuilder that has remained unchanged:

interface FragmentComponentBuilder<M : FragmentModule<*>, C : FragmentComponent<*>> {

    fun module(activiyModule: M) : FragmentComponentBuilder<M, C>

    fun build() : C

}

ActivityComponentBuilder that has remained unchanged:

interface ActivityComponentBuilder<M : ActivityModule<*>, C : ActivityComponent<*>> {

    fun activityModule(activityModule: M): ActivityComponentBuilder<M, C>?

    fun build(): C

}

AppComponent that contains a newly created inject method that takes the DevicesActivity as a parameter:

@Singleton
@Component(modules = [AppModule::class, ActivityBindingModule::class])
interface AppComponent {

    fun inject(app: App)

    fun inject(printingService: PrintingService)

    fun context() : Context

    fun preferenceFactory() : PreferenceFactory

    fun inject(devicesActivity: DevicesActivity)
}

Please let me know if you need to see any additional files not included here that would assist in troubleshooting. Thank you for whatever help you're willing to give!



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source