Search code examples
androiddependency-injectionkotlindagger-2dagger

Dagger 2 and dependency injection hell?


How do you use dagger from Kotlin?

I've been in a loop of fixing one compile error and moving to another and at the end I get back to step 1

Here is all I need:

  • AppDependencies
  • GenericActivityDependencies
  • PerActivityDependency

Here are my main dependencies:

App

@Module
class ApplicationModule(private val application: Application) {
    @Provides
    @Singleton
    fun provideContext(): Application = this.application
}

@Singleton
@Component(modules = [ HttpModule::class, ApplicationModule::class ])
interface AppComponent {
    val app: Application
}

Why do I need to once provide the dependency in the module and another time define it in the component?

Activity Module

@Module
class ActivityModule(private val activity: Activity) {

    @PerActivity
    @Provides
    @ActivityContext
    fun provideContext(): Context = activity
}

@Component(modules = [ActivityModule::class], dependencies = [AppComponent::class])
@ActivityContext
interface ActivityComponent {
    fun inject(activity: MainActivity)
}

HomeModule

@Module
class LandingModule {
    @PerActivity
    @Provides
    fun provideSomethig(): Something {
        return  Something()
    }
}
@SomeActivity
@Subcomponent(modules = [LandingModule::class])
interface LandingSubcomponent {
    val something: Something
}

By this point, I have written more code than there needs to be in my whole activity.

  • I get errors like can't inherit from a scopes component
  • Can't generate Dagger gencode
  • Subcomponent needs a different scope

How do I achieve this?

Is there a better di for kotlin?

Is there a sample somewhere I could follow that has per activity module?


Solution

  • I understand your frustrations. I have been there before and it took me quite some time to understand dagger myself. Just a quick demo/tutorial.

    @Singleton
    @Component(modules = [AppModule::class])
    interface AppComponent {
        fun context(): Context
    }
    
    @Module
    class AppModule(private val application: Application) {
        @Provides
        @Singleton
        fun provideApplication(): Application= application
    }
    

    The component is the interface to the container. Anything defined in here can be accessible if you are able to instantiate your container successfully. Also, it is the interface to other containers/components. This means that if you want to expose something outside your container, you define it here. Therefore,

    Why the heck do I need to once provide the dependency in the module and another time define it in the component. This is plain stupid.

    is not always true. You don't need to define anything in your component if you don't want anything to expose outside. An alternative to exposing would be injecting.

    @Singleton
    @Component(modules = [AppModule::class])
    interface AppComponent {
        fun inject(activity: MainActivity)
    }
    

    You are not exposing anything here but you can still get the activity context from the container thru inject.

    Now let's proceed to scoping.

    Scoping is the way to provide 'local singletons' inside your container. A scoped dependency will be created only once inside the container. An example is your PerActivity scope. A scoped component will only accept a module that is scoped with the same Scope. For example:

    @PerActivity
    @Component(dependencies = [AppComponent::class],
            modules = [ActivityModule::class])
    interface ActivityComponent{
        fun inject(activity: MainActivity)
    }
    

    The corresponding module should only be scoped with PerActivity as well.

    class ActivityModule(activity:Activity) {
        @PerActivity
        @Provides
        fun provideActivity() = activity
    }
    

    Any other scope defined in your module that is not the same scope as your intended component will result in a compile error. Multiple scopes is not allowed as well.

    As for the component dependencies, you can use use dependencies or subcomponents. If dependencies is used, any dependency that is required by the child must be exposed by the parent. In our case above, if the ActivityComponent requires the activity context, the AppComponent must define a function that returns it. In subcomponents, just define your subcomponent in your component and the dependencies will be resolved internally.

    I have written a small guide to learning dagger 2. If you are interested, you can go check it out. https://medium.com/tompee/android-dependency-injection-using-dagger-2-530aa21961b4 https://medium.com/tompee/dagger-2-scopes-and-subcomponents-d54d58511781