Dagger/MissingBinding error when creating a subcomponent - android

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?

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.

Related

Dagger2 Can't provide dependency of activity to dagger

MainActivity cannot be provided without an #Inject constructor or an
#Provides-annotated method. This type supports members injection but
cannot be implicitly provided.
I'm using dagger-android, I injected MainActivity through AndroidInjection.inject(this), but it's still unavailable in Module. I prepared sample project: https://github.com/deepsorrow/test_daggerIssu.git, files listed below:
FactoryVmModule:
#Module
class FactoryVmModule {
#Provides
#Named("TestVM")
fun provideTestVM(
activity: MainActivity, // <--- dagger can't inject this
viewModelFactory: ViewModelFactory
): TestVM =
ViewModelProvider(activity, viewModelFactory)[TestVM::class.java]
}
MainActivityModule:
#Module
abstract class MainActivityModule {
#ContributesAndroidInjector
abstract fun injectMainActivity(): MainActivity
}
MainActivity (using DaggerAppCompatActivity):
class MainActivity : DaggerAppCompatActivity() {
#Named("TestVM")
#Inject
lateinit var testVM: TestVM
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
TestApplication:
class TestApplication : Application(), HasAndroidInjector {
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
override fun onCreate() {
super.onCreate()
DaggerAppComponent.create().inject(this)
}
override fun androidInjector() = dispatchingAndroidInjector
}
AppComponent:
#Component(modules = [AndroidInjectionModule::class, MainActivityModule::class, ViewModelModule::class, FactoryVmModule::class])
interface AppComponent {
fun inject(application: TestApplication)
}
dagger.android does do this automatically: See the explicit version of the binding that #ContributesAndroidInjector generates for you, where the generated AndroidInjector.Factory contains a #BindsInstance binding of the type you request here.
This isn't working for you because you are injecting MainActivity in a binding that is installed on your top-level component. This is a problem because AppComponent will exist before the Activity does, and will also be replaced as Android recreates the Activity: Passing an instance through #Component.Builder is not a way around this problem.
Instead, move your FactoryVmModule::class to within the subcomponent that #ContributesAndroidInjector generates, which you can do by including it in the modules attribute on #ContributesAndroidInjector. Dagger will create a different subcomponent instance per Activity instance, so your FactoryVmModule will always have a fresh binding to MainActivity.
#Module
abstract class MainActivityModule {
#ContributesAndroidInjector(
modules = [ViewModelModule::class, FactoryVmModule::class]
)
abstract fun injectMainActivity(): MainActivity
}
I moved your ViewModelModule class there as well; though it's possible you could leave it in your top-level Component if it doesn't depend on anything belonging to the Activity, you might want to keep them together. Bindings in subcomponents inherit from the application, so you can inject AppComponent-level bindings from within your Activity's subcomponent, but not the other way around. This means you won't be able to inject VM instances (here, TestVM) outside your Activity, but if they depend on the Activity, you wouldn't want to anyway: Those instances might go stale and keep the garbage collector from reclaiming your finished Activity instances.

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()
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
}
}

Dagger2 dependency Cycle by Using #Binds and #Inject fields

I am getting a dependency cycle whenever I try to use a subcomponent with binding objects. I have an app scope and an activity scope. At the app scope I create my web service then when the activity opens I want to create a storage object, controller, and navigator (all custom classes not androidx classes) and inject them into my androidx ViewModel class. But when I do so I get a dependency cycle.
My top level component looks like
#AppScope
#Component(modules = [AppModule::class])
interface AppComponent {
val activityComponentBuilder: ActivityComponent.Builder
}
#Module(subcomponents = [ActivityComponent::class])
interface AppModule {
#Binds
fun mockWebService(mockWebService: MockWebService): MockWebService
}
Next my subcomponent looks like
#ActivityComponent
#Subcomponent(modules = [ActivityModule::class])
interface ActivityComponent {
fun inject(sharedViewModel: SharedViewModel)
#Subcomponent.Builder
interface Builder {
#BindsInstance
fun storage(storage: Storage): Builder
fun build(): ActivityComponent
}
}
In my activity module I bind two objects
#Binds
abstract fun controller(controller: Controller): Controller
#Binds
abstract fun navigator(navigator: Navigator): Navigator
Each object has an #Inject constructor
class Navigator #Inject constructor(private val storage: Storage)
class Controller #Inject constructor(
private val webService: MockWebService,
private val navigator: Navigator,
private val storage: Storage
) {
Inside my shared view model I try to build my component and inject the fields
#Inject
lateinit var navigator: Navigator
#Inject
lateinit var controller: Controller
init {
MainApplication.component.activityComponentBuilder
.storage(InMemoryStorage.from(UUID.randomUUID().toString()))
.build()
.inject(this)
}
But dagger won't build. I get an error
[Dagger/DependencyCycle] Found a dependency cycle: public abstract interface AppComponent {
MockWebService is injected at di.AppModule.mockWebService(mockWebService)
MockWebService is injected at ActivityModule.Controller(webService, …)
Controller is injected at SharedViewModel.controller
SharedViewModel is injected at
But the error message cuts off there. Am I missing something in how to use a subcomponent to put objects on the graph and then inject them into an object? Is this not possible with Dagger?
#Binds is used to let dagger know the different implementations of an interface. You don't need #Binds here since Navigator and Controller are simple classes that do not implement any interface. I'd assume that's the case with MockWebService too. Also, those classes have #Inject constructor, which means dagger can instantiate them and we don't need to write extra #Provides functions for those classes.
#Binds isn't doing any scoping. Its only job is to tell dagger about different implementations. You can add #XScope with #Binds to make some object scoped. Or, you could just add the scope annotation to the class declaration. Here's an example of how you can add scope to class declaration.
As for the dependency cycle, I think it's because you're telling ActivityComponent to use ActivityModule and telling ActivityModule to install ActivityComponent. Doing just either one should be the case (I think).

Dagger.android UserScope

I'm trying to create UserScope with https://google.github.io/dagger/android I have #Singleton, #ActivityScope, #FragmentScope and #UserScope.
AppComponent
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class,
ActivityBindingModule::class,
AppModule::class,
ApiModule::class
]
)
interface AppComponent {
fun inject(application: Application)
fun createUserComponent(): UserComponent.Builder
#Component.Builder
interface Builder {
#BindsInstance
fun application(app: Application): Builder
fun apiModule(module: ApiModule): Builder
fun build(): AppComponent
}
}
UserComponent:
#UserScope
#Subcomponent(
modules = [UserModule::class]
)
interface UserComponent {
#Subcomponent.Builder
interface Builder {
#BindsInstance
fun user(user: User): Builder
fun build(): UserComponent
}
}
UserModule:
#Module
class UserModule {
#UserScope
#Provides
fun provideUser(user: User): User = user
}
Here is how I am creating UserComponent after successfull login:
private fun createSession(user: User) {
userComponent = appComponent.createUserComponent().user(user).build()
}
And also I have UserManager which is triggering an issue with User injection constructor param
#UserScope
class SomeManager #Inject constructor(
private val apiService: ApiService,
private val user: User
) {}
Error message: Error:[dagger.android.AndroidInjector.inject(T)]
domain.model.authorize.User cannot be provided without an #Inject
constructor or from an #Provides-annotated method.
di.modules.MainActivityBindingModule_ContributeUserFragment.UserFragmentSubcomponent
scoped with #di.scopes.FragmentScope may not reference bindings with
different scopes: #di.scopes.UserScope class
domain.managers.SomeManager
I want to create #UserScope to manage related ApiManagers lifecycle
UPD
#Module
class UserFragmentModule {
#Provides
#FragmentScope
fun provideViewModelFactory(someModule: SomeModule) = UserFragmentViewModelFactory(someModule)
}
ContributesAndroidInjector logic:
#Module
interface ActivityBindingModule {
#ActivityScope
#ContributesAndroidInjector(modules = [SplashModule::class])
fun contributeSplashActivity(): SplashActivity
#ActivityScope
#ContributesAndroidInjector(modules = [SignInModule::class])
fun contributeAuthorizeActivity(): Activity
#ActivityScope
#ContributesAndroidInjector(modules = [MainModule::class])
fun contributeMainActivity(): MainActivity
}
#Module(includes = [MainActivityBindingModule::class])
class MainModule
#Module
interface MainActivityBindingModule {
#FragmentScope
#ContributesAndroidInjector(modules = [UserFragmentModule::class])
fun contributeUserFragment(): UserFragment
}
You are trying to inject a User instance that is provided only in the #UserScope in #FragmentScope.
Basically, the rule is whenever you need a User injected - you need to be in the #UserScope.
You need to change your #FragmentScope to #UserScope annotation in all places like that.
From what you posted I believe it is here:
#Module
class UserFragmentModule {
#Provides
#UserScope
fun provideViewModelFactory(someModule: SomeModule) = UserFragmentViewModelFactory(someModule)
}
And you'll need to move your UserFragmentModule to the UserComponent:
#UserScope
#Subcomponent(
modules = [UserModule::class, UserFragmentModule::class]
)
interface UserComponent {
And you'll also need to inject with application.userComponent into your UserFragment
Here is a good article about subcomponents. I'd recommend you to read it to gain full understanding.
Another option, I'm not sure about this one, but maybe you can just inject your UserFragment with your application.userComponent.
I think your problem is wrong use of scopes.
Scopes help us to handle injected item life-cycle to prevent keep un-needed object in whole app life cycle.
and take look at this image:
Your modules should be in same scope that your component is.
for example in your AppComponent, you have ActivityBindingModule that have ActivityScope. why you do this?
I think it is better to have a activityComopnent that have dependency toappComponent. If your activityComponent had some dependency in upper scoped component (appComponent), first you had add dependency of that component. (dependencies= [ AppComponent::class]). Second, you had to expose needed dependency in appComponent with a method that return needed dependency object. Finally in wireUpping you should call appComponent as call activity modules in Dagger wire upping. ( I will show this in a example)
So all you need is activityComponent like this:
#ActivityScope
#Component(modules = [ActivityBindingModule::class],dependencies= [ AppComponent::class])
interface activityComponent {
.
.
.
This is my example written in java that shows how connect appComponent and activityComponent:
#Singleton
#Component(modules = {ApplicationModule.class ,ServicesModule.class})
public interface ApplicationComponent {
void inject(ImicoApplication imicoApplication);
// exposed for child-components.
Context getContext();
Application getApplication();
CompanyService getCompanyService();
CategoryService getCategoryService();
RatingService getRatingService();
NewsService getNewsService();
AuthorizationManager getAuthorizationManager();
}
And this is activityComponent:
#ActivityScope
#Component(dependencies = {ApplicationComponent.class},modules = {SearchActivityModule.class})
public interface SearchserviceComponent {
void inject (SearchFragment searchFragment);
}
And in SearchFragment do this for wire-up:
DaggerSearchserviceComponent.builder()
.applicationComponent(((ImicoApplication) getActivity().getApplication()).getApplicationComponent())
.searchActivityModule(new SearchActivityModule(this)).build().inject(this);
If in SearchFragment i need CompanyService just inject it and it is provided and exposed by applicationComponent.

Dagger 2 Scopes and Dependencies in Kotlin

I'm looking to add Dagger 2 to an existing app and am running into issues with component dependencies and scoping once I get more that 2 levels down. My thought process is to have an AppComponent, LandingActivityComponent and LandingFragmentComponent (not great names right now but we're early) with the scopes #Singleton, #ActivityScope and #FragmentScope respectively. Everything I've done works if I stop at the activity, but once I add the fragment level I get the following error:
e: /Users/me/git/myapp-android/myapp/build/tmp/kapt3/stubs/regularDebug/tv/myapp/android/app/core/dagger/LandingActivityComponent.java:17: error: com.myapp.android.app.core.LandingActivity cannot be provided without an #Inject constructor or from an #Provides-annotated method. This type supports members injection but cannot be implicitly provided.
public abstract com.myapp.android.app.core.LandingActivity landingActivity();
^
com.myapp.android.app.core.LandingActivity is provided at
com.myapp.android.app.core.dagger.LandingActivityComponent.landingActivity()
e: /Users/me/git/myapp-android/myapp/build/tmp/kapt3/stubs/regularDebug/tv/myapp/android/app/core/dagger/LandingFragmentComponent.java:13: error: com.myapp.android.app.settings.QuickSettingsPresenter cannot be provided without an #Inject constructor or from an #Provides- or #Produces-annotated method.
public abstract void inject(#org.jetbrains.annotations.NotNull()
^
com.myapp.android.app.settings.QuickSettingsPresenter is injected at
com.myapp.android.app.profile.ProfileViewPagerFragment.mQuickSettingsPresenter
com.myapp.android.app.profile.ProfileViewPagerFragment is injected at
com.myapp.android.app.core.dagger.LandingFragmentComponent.inject(profileViewPagerFragment)
Application
#Module
class AppModule(private val app: MyApplication) {
#Provides #Singleton
fun provideApp(): MyApplication = app
#Provides #Singleton
fun provideContext(): Context = app.applicationContext
#Provides #Singleton
fun provideAccountManager(): AccountManager = AccountManager(app)
}
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
fun inject(application: MyApplication)
// Allow dependents to see the properties provided below
fun accountManager(): AccountManager
}
Activity
#Scope
#Qualifier
#Retention(RUNTIME)
annotation class ActivityScope
#Module
class LandingActivityModule(private val landingActivity: LandingActivity) {
#Provides #ActivityScope
fun provideLandingActivity(): LandingActivity = landingActivity
}
#ActivityScope
#Component(dependencies = [AppComponent::class], modules = [LandingActivityModule::class])
interface LandingActivityComponent {
fun inject(landingActivity: LandingActivity)
// Allow dependents to see the properties provided below
fun landingActivity(): LandingActivity
fun accountManager(): AccountManager
}
Fragment
#Scope
#Qualifier
#Retention(RUNTIME)
annotation class FragmentScope
#Module
class LandingFragmentModule(private val landingFragment: LandingFragment) {
#Provides #FragmentScope
fun provideFragment(): LandingFragment = landingFragment
#Provides #FragmentScope
fun provideQuickSettings(activity: LandingActivity): QuickSettingsPresenter =
QuickSettingsPresenter.create(activity)
}
#FragmentScope
#Component(dependencies = [LandingActivityComponent::class], modules = [LandingFragmentModule::class])
interface LandingFragmentComponent {
fun inject(profileViewPagerFragment: ProfileViewPagerFragment)
}
I feel like I'm probably missing something fundamental or need to structure things a little differently but I think this highlights what I'm going for.
Interestingly, if I remove the #Scope annotations for the #Provides in the activity and fragment modules, everything is fine. And as I mentioned, if I cut out the fragment module, I can scope the activity no problem.
The end goal here is to have these components work with presenters as well but I'm kind of going step by step. I'm new to non-monolithic components in Dagger and haven't found a guide or post that's made this click for me.
(Posted on behalf of the question author).
I figured out what's going on. Somewhere along the way I read something that lead me to believe I needed #Qualifier as a part of my scopes but that isn't the case. Removing it seems to have fixed my problem. Will leave this here as a dependency based approach for anyone looking.

Categories

Resources