• After 15+ years, we've made a big change: Android Forums is now Early Bird Club. Learn more here.

Dagger 2: binding with matching key exists in components

MasonM

Lurker
Mar 23, 2022
3
1
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:

Code:
/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:

Code:
class DevicesActivity: InjectableActivity(), CaView, HasFragmentSubComponentBuilders{
       [USER=264617]@INJECT[/USER]
       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
Code:
@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:

Code:
@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:

Code:
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
Code:
@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:

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

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

   fun build() : C

}

ActivityComponentBuilder that has remained unchanged:

Code:
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

Code:
@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!
 
Last edited:

BEST TECH IN 2023

We've been tracking upcoming products and ranking the best tech since 2007. Thanks for trusting our opinion: we get rewarded through affiliate links that earn us a commission and we invite you to learn more about us.

Smartphones