How to @Inject members in BaseActivity using dagger.android?

耗尽温柔 提交于 2019-12-23 03:52:08

问题


This is my scenario:

  • I want all my Activities to inherit from 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

回答1:


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:

  1. It adds BaseActivityModule to the MainActivityComponent, so that your base-dependencies can be provided by this component and MainActivity can be injected
  2. It binds MainActivity 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 have

While 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.




回答2:


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



来源:https://stackoverflow.com/questions/48533899/how-to-inject-members-in-baseactivity-using-dagger-android

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!