dagger error binding with matching key exists in component - android

Dagger 2
/di/AppComponent.java:19: error: [Dagger/MissingBinding] ProductListFragment 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 AppComponent {
^
A binding with matching key exists in component: MainFragmentProvider_BindProductListFragment.ProductListFragmentSubcomponent
ProductListFragment is injected at
productlist.ProductListModule.provideChildFragmentManager(productListFragment)
androidx.fragment.app.FragmentManager is injected at
productlist.adapter.NewProductListPagerAdapter(…, fragmentManager)
productlist.adapter.NewProductListPagerAdapter is injected at
productlist.ProductListV2Fragment.mPagerAdapter
productlist.ProductListV2Fragment is injected at
dagger.android.AndroidInjector.inject(T) [di.AppComponent → di.BuilderModule_BindMainActivity.MainActivitySubcomponent → MainFragmentProvider_BindProductListV2Fragment.ProductListV2FragmentSubcomponent]
I have the following module with these 2 fragments here:
#Module
abstract class FragmentProvider {
#PerFragment
#ContributesAndroidInjector(modules = [ProductListModule::class])
abstract fun bindProductListV2Fragment(): ProductListV2Fragment
#PerFragment
#ContributesAndroidInjector(modules = [ProductListModule::class])
abstract fun bindProductListFragment(): ProductListFragment
}
In my ProductListModule I have the following:
#Module
class ProductListModule {
#Provides
fun provideChildFragmentManager(productListFragment: ProductListFragment) =
productListFragment.childFragmentManager
}
The fragmentManager will be injected into the following class:
class NewProductListPagerAdapter #Inject constructor(
#ActivityContext private val context: Context,
fragmentManager: FragmentManager
) { ..... }
And both fragments will inject this NewProductListPagerAdapter in them.
class ProductListV2Fragment : BaseProductListFragment() {
#Inject
lateinit var mPagerAdapter: NewProductListPagerAdapter
}
class ProductListFragment : BaseProductListFragment() {
#Inject
lateinit var mPagerAdapter: NewProductListPagerAdapter
}
My AppComponent:
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
ProductModule::class,
BaseAppModule::class
]
)
interface AppComponent {
#Component.Factory
interface Factory {
fun create(
#BindsInstance application: Application,
): AppComponent
}
fun inject(tsApplication: TsApplication)
}
==== UPDATE ====
#Module
abstract class FragmentProvider {
#PerFragment
#ContributesAndroidInjector(modules = [ProductListModule::class, ProductListBindingModule::class])
abstract fun bindProductListFragment(): ProductListFragment
#PerFragment
#ContributesAndroidInjector(modules = [ProductListModule::class, ProductListV2BindingModule::class])
abstract fun bindProductListV2Fragment(): ProductListV2Fragment
}
#Module
class ProductListModule {
#Provides
fun provideChildFragmentManager(productListFragment: Fragment): FragmentManager =
productListFragment.childFragmentManager
}
#Module
abstract class ProductListBindingModule {
#Binds
abstract fun bindFragment(fragment: ProductListFragment): Fragment
}
#Module
abstract class ProductListV2BindingModule {
#Binds
abstract fun bindFragment(fragment: ProductListV2Fragment): Fragment
}
The stack trace is below which is indicating that the Fragment is bound multiple times. The reason being is because each fragment injects the following dependency:
#Inject
lateinit var mPagerAdapter: NewProductListPagerAdapter
And in the NewProductListPagerAdapter constructor:
class NewProductListPagerAdapter #Inject constructor(
#ActivityContext private val context: Context,
fragmentManager: FragmentManager
)
Which needs to provide the FragmentManager;
app/build/tmp/kapt3/stubs/uatDebug/tech/central/tops/di/AppComponent.java:22: error: [Dagger/DuplicateBindings] androidx.fragment.app.Fragment is bound multiple times:
public abstract interface AppComponent {
#org.jetbrains.annotations.NotNull #Binds androidx.fragment.app.Fragment productlist.ProductListBindingModule.bindFragment(productlist.ProductListFragment)
#org.jetbrains.annotations.NotNull #Binds androidx.fragment.app.Fragment productlist.ProductListV2BindingModule.bindFragment(productlist.ProductListV2Fragment)
androidx.fragment.app.Fragment is injected at
authentication.usecase.FacebookLoginUseCase(fragment, …)
authentication.usecase.FacebookLoginUseCase is injected at
authentication.usecase.SocialLoginUseCase(…, facebookLoginUseCase, …)
authentication.usecase.SocialLoginUseCase is injected at
authentication.FacebookLoginViewModel(…, socialLoginUseCase, …)
authentication.FacebookLoginViewModel is injected at
authentication.FacebookLoginViewModelModule.facebookViewModelChildFactory(facebookLoginViewModel)
java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
di.factory.ViewModelProviderFactory(classToViewModel)
di.factory.ViewModelProviderFactory is injected at
.authentication.login.LoginFragment.mViewModelChildFactory
authentication.login.LoginFragment is injected at
dagger.android.AndroidInjector.inject(T) [tech.central.tops.di.AppComponent → di.BuilderModule_BindMainActivity.MainActivitySubcomponent → FragmentProvider_BindLoginFragment.LoginFragmentSubcomponent]

Your module depends on ProductListFragment to obtain a child FragmentManager. However, that same module is used to inject into ProductListV2Fragment, and there is no ProductListFragment available at that point.
In order to reuse that module, you will need to split the dependency on ProductListFragment into a separate module. This module should be used for ProductListFragment, and a similar module should be used for ProductListV2Fragment. In these modules, you can either provide FragmentManager directly or bind both fragments to a common superclass such as Fragment or BaseProductListFragment.
If you bind both fragments to Fragment, the resulting code will look like this:
#Module
abstract class FragmentProvider {
#PerFragment
#ContributesAndroidInjector(modules = [
ProductListModule::class,
ProductListV2BindingModule::class
])
abstract fun bindProductListV2Fragment(): ProductListV2Fragment
#PerFragment
#ContributesAndroidInjector(modules = [
ProductListModule::class,
ProductListBindingModule::class
])
abstract fun bindProductListFragment(): ProductListFragment
}
#Module
class ProductListModule {
#Provides
fun provideChildFragmentManager(productListFragment: Fragment) =
productListFragment.childFragmentManager
}
#Module
abstract class ProductListBindingModule {
#Binds
fun bindFragment(fragment: ProductListFragment): Fragment
}
#Module
abstract class ProductListV2BindingModule {
#Binds
fun bindFragment(fragment: ProductListV2Fragment): Fragment
}
As for the error message itself, it tells you three things:
The generated ProductListV2FragmentSubcomponent requires a ProductListFragment, but there is no available binding for it.
The subcomponent may be able to inject dependencies into an existing ProductListFragment. This can be useful information in other situations, but it isn't relevant to what you're trying to do.
There is a component in your app which can provide ProductListFragment. Specifically, this is the generated ProductListFragmentSubcomponent, which takes a ProductListFragment in its factory.

Related

Dagger fails after migrating to 2.17

I've two modules :app and :settings. Here are my dagger configuration.
My component:
#ApplicationScope
#Component(
modules = [
AndroidInjectionModule::class,
AndroidSupportInjectionModule::class,
RoutingModule::class,
SettingsModule::class
]
)
interface ELanguageComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): ELanguageComponent
}
fun inject(application: BaseELanguageApplication)
}
The RoutingModule:
#Module
class RoutingModule {
#Provides
fun ProvidesettingsFragmentRouter(router: SettingsRouter): SettingsFragment.Router = router
}
The SettingsRouter:
class SettingsRouter #Inject constructor(
private val applicationStateManager: ApplicationStateManager
) : SettingsFragment.Router
The SettingsModule:
#Module
abstract class SettingsModule {
#ActivityScope
#ContributesAndroidInjector(modules = [SettingsFragmentModule::class, ApplicationStateManagerModule::class])
abstract fun settingsActivity(): SettingsActivity
}
#Module
abstract class ApplicationStateManagerModule {
#Module
companion object {
#JvmStatic
#Provides
fun bindApplicationStateManager(settingsActivity: SettingsActivity): ApplicationStateManager = settingsActivity
}
}
My SettingsFragmentModule:
#Module
abstract class SettingsFragmentModule {
#FragmentScope
#ContributesAndroidInjector
abstract fun settingsFragment(): SettingsFragment
}
And the SettingsFragment.Router is injected inside my SettingsFragment:
class SettingsFragment : DaggerFragment() {
#Inject
lateinit var router: Router
...
}
I've already found and read this article but it does not help much since the error is not that explanatory:
[Dagger/MissingBinding]
com.altissia.common.authentication.ApplicationStateManager cannot be
provided without an #Provides-annotated method. public abstract
interface ELanguageComponent {
^
com.altissia.common.authentication.ApplicationStateManager is injected at
com.altissia.router.SettingsRouter(applicationStateManager, …)
com.altissia.router.SettingsRouter is injected at
com.altissia.injection.module.RoutingModule.ProvidesettingsFragmentRouter(router)
com.altissia.settings.fragment.SettingsFragment.Router is injected at
com.altissia.settings.fragment.SettingsFragment.router
com.altissia.settings.fragment.SettingsFragment is injected at
dagger.android.AndroidInjector.inject(T) [com.altissia.injection.component.ELanguageComponent →
com.altissia.settings.injection.module.SettingsModule_SettingsActivity.SettingsActivitySubcomponent
→
com.altissia.settings.injection.module.SettingsFragmentModule_SettingsFragment.SettingsFragmentSubcomponent]
What am I missing here? ApplicationStateManager is provided through the ApplicationStateManagerModule which is installed inside SettingsModule.
The situation in the linked article applies here.
SettingsFragment requests a SettingsFragment.Router.
SettingsFragment.Router depends on SettingsRouter, and this binding is in the application component.
SettingsRouter depends on ApplicationStateManager via an #Inject constructor.
The ApplicationStateManager binding is in your (generated) activity subcomponent.
Thus, you have a binding in the parent component which requires a binding in a subcomponent. As described in the article, the easiest way to fix this is to move RoutingModule into your activity (or fragment) subcomponent.
You should add SettingsFragment to SettingsActivity using a contributor module
#ActivityScope
#ContributesAndroidInjector(modules = [
ApplicationStateManagerModule::class,
FragmentContributorModule::class])
abstract fun settingsActivity(): SettingsActivity
And contributor module is something like this
#Module
abstract class FragmentContributorModule {
#FragmentScope
#ContributesAndroidInjector(modules = [SettingsFragmentModule::class])
abstract fun contributeSettingsFragment(): SettingsFragment
}
My setup that i used to use with dagger-android was
AppComponent
#Component(modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
ActivityContributorModule::class])
/*
* ActivityContributorModule defines which Activities will have which modules and inject objects
* If an Activity has any fragments it should add them via FragmentContributorModule with #ContributesAndroidInjector
* #ContributesAndroidInjector(modules = {MainActivityModule.class, FragmentContributorModule.class})
*/
#Singleton
interface AppComponent : AndroidInjector<MyApplication> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
override fun inject(myApplication: MyApplication)
}
and Activity set up with ActivityContributorModule
#Module
abstract class ActivityContributorModule {
#ActivityScope
#ContributesAndroidInjector(modules = [MainActivityModule::class, FragmentContributorModule::class])
abstract fun contributeMainActivity(): MainActivity
#ActivityScope
#ContributesAndroidInjector(modules = [SecondActivityModule::class])
abstract fun contributeSecondActivity(): SecondActivity
#ActivityScope
#ContributesAndroidInjector
abstract fun contributeThirdActivity(): ThirdActivity
}
And for the fragments to be used by Activities
#Module
abstract class FragmentContributorModule {
/**
* FragmentContributorModule is used inside ActivityContributorModule
* With #ContributesAndroidInjector(modules = MyFragmentModule.class)
* defines which module will be used to inject objects to MyFragment
*
*
* In this example [MainActivity] has [FirstFragment] fragment1, and [FirstFragment]
* uses [FirstFragmentModule] to inject objects
*
*
*
* Scope of #ContributesAndroidInjector methods and their modules should be same.
* Otherwise app returns HAS CONFLICTING SCOPES error
*
*/
#FragmentScope
#ContributesAndroidInjector(modules = [FirstFragmentModule::class])
abstract fun contributeMyFragment(): FirstFragment
}
This is obsolete since the Dagger Hilt came out. It's much easier to implement dependency injection.
You can check out this github link you can refer both for dagger-android with scoped components or Hilt implementations.

Injecting ViewModelFactory in different activities

I'm using the well-known Dagger-ViewModelFactory pattern to be able to inject a factory for all the ViewModel in all the activities.
#ActivityScope
class ViewModelFactory #Inject constructor(
private val creators: MutableMap<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val creator = creators[modelClass] ?: creators.entries.firstOrNull {
modelClass.isAssignableFrom(it.key)
}?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
return creator.get() as T
}
}
The problem I have is that when I inject the factory into an Activity Dagger fails because the providers of the objects for the ViewModels that I'm not going to use are not always accessible. They are not because the modules that contain the providers have not been added.
For example, I have a LogIn activity and a SignUp activity, and this is the way I add the subcomponents for them:
#ContributesAndroidInjector(modules = [
ViewModelModule::class,
FirebaseModule::class,
LogInModule::class,
BindLogInModule::class
])
#ActivityScope
internal abstract fun loginActivityInjector(): LoginActivity
#ContributesAndroidInjector(modules = [
ViewModelModule::class,
FirebaseModule::class,
SignUpModule::class,
BindSignUpModule::class
])
#ActivityScope
internal abstract fun signUpActivityInjector(): SignUpActivity
Please notice that when I create the subcomponent for SignUpActivity I do not add the Module LogInModule because I do not need the bindings in that Module.
The result is that I get the error
e: com.package.my.AppComponent.java:8: error: [Dagger/MissingBinding] com.package.my.login.domain.LogInAuthenticator cannot be provided without an #Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector {
^
A binding with matching key exists in component: com.package.my.di.ActivityInjectorsModule_LoginActivityInjector$app_prodDebug.LoginActivitySubcomponent
com.package.my.login.domain.LogInAuthenticator is injected at
com.package.my.login.repository.LoginRepository(logInAuthenticator)
com.package.my.login.repository.LoginRepository is injected at
com.package.my.login.domain.LoginUseCase(loginRepository)
com.package.my.login.domain.LoginUseCase is injected at
com.package.my.login.presentation.LoginViewModel(loginUseCase)
com.package.my.login.presentation.LoginViewModel is injected at
com.package.my.di.ViewModelModule.provideLoginViewModel(viewModel)
java.util.Map,javax.inject.Provider> is injected at
com.package.my.di.ViewModelFactory(creators)
com.package.my.di.ViewModelFactory is injected at
com.package.my.di.ViewModelModule.bindViewModelFactory$app_prodDebug(factory)
androidx.lifecycle.ViewModelProvider.Factory is injected at
com.package.my.login.ui.SignUpActivity.viewModelFactory
com.package.my.login.ui.SignUpActivity is injected at
dagger.android.AndroidInjector.inject(T) [com.package.my.di.AppComponent → com.package.my.di.ActivityInjectorsModule_SignUpActivityInjector$app_prodDebug.SignUpActivitySubcomponent]
This happens because LogInAuthenticator is provided by LogInModule.
Does this mean that the only solution is to add LogInModule even if I don't really need to create GoogleSignInClient in the SignUpActivity?
You have declared both of #ContributesAndroidInjector methods to be dependent on ViewModelModule. Inside ViewModelModule you have declared all of the ViewModels out there, which means, that at the point when Dagger wants to construct the dependency tree for SignUpActivity it will also require you to explicitly mention how LoginViewModel should be constructed. This happens, because Dagger needs to know how each of the dependency declared inside ViewModelModule should be constructed.
The solution for you case will be either include all of the modules in all of #ContributesAndroidInjector declarations (which is an ugly approach), or, alternatively, move the provider method of SignUpViewModel to SignUpModule and do not include ViewModelModule for SignUpActivity declaration.
Here's the setup that works for me.
First, I have created a BaseActivityModule, which all of feature modules should include in their dedicated #Module classes:
#Module
abstract class BaseActivityModule {
#Binds abstract fun bindsViewModelFactory(factory: MyViewModelFactory): ViewModelProvider.Factory
}
Then, assuming we have 2 features: Foo and Bar:
#Module
abstract class ActivitiesModule {
#PerActivity #ContributesAndroidInjector(modules = [FooModule::class])
abstract fun contributesFooActivity(): FooActivity
#PerActivity #ContributesAndroidInjector(modules = [BarModule::class])
abstract fun contributesBarActivity(): BarActivity
}
The implementation class of ViewModelProvider.Factory should be scoped with #PerActivity because the same instance of ViewModelProvider.Factory should be provided each time that dependency is needed to be injected in the scope of particular activity:
private typealias ViewModelProvidersMap = Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
#PerActivity
class MyViewModelFactory #Inject constructor(
private val creators: ViewModelProvidersMap
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
var viewModelProvider = creators[modelClass]
if (viewModelProvider == null) {
val entries = creators.entries
val mapEntry = entries.firstOrNull {
modelClass.isAssignableFrom(it.key)
} ?: throw IllegalArgumentException("Unknown model class $modelClass")
viewModelProvider = mapEntry.value
}
try {
#Suppress("UNCHECKED_CAST")
return viewModelProvider.get() as T
} catch (e: Throwable) {
throw IllegalArgumentException("Couldn't create ViewModel with specified class $modelClass", e)
}
}
}
Where #PerActivity is declared this way:
#Scope
#Retention(AnnotationRetention.RUNTIME)
annotation class PerActivity
FooModule and BarModule are declared as such:
#Module(includes = [BaseActivityModule::class])
abstract class FooModule {
#Binds #IntoMap #ViewModelKey(FooViewModel::class)
abstract fun bindsFooViewModel(viewModel: FooViewModel): ViewModel
}
#Module(includes = [BaseActivityModule::class])
abstract class BarModule {
#Binds #IntoMap #ViewModelKey(BarViewModel::class)
abstract fun bindsBarViewModel(viewModel: BarViewModel): ViewModel
}
Then we are including ActivitiesModule in the AppComponent as such:
#Singleton
#Component(modules = [
AndroidInjectionModule::class,
ActivitiesModule::class
])
interface AppComponent {
...
}
With this approach we've moved the ViewModelProvider.Factory creation one layer down: previously it was in the topmost AppComponent and now each of subcomponents will take care of creating the ViewModelProvider.Factory.
The answer to why you need to add the LoginModule lies in your error log. The error log traces the dependencies as below:
SignUpActivity <--is injected at-- ViewModelFactory <--is injected at-- LoginViewModel <--is injected at-- LoginUseCase <--is injected at-- LoginRepository <--is injected at-- LoginAuthenticator
The map above shows that you need to add LoginModule because Dagger needs it to successfully inject your ViewModelFactory into your SignUpActivity.
EDIT
Move SignUpViewModel binding from your ViewModelModule and place it in your SignUpModule like below:
#Module(includes = [SignUpModule.BindsModule::class])
class SignUpModule {
// your other provides methods
#Module
interface BindsModule{
#Binds
#IntoMap
#ViewModelKey(SignUpViewModel::class)
fun signUpViewModel(signUpViewModel: SignUpViewModel): ViewModel
}
}
Then add subcomponents this way. Notice ViewModelModule has been excluded
#ContributesAndroidInjector(modules = [
FirebaseModule::class,
SignUpModule::class,
BindSignUpModule::class
])
#ActivityScope
internal abstract fun signUpActivityInjector(): SignUpActivity

Dagger class cannot be provided without an #Inject constructor or an #Provides-annotated method

Hello I'm new to Dagger and I have crated a simple project to learn more about it. I have a class PermissionManager that has activity as constructor parameter
class PermissionManager(activity: MainActivity) {
}
and my MainFragment has a dependency on it. So I created BindingModule
#Module
interface BindingModule {
#DaggerScope(MainActivity::class)
#ContributesAndroidInjector(modules = [MainActivityModule::class])
fun provideMainActivity(): MainActivity
#FragmentScope
#ContributesAndroidInjector(modules = [MainFragmentModule::class])
fun provideMainFragment(): MainFragment
}
Here's my MainActivityModule that provides PermissionManager
#Module
abstract class MainActivityModule private constructor() {
#Module
companion object {
#Provides
#JvmStatic
fun providePermissionManager(activity: MainActivity): PermissionManager = PermissionManager(activity)
}
}
and here's my MainFragmentModule that has to use PermissionManager that was created in my MainActivityModule
#Module
abstract class MainFragmentModule private constructor() {
#Module
companion object {
#JvmStatic
#Provides
#IntoMap
#ViewModelKey(MyTestViewModel::class)
fun provideModelFactory(
permissionManager: PermissionManager
): ViewModel = MyTestViewModel(permissionManager)
}
}
and here's what I get
com\nav\component\di\AppComponent.java:12: error: [Dagger/MissingBinding] com.nav.component.utils.PermissionManager cannot be provided without an #Inject constructor or an #Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.nav.component.MyTestDaggerApp>
So first of all I don't understand why I can't use dependency that was created for activity in my fragment? Any ideas how to solve this?
EDIT:
Here's how Binding Module is used
#AppScope
#Component(
modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
BindingModule::class,
NetworkingModule::class
]
)
interface AppComponent : AndroidInjector<MyTestDaggerApp> {
/**
* AppComponent Builder interface. All implementation part is handled by a dagger compiler.
*/
#Component.Factory
interface Factory : AndroidInjector.Factory<MyTestDaggerApp>
}
Make your fragment a subcomponent of BindingModule to get bindings from it. Don't forget to make MainActivity implement HasAndroidInjector (if it doesn’t already).
#Module
interface BindingModule {
#DaggerScope(MainActivity::class)
#ContributesAndroidInjector(modules = [MainActivityModule::class, MainFragmentBindingModule::class])
fun provideMainActivity(): MainActivity
}
#Module
interface MainFragmentBindingModule {
#FragmentScope
#ContributesAndroidInjector(modules = [MainFragmentModule::class])
fun provideMainFragment(): MainFragment
}

Android Dagger get Parent Fragment into child fragment

I have been trying to, unsuccessfully, inject the Parent Fragment into its sub fragments for navigation purposes. I have followed a couple of different posts but I can't seem to understand what am I missing in my implementation.
I have a MainActivity that contains a ViewPager with one such page containing EventsFragment. This fragment in turn has two child fragments EventsListFragment and EventsDetailFragment. I wanted to inject EventsFragment fragment into EventsListFragment so that I can tell it to navigate to EventsDetailFragment.
I know this is probably a repetition of the posts below and I really apologize for that but I honestly cannot see what am I missing. These are the posts I've found:
Dagger 2.10 Android subcomponents and builders
How to create custom scoped modules in dagger 2.10
https://google.github.io/dagger/subcomponents.html
My implementation is as follows:
ApplicationComponent
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
ActivityModule::class,
BaseApplicationModule::class,
ApplicationModule::class])
interface ApplicationComponent : AndroidInjector<AndroidApplication> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<AndroidApplication>()
}
ActivityModule
#Module(includes = [MainActivityProvider::class])
abstract class ActivityModule{
}
MainActivityProvider
#Module(includes = [EventsFragmentProvider::class])
abstract class MainActivityProvider {
#PerActivity
#ContributesAndroidInjector(modules = [MainActivityModule::class])
abstract fun provideMainActivityFactory(): MainActivity
}
EventsFragmentProvider
#Module(includes = [EventsListProvider::class,
EventsDetailProvider::class])
abstract class EventsFragmentProvider {
#PerFragment
#ContributesAndroidInjector(modules = [EventsFragmentModule::class])
abstract fun provideEventsFragmentFactory(): EventsFragment
}
EventsFragmentModule
#Module
class EventsFragmentModule {
#Binds
abstract fun providesEventListView(eventsFragment: EventsFragment): EventContract.Flow
}
EventsListProvider
#Module
abstract class EventsListProvider {
#PerFragment
#ContributesAndroidInjector(modules = [EventsListModule::class])
abstract fun provideEventsListFragmentFactory(): EventsListFragment
}
EventsFragment
class EventsFragment : DaggerFragment(), EventContract.Flow {
override fun navigateToList() {
addFragment(navigator.getEventsListFragment(context!!))
}
override fun navigateToDetail(id: String) {
println("id = ${id}")
}
...
}
EventContract
interface EventContract {
interface Flow {
fun navigateToList()
fun navigateToDetail(id: String)
}
}
EventsListFragment
class EventsListFragment : DaggerFragment() {
#Inject
lateinit var eventsFlow: EventContract.Flow
...
}
Error
[Dagger/MissingBinding] [dagger.android.AndroidInjector.inject(T)] EventContract.Flow cannot be provided without an #Provides-annotated method.
public abstract interface ApplicationComponent extends dagger.android.AndroidInjector<AndroidApplication> {
^
EventContract.Flow is injected at
EventsListFragment.eventsFlow
EventsListFragment is injected at
dagger.android.AndroidInjector.inject(T)
component path: ApplicationComponent →EventsListProvider_ProvideEventsListFragmentFactory.EventsListFragmentSubcomponent
I may be using a anti-pattern but this is what got me working to this point. Im open to changes that may help me achieve this
The error is caused by the following module:
#Module
class EventsFragmentModule {
#Binds
abstract fun providesEventListView(eventsFragment: EventsFragment): EventContract.Flow
}
Dagger would not provide you with a Fragment and you shouldn't do that.
Moreover, I think you misunderstand the meaning of #ContributesAndroidInjector. It means creating an AndroidInjector for you but proving an instance.
#Module
abstract class EventsListProvider {
#PerFragment
#ContributesAndroidInjector(modules = [EventsListModule::class])
abstract fun provideEventsListFragmentFactory(): EventsListFragment
}
So you should pass your EventsFragment instance into the module like this post instead of using field injection.

Dagger: A binding with matching key exists in component

I am using Dagger 2.16 and was following this article for my dagger implementation. Everything was working fine with this implementation until I had only one Activity(HomeActivity). As soon as I started implementing Dagger in SplashScreenActivity. I started getting this error. Here is some code from my project
AppComponent.kt
#Singleton
#Component(modules = [
AndroidInjectionModule::class,
AppModule::class,
ActivityBuilder::class,
ServiceBuilder::class,
BroadcastRecieverBuilder::class])
interface AppComponent : AndroidInjector<MyApp> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<MyApp>()
}
AppModule.kt
#Module()
class AppModule {
#Provides
#Singleton
fun provideContext(application: MyApp): Context {
return application
}
#Provides
#Singleton
fun provideRestService(retrofit: Retrofit): RestService {
return retrofit.create(RestService::class.java)
}
...
}
ActivityBuilder.kt
#Module
abstract class ActivityBuilder {
#ContributesAndroidInjector(modules = [HomeActivityModule::class])
#PerActivity
abstract fun bindHomeActivity(): HomeActivity
#ContributesAndroidInjector(modules = [SplashScreenModule::class])
#PerActivity
abstract fun bindSplashActivity(): SplashScreenActivity
}
BaseActivity.kt
abstract class BaseActivity<V : BaseView, P : MvpBasePresenter<V>> :
MvpActivity<V, P>(), BaseView, HasSupportFragmentInjector {
#Inject
lateinit var fragmentInjector: DispatchingAndroidInjector<Fragment>
#Inject
lateinit var mPresenter: P
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
}
override fun createPresenter(): P = mPresenter
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return fragmentInjector
}
}
I have my own BaseActivity instead of DaggerActivity because I what to inherit from mosby's MvpActivity.
SplashScreenModule.kt
#Module
abstract class SplashScreenModule {
#Binds
#PerActivity
internal abstract fun splashPresenter(splashPresenter: SplashScreenPresenter): BasePresenter<*>
}
HomeActivityModule.kt
#Module
abstract class HomeActivityModule {
#Binds
#PerActivity
internal abstract fun homePresenter(homePresenter: HomeActivityPresenter): BasePresenter<*>
#ContributesAndroidInjector(modules = [DownloadFragmentModule::class])
#PerFragment
internal abstract fun downloadsFragment(): DownloadsFragment
}
Now when I build this, I get an error as follows
error: [Dagger/MissingBinding] [dagger.android.AndroidInjector.inject(T)] java.util.Map<java.lang.Class<? extends android.support.v4.app.Fragment>,javax.inject.Provider<dagger.android.AndroidInjector.Factory<? extends android.support.v4.app.Fragment>>> cannot be provided without an #Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.realtime.app.MyApp> {
^
A binding with matching key exists in component: com.realtime.dagger.ActivityBuilder_BindHomeActivity.HomeActivitySubcomponent
java.util.Map<java.lang.Class<? extends android.support.v4.app.Fragment>,javax.inject.Provider<dagger.android.AndroidInjector.Factory<? extends android.support.v4.app.Fragment>>> is injected at
dagger.android.DispatchingAndroidInjector.<init>(injectorFactories)
dagger.android.DispatchingAndroidInjector<android.support.v4.app.Fragment> is injected at
com.realtime.core.BaseActivity.fragmentInjector
com.realtime.splashScreen.SplashScreenActivity is injected at
dagger.android.AndroidInjector.inject(T)
component path: com.realtime.dagger.AppComponent → com.realtime.dagger.ActivityBuilder_BindSplashActivity.SplashScreenActivitySubcomponent
I have gone through other similar que like this but couldn't relate it to what I am facing. What am I missing?
Update: For now I am not inheriting BaseActivity in SplashScreenActivity so that I can avoid injecting fragmentInjector: DispatchingAndroidInjector<Fragment>. It is working for now as I don't have any fragment in SplashScreenActivity.
It works for HomeActivity because it binds a fragment:
#ContributesAndroidInjector
fun downloadsFragment(): DownloadsFragment
SplashScreenActivity does not.
AndroidInjection uses DispatchingAndroidInjector to handle runtime injections, which basically contains a Map of classes to their component builders. This map needs to be injected like everything else. In the case of HomeActivity the fragment declaration in the module generates a binding for the map, which can then be injected.
Since there is no Fragment on the splash activity Dagger does not know about any bindings, let alone any map. Which is why it complains that it cannot be provided.
You can read more here about multibindings.
To prevent this from happening, you should register AndroidInjectionModule on your AppComponent, which just contains the declarations for the empty maps.
While it contains the declaration for android.app.Fragment it does not for android.support.v4.app.Fragment, which is where the error comes from.
So to fix this specific error you should add AndroidSupportInjectionModule to your component, which also includes the support bindings, providing an empty map when there are no fragments in an activity.
#Component(modules = [AndroidSupportInjectionModule::class, /* ... */])
interface AppComponent { /* ... */ }

Categories

Resources