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
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.
MainActivityModule with this in it: @Module
abstract class MainActivityModule {
@Binds @ControllerScope
abstract fun bindsActivity(mainActivity: MainActivity): FragmentActivity
}
@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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With