Dagger in Multi-Module Clean Applications

In this tutorial, you’ll learn how to integrate Dagger in an Android multi-module project built using the clean architecture paradigm. By Pablo L. Sordo Martinez.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Getting Familiar with Components and Subcomponents

Dagger is mainly built upon modules and components, but it recently added a third entity: subcomponents. According to the documentation, Subcomponents are components that inherit and extend the object graph of a parent component.

In this application, you’ll create a component called ApplicationComponent and two subcomponents called SplashComponent and MainComponent.

The idea behind this approach is:

  • ApplicationComponent will include all dependencies declared in both data-layer and domain-layer. It’s the container of entities ready to be injected when needed. It’ll also include the subcomponents, but in a particular way you’ll see later.
  • SplashComponent and MainComponent will comprise the dependencies corresponding to the respective feature in the presentation-layer.
  • You’ll implement a mechanism to build the dependency graph through ApplicationComponent.
  • Moreover, you’ll define an approach to inject any of the subcomponents. This will trigger any dependency insertion in your code, since the subcomponents will have access to all the entities in ApplicationComponent.

Buliding Activity Components

Navigate again to PresentationlayerModule.kt and add the following snippet:

@ActivityScope
@Subcomponent(modules = [SplashModule::class]) // 1
interface SplashComponent {
    // 2
    @Subcomponent.Factory
    interface Factory {
        fun create(module: SplashModule): SplashComponent
    }
    // 3
    fun inject(activity: SplashActivity)

}
// 4
interface SplashComponentFactoryProvider {
    fun provideSplashComponentFactory(): SplashComponent.Factory
}

It’s especially remarkable that:

  1. A subcomponent definition uses the @Subcomponent annotation. It can be comprised of any Dagger module you want. In this case, it’s SplashModule.
  2. Since the included module has a non-empty constructor, you need to declare a subcomponent factory to build it.
  3. The subcomponent will be injected into SplashActivity only.
  4. SplashComponentFactoryProvider is the mechanism you’ll use with this subcomponent — more on this later.

Similar to what you did with the previous subcomponent, add the following snippet to PresentationlayerModule.kt:

@ActivityScope
@Subcomponent(modules = [MainModule::class])
interface MainComponent {

    @Subcomponent.Factory
    interface Factory {
        fun create(module: MainModule): MainComponent
    }

    fun inject(activity: MainActivity)

}

interface MainComponentFactoryProvider {
    fun provideMainComponentFactory(): MainComponent.Factory
}

The approach is similar, and the two subcomponents are ready to be used. However, as stated before, you need to include them inside ApplicationComponent. You can achieve this by including them inside a new Dagger module. Take the same file and add this snippet:

@Module(subcomponents = [SplashComponent::class, MainComponent::class])
object PresentationlayerModule

PresentationlayerModule is a module consisting of two subcomponents, which come with their respective dependencies.

Building the Application Component

Time to implement the component that will build the application graph. Navigate to app and create a folder called “di” and a file called ApplicationGraph.kt. Then add the following:

@ApplicationScope // 1
// 2
@Component(
    modules = [UtilsModule::class, PresentationlayerModule::class, UsecaseModule::class, RepositoryModule::class, DatasourceModule::class])
interface ApplicationComponent {
    // 3
    @Component.Factory
    interface Factory {
        fun create(modules: UtilsModule): ApplicationComponent
    }
    // 4
    fun splashComponentFactory(): SplashComponent.Factory
    fun mainComponentFactory(): MainComponent.Factory

}
// 5
@Module
class UtilsModule(private val ctx: Context) {

    @ApplicationScope
    @Provides
    fun provideApplicationContext(): Context = ctx

}

It’s important to note that:

  1. The scope of this component is set to @ApplicationScope, which is wider than any other scope in the app.
  2. The list of modules composing this component includes all the implementations seen thus far and a new module called UtilsModule, which is defined at the end of the file.
  3. Since UtilsModule will have a non-empty constructor, you need to include a component factory.
  4. If the component includes any subcomponent, as in this case, you’ll need to expose it if you want to use it later.
  5. UtilsModule is a standard way to expose the application context as a dependency in Dagger.

Wrapping Everything Up

After defining all the required Dagger entities, now you’ll build the application graph. Navigate to app, open BaseApplication.kt and replace the class definition with the following:

class BaseApplication : Application(), SplashComponentFactoryProvider, MainComponentFactoryProvider { // 1
    
    private lateinit var appComponent: ApplicationComponent

    override fun onCreate() {
        super.onCreate()
        // 2
        appComponent = DaggerApplicationComponent.factory().create(modules = UtilsModule(ctx = this))
    }
    // 3
    override fun provideSplashComponentFactory(): SplashComponent.Factory =
        appComponent.splashComponentFactory()

    override fun provideMainComponentFactory(): MainComponent.Factory =
        appComponent.mainComponentFactory()

}

The above implementation needs a few comments:

  1. In addition to extending Application, BaseApplication implements the two factory provider interfaces defined for each of the subcomponents. This means you’ll use BaseApplication to build subcomponents and inject dependencies.
  2. The appComponent variable holds a reference to DaggerApplicationComponent. Dagger is responsible for creating this instance when the project compiles. Use the component factory to indicate any external dependency, such as the application context.
  3. You can override the component factory functions using the dependencies exposed when defining ApplicationComponent.

Once you’ve indicated how to build the application graph, trigger the dependency chain in the application entry points. In Android, this is normally done in the activities.

Open SplashActivity and delete the presenter variable (line 34). Then, copy the following snippet into the class definition:

// 1
@Inject
@Named(SPLASH_PRESENTER_TAG)
lateinit var presenter: SplashContract.Presenter

override fun onCreate(savedInstanceState: Bundle?) {
    getSplashComponent().inject(this) // 2
    super.onCreate(savedInstanceState)
}

In the code above:

  1. This annotation tells Dagger that this class needs an instance of a SplashContract.Presenter with SPLASH_PRESENTER_TAG as the qualified name.
  2. You need to tell Dagger where to take the above dependency from. You can do this by including an extension function in the same file and invoking it from onCreate.
private fun SplashActivity.getSplashComponent(): SplashComponent =
    (application as SplashComponentFactoryProvider).provideSplashComponentFactory().create(module = SplashModule(this))

Now, repeat the process with MainActivity. Open the class, remove the presenter variable and replace the onCreate function with:

@Inject
@Named(MAIN_PRESENTER_TAG)
lateinit var presenter: MainContract.Presenter

override fun onCreate(savedInstanceState: Bundle?) {
    getMainComponent().inject(this)
    super.onCreate(savedInstanceState)
    viewBinding = ActivityMainBinding.inflate(layoutInflater)
    initView()
    setContentView(viewBinding.root)
}

Once again, you’d better use an extension function. Add the following implementation at the end of the file for getMainComponent():

private fun MainActivity.getMainComponent(): MainComponent =
    (application as MainComponentFactoryProvider).provideMainComponentFactory()
        .create(module = MainModule(this))

And that’s all. You’ve finished implementing Numberizer. Congratulations!

Note: Before proceeding to the next section, don’t forget to build the project. Dagger needs this step to create compile-time entities, such as DaggerApplicationComponent.

Performance Assessment

Once completed, your app should be functional, like in the following demo video:

When started, the splash screen appears for a few seconds, and then it jumps to main view. At this screen, the user can select a category from a drop-down menu and type a number. When the button gets tapped, and after some loading, a message displays showing a fact related to the number introduced.