Submodules with Dagger and AndroidInjectors

Gabriel Sanmartin :

I have been Dagger with the pre Android Injector style for a while, and have now decided to try the new methods. Til now, I was basically declaring on AppComponent like this:

@Singleton
@Component(
        modules = [ApplicationModule::class,
            NetModule::class,
            ApiModule::class,
            AnalyticsModule::class,
            DbModule::class,
            RepositoryModule::class,
            InteractorModule::class]
)
interface ApplicationComponent {
    fun inject(app: MyApp)
    fun plus(controllerModule: ControllerModule): ControllerComponent
}

Then I would inject my Activities/Fragments/Services/Dialogs like this:

class MyActivity : AppCompatActivity() {
...
    val component by lazy {
            (application as MyApp)
                    .applicationComponent
                    .plus(
                            ControllerModule(this)
                    )
        }

    override fun inject() {
            component.inject(this)
        }
...
}

Basically I had one top level app component with app-traversal modules, then an activity level component (ControllerComponent) with per-activity instances, common to all activities.

Now that I switched to the new methods, I create my component like so:

@Singleton
@Component(
        modules = [
            AndroidSupportInjectionModule::class,
            AppModule::class,
            NetModule::class,
            ApiModule::class,
            AnalyticsModule::class,
            DbModule::class,
            RepositoryModule::class,
            InteractorModule::class
        ]
)
interface AppComponent : AndroidInjector<SoulpicksApp> {

    @Component.Builder
    interface Builder {
        fun build(): AppComponent
        @BindsInstance
        fun application(application: SoulpicksApp): Builder
    }

}

Make my app extend DaggerApplication:

open class MyApp : DaggerApplication() {


    override fun applicationInjector(): AndroidInjector<out DaggerApplication> = DaggerAppComponent.builder().application(this).build()

}

And my Activities/Fragments extend DaggerAppCompatActivty/DaggerFragment respectively:

class MyActivity : DaggerAppCompatActivity() {

...
}

I understand this should automatically wire up all the activities dependencies, provided Dagger is properly set up. However I havent declared my ControllerModule/Component so of course when running my app I get:

e: /Users/user/dev/my-android/app/build/generated/source/kapt/devDebug/com/myapp/android/di/activity/ActivityBinder_ContributesMyActivity.java:28: error: @Subcomponent.Builder is missing setters for required modules or subcomponents: [com.myapp.android.di.controller.ControllerModule]

I understand previously I was creating this component on each Activity by using the plus() method and injecting explicitly (which is what I am trying to avoid here), how can I do that now?

Also, I have some BottomSheetDialogFragments and JobServiceIntents in my app, and theres no equivalent DaggerBottomSheedDialogFragments/DaggerJobServiceIntents to extend from, how can I work around that?

ControllerModule:

@Module
class ControllerModule(val activity: androidx.fragment.app.FragmentActivity) {

    @Provides
    @ControllerScope
    fun context(): Context = activity

    @Provides
    @ControllerScope
    fun activity() = activity

    @Provides
    @ControllerScope
    fun layoutInflater() = activity.layoutInflater

    @Provides
    @ControllerScope
    fun fragmentManager(): androidx.fragment.app.FragmentManager = activity.supportFragmentManager

    @Provides
    @ControllerScope
    fun provideNavigationController(activity: androidx.fragment.app.FragmentActivity, analyticsManager: AnalyticsCompositeManager) = NavigationController(activity, analyticsManager)

    @Provides
    @ControllerScope
    fun providePackageUtils(activity: androidx.fragment.app.FragmentActivity) : PackageUtils = PackageUtilsImpl(activity)
}

Changes after @luis_cortes answer:

package io.soulpicks.android.di.activity

@Module
abstract class ActivityBinder {

    @ControllerScope
    @ContributesAndroidInjector(modules = [ControllerModule::class])
    abstract fun constributesSplashActivity(): SplashActivity

    @ControllerScope
    @ContributesAndroidInjector(modules = [ControllerModule::class])
    abstract fun contributesDashboardActivity(): DashboardActivity

    ....


}

ControllerModule:

@Module(includes = [ViewContainerModule::class])
class ControllerModule {


    @Provides
    @ControllerScope
    fun context(activity: DaggerAppCompatActivity): Context = activity.applicationContext

    @Provides
    @ControllerScope
    fun layoutInflater(activity: DaggerAppCompatActivity) : LayoutInflater = activity.layoutInflater

    @Provides
    @ControllerScope
    fun fragmentManager(activity: DaggerAppCompatActivity): FragmentManager = activity.supportFragmentManager

    @Provides
    @ControllerScope
    fun navigationController(activity: DaggerAppCompatActivity, analyticsManager: AnalyticsCompositeManager): NavigationController = NavigationController(activity, analyticsManager)

    @Provides
    @ControllerScope
    fun providePackageUtils(activity: DaggerAppCompatActivity): PackageUtils = PackageUtilsImpl(activity)
}

Error:

e: /Users/kelmer/dev/myapp-android/app/build/tmp/kapt3/stubs/devDebug/io/myapp/android/di/application/AppComponent.java:8: error: [Dagger/MissingBinding] com.myapp.android.managers.PackageUtils cannot be provided without an @Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.myapp.android.MyApp> {
                ^
      com.myapp.android.managers.PackageUtils is injected at
          com.myapp.android.ui.invite.SendInviteViewModel(packageUtils, …)
      com.myapp.android.ui.invite.SendInviteViewModel is injected at
          com.myapp.android.di.viewmodel.ViewModelModule.sendInviteViewModel$app_devDebug(sendInviteViewModel)
      java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
          com.myapp.android.di.viewmodel.MyappViewModelFactory(viewModels)
      com.myapp.android.di.viewmodel.MyappViewModelFactory is injected at
          com.myapp.android.di.viewmodel.ViewModelModule.bindViewModelFactory$app_devDebug(factoryMyapp)
      androidx.lifecycle.ViewModelProvider.Factory is injected at
          com.myapp.android.base.BaseActivity.viewModelFactory
      com.myapp.android.ui.splash.SplashActivity is injected at
          dagger.android.AndroidInjector.inject(T) [com.myapp.android.di.application.AppComponent → com.myapp.android.di.activity.ActivityBinder_ConstributesSplashActivity.SplashActivitySubcomponent]
  It is also requested at:
      com.myapp.android.ui.dashboard.friends.contact.ContactSyncViewModel(…, packageUtils, …)
      com.myapp.android.views.appchoosedialog.AppChooserViewModel(packageUtils)
  The following other entry points also depend on it:
      dagger.android.AndroidInjector.inject(T) [com.myapp.android.di.application.AppComponent → com.myapp.android.di.activity.ActivityBinder_ContributesDashboardActivity.DashboardActivitySubcomponent]

https://github.com/kelmer44/mvvm-base

branch master is current implementation (no dagger-android) branch chore/dagger-rework is my attempt

luis_cortes :

Update

I managed to get the sample project working with some very minor tweaks to the original solution. I've updated the answer below for posterity and marked steps that are new or have changed in bold.

Note: There were some very small tweaks necessary to the ControllerModule in the sample project that were also necessary, but the answers below are what would be most applicable to everyone else on the site so I chose to omit them here.


  • Create a file called MainActivityModule with this in it:
@Module
abstract class MainActivityModule {
   @Binds @ControllerScope
   abstract fun bindsActivity(mainActivity: MainActivity): FragmentActivity
}
  • Add this to a module that is installed on your AppComponent:
@ContributesAndroidInjector([ControllerModule::class, MainActivityModule::class])
@ControllerScope 
abstract fun contributesMyActivity(): MyActivity
  • Remove val activity: androidx.fragment.app.FragmentActivity as a property on the constructor for ControllerModule

  • Change your @Provides methods to take in a FragmentActivity like this:

@Provides
@ControllerScope
fun providesContext(activity: FragmentActivity): Context = activity

For your other classes it just depends on whether or not you control when and how they get instantiated. If you do, just use regular constructor injection.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=123703&siteId=1