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
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 forControllerModule
Change your
@Provides
methods to take in aFragmentActivity
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.