Reputation: 2938
This is my scenario:
BaseActivity
Within the BaseActivity
, I want to inject Navigator
(it helps me to manage the Fragments
backstack and navigate between activities):
abstract class BaseActivity : DaggerAppCompatActivity() {
@Inject
lateinit var navigator: Navigator
@Inject
lateinit var prefs: SharedPreferences // injected via AppModule.kt, see below.
}
The Navigator
class needs a FragmentManager
in its constructor:
class Navigator @Inject constructor(
val fragmentManager: FragmentManager) {
// class body
}
I want to provide FragmentManager
from a BaseActivity
object in BaseActivityModule
:
@Module
class BaseActivityModule {
@PerActivity
@Provides
fun provideFragmentManager(baseActivity: BaseActivity): FragmentManager {
return baseActivity.supportFragmentManager
}
}
This is the rest of my components and modules:
AppComponent.kt
@Singleton @Component(modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
ActivityBindingModule::class])
interface AppComponent {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: AndroidApplication)
}
AppModule.kt
@Module
class AppModule {
@Singleton
@Provides
fun providesPrefs(application: Application): SharedPreferences {
return application.getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)
}
}
ActivityBindingModule.kt
@Module
abstract class ActivityBindingModule {
@ContributesAndroidInjector
abstract fun bindMainActivity(): MainActivity
@ContributesAndroidInjector(modules = [(BaseActivityModule::class)])
abstract fun bindBaseActivity(): BaseActivity
}
BaseActivityModule.kt
@Module
class BaseActivityModule {
@Provides
fun provideFragmentManager(baseActivity: BaseActivity): FragmentManager {
return baseActivity.supportFragmentManager
}
}
This is the compilation error message:
Error: [dagger.android.AndroidInjector.inject(T)]
android.support.v4.app.FragmentManager cannot be provided
without an @Provides- or @Produces-annotated method.
Error:A binding with matching key exists in component:
com.myapp.ActivityBindingModule_BindBaseActivity.BaseActivitySubcomponent
Upvotes: 1
Views: 4687
Reputation: 3060
Main idea to understand that Dagger understands only top component/activity injection and doesn't see BaseActivityComponent
Alternative implementation to @David Medenjak solution is to simply include BaseActivityModule
and inject BaseActivity
:
@ActivityScope
@Subcomponent(modules = {MainActivityModule.class, BaseActivityModule.class} )
public interface PlayerActivityComponent {
void inject(MainActivity activity);
void inject(BaseActivity activity);
}
And you can inject your objects now both in BaseActivity
and MainActivity
Upvotes: 1
Reputation: 34522
The problem lies with your assumption that you need to inject a BaseActivity
at some point...
@Module
abstract class ActivityBindingModule {
@ContributesAndroidInjector
abstract fun bindMainActivity(): MainActivity
// ...that's not really how it works... :/
@ContributesAndroidInjector(modules = [(BaseActivityModule::class)])
abstract fun bindBaseActivity(): BaseActivity
}
Using the above code you end up with some component to inject MainActivity
, and some component to inject BaseActivity
, but neither can inject "both". The error you receive is because MainActivity
can't supply the FragmentManager
it needs to inject in its parent BaseActivity
. It's missing the module to do so. You only add the BaseActivityModule
to your other component, to which the MainActivityComponent effectively has no access—hence the cannot be provided error.
Dagger always needs to inject the whole object. There is no partial injection, or injecting from multiple components at once. If a single component can't provide all the dependencies it needs you will get an error. Your fun bindBaseActivity(): BaseActivity
is useless, because you will never use BaseActivity
, but you will only use MainActivity
or other subclasses of it. Those components need to be able to provide the dependencies of the BaseActivity as well.
If you want to inject dependencies in the BaseActivity you need to add a module that provides the necessary bindings. Your code should end up looking like the following:
@Module
abstract class ActivityBindingModule {
@ContributesAndroidInjector(modules = [BaseActivityModule::class, MainActivityModule::class])
abstract fun bindMainActivity(): MainActivity
// no BaseActivity component necessary
}
@Module
abstract class MainActivityModule {
@Binds
abstract fun bindBaseActivity(activity: MainActivity) : BaseActivity
}
This does the following things:
BaseActivityModule
to the MainActivityComponent, so that your base-dependencies can be provided by this component and MainActivity
can be injectedMainActivity
in another module as your BaseActivity
so that you can use that in your module and don't have to bind a FragmentManager for every activity you haveWhile you can reuse BaseActivityModule
and add it to all of your activity implementations, you will have to add a module to bind the activity as a BaseActivity
for every one of your activities.
There might be a more optimized approach, but that's the vanilla requirements for injecting subclasses.
Upvotes: 7