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.

Login to leave a rating/review
Download materials
Save for later

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

There are several things to consider when designing and developing an Android app. These include architecture, class hierarchy, package structure and the tech stack.

This tutorial will focus on:

  • Multi-module apps
  • Clean architecture
  • Dependency injection with Dagger

In particular, it’ll show how the above implementations work together.

You’ll also use several other tools and techniques, including:

  • Extensive application of the abstraction principle. Every entity in the project conforms to an interface. This ensures flexibility and maintainability.
  • Coroutines. Since the application is written in Kotlin, it’s based on coroutines. You can look at DomainLayerContract, particularly at the UseCase interface.
  • The inclusion of certain functional programming features, thanks to the Arrow library. You can see this on the service queries’ responses, which are typed with Either.
Note: This tutorial assumes you’re comfortable working in Kotlin and using Dagger. If not, first check out Kotlin For Android: An Introduction and Dependency Injection in Android with Dagger 2 and Kotlin.

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.

To learn about the concepts above, you’ll create an app named Numberizer, which allows you to fetch information about numbers using a public API. During the process, you’ll see how to implement a dependency injection scheme into a multi-module app, with clean architecture, from scratch.

sample project structure

Open the project in Android Studio and take a quick look at it. The app structure is consistent with the clean paradigm, which you’ll learn about soon. Once the app is running in your favorite device or emulator, type any number in the provided text box and hit the button.

Initial numberizer page

After a second, a toast message will display saying “Unknown error”.

To figure out what’s going on here, follow the code starting from MainActivity, where the button listener lambda is invoked (line 50). You’ll see the problem resides in FetchNumberFactUc:

class FetchNumberFactUc : DomainlayerContract.Presentation.UseCase<NumberFactRequest, NumberFactResponse> {
    // 1
    private lateinit var numberDataRepository: DomainlayerContract.Data.DataRepository<NumberFactResponse>

    override suspend fun run(params: NumberFactRequest?): Either<Failure, NumberFactResponse> =
        params?.let {
            // 2
            if (::numberDataRepository.isInitialized) {
                numberDataRepository.fetchNumberFact(request = params)
            // 3
            } else {
        } ?: run {


In the code above:

  1. numberDataRepository relates to a repository instance that needs to be initiated at some point.
  2. Due to this early stage of the implementation, the above variable is checked before use. You’ll change this and other similar features during this tutorial.
  3. Since there hasn’t been any initialization of the repository variable, the function returns Failure.Unknown.

There are similar defects across the project. You’ll soon sort out these problems so that Numberizer is functional. But before diving head first into the implementation, you’ll cover some key concepts.

Covering Key Concepts

This section serves as a brief overview of the foundations of the aforementioned concepts.

Multi-Module App

Creating an app comprised of multiple modules is definitely not a novelty in Android. Modules have always been available in Android Studio. However, Google hasn’t advocated for them — not much, at least — until recently, when dynamic features came out.

Organizing your logic and utilities in distinct modules provides flexibility, scalability and code legibility.

Clean Architecture

There are several options when it comes to software architecture. In this tutorial, you’ll use a class hierarchy based on the clean architecture paradigm.

Among the existing implementations of clean architecture, there are remarkable contributions such as the ones from Antonio Leiva and Fernando Cejas.

The project you’ll start off with consists of several layers. Each of these entities is in charge of certain responsibilities, which are handled in isolation. All them are interconnected through interfaces, which allows you to achieve the necessary abstraction between them.

Here’s a bit about each layer:

  • Presentation: This layer’s duties consist of managing events caused by user interactions and rendering the information coming from the domain layer. You’ll be using the well-known Model-View-Presenter (MVP) architecture pattern. This entity “sees” the domain layer.
  • Domain: This layer is in charge of the application business logic. It’s built upon use cases and repositories — see the Repository Pattern for more information. This entity only contains Kotlin code, so testing consists of unit tests. This layer represents the most inner entity, and thus it doesn’t “see” any layer other but itself.
  • Data: This layer provides data to the application (data sources). You’ll be using Retrofit for service queries. This layer “sees” the domain layer.

Using clean architectures lets you make your code more SOLID. This makes applications more flexible and scalable when implementing new functionality, and it makes testing easier.

Dependency Injection With Dagger

The last pillar of the implementation you’ll be working with is dependency injection. Although Hilt is new and Koin is gaining supporters, you’ll be using vanilla Dagger. More specifically, you’ll use dependency injection with Dagger in an easy and straightforward way.

Note: While it may seem like overkill to use these techniques for such a simple app, using clean architecture and dagger can help you to build a scalable robust architecture as your application grows.

Now that you have a better overview, it’s time to dive deeper into the theory!

Analyzing the Problem

The implementation for FetchNumberFactUc shown ealier has one main problem, apart from the obvious unwanted return value: It depends on a repository declared internally through lateinit var. This makes the class and its functions difficult to test, since those dependencies can’t be mocked and stubbed in unit tests.

To confirm this, look at FetchNumberFactUcTest. This file shows two unit tests for this use case. If you run it, you’ll see the second test fails because the assertion doesn’t succeed:

fun `Given right parameters, when usecase is invoked -- 'NumberFactResponse' data is returned`() = runBlockingTest {
    // given
    val rightParams = NumberFactRequest(number = DEFAULT_INTEGER_VALUE)
    // when
    val response = usecase.run(params = rightParams)
    // then
    Assert.assertTrue(response.isRight() && (response as? Either.Right<NumberFactResponse>) != null)

This outcome is expected, since the repository can’t be mocked and stubbed, and thus the return value remains unchanged and equal to Failure.Unknown().

The key to fixing this use case is to provide it externally with any dependency it needs:

class FetchNumberFactUc(
    private val numberDataRepository: DomainlayerContract.Data.DataRepository<NumberFactResponse>
) : DomainlayerContract.Presentation.UseCase<NumberFactRequest, NumberFactResponse> {

    override suspend fun run(params: NumberFactRequest?): Either<Failure, NumberFactResponse> =
        params?.let {
            numberDataRepository.fetchNumberFact(request = params)
        } ?: run {


Now, instead of having a lateinit var numberDataRepository that could be uninitialized, FetchNumerFactUc takes one constructor parameter – the numberDataRepository. Now you have full access to the repository instance utilized by the use case. However, this solution will force you to tell the corresponding presenter — MainPresenter in this case — which repository to use when invoked by the related view — MainActivity in this case.

That means MainActivity will have to know how to build and provide the specific type of repository this usecase needs. What a mess! This is when a developer begins to appreciate a dependency injection mechanism such as Dagger. The key is to build a container of dependencies that will deliver any instance when required.

Note: The project also includes a test file for MainPresenter, which has a failing case too. Don’t worry; you’ll address this later.

Building Up the Dagger Graph

As you may know, Dagger mainly works with components and modules. The latter are only necessary when dependency class constructors aren’t accessible and/or when an interface encapsulates the dependency class. This is the situation you’ll have to deal with, since, for example, the repository injected into FetchNumberFactUc conforms to DomainlayerContract.Data.DataRepository. You’ll see that this same thing happens to all related entities in the application.

Now you’ll analyze each module to build up the dependency graph.

Note: The starter project already includes the dependencies necessary to use Dagger in all modules. Check out the respective build.gradle files to see how.

Connecting the Data Layer

Look at the data-layer module of the project, particularly at the repository folder. Open up NumberDataRepository and you’ll see the following:

// 1
object NumberDataRepository : DomainlayerContract.Data.DataRepository<NumberFactResponse> {
    // 2
    private val numberFactDataSource: NumberFactDataSource by lazy { NumbersApiDataSource() }

Consider that:

  1. This entity is an object.
  2. It requires a NumberFactDataSource — originally instantiated internally, which is definitely not what you want.

Create a folder called “di” and a file called DatalayerModule.kt. In this file, you’ll add all the dependencies you want to make available from data-layer.

Start filling the file with the module definition of the repository you’ll inject:

object RepositoryModule {

    @Named(DATA_REPOSITORY_TAG) // 1
    // 2
    fun provideDataRepository(
        numberFactDs: NumberFactDataSource
    ): @JvmSuppressWildcards // 3
 DomainlayerContract.Data.DataRepository<NumberFactResponse> =
        // 4
        NumberDataRepository.apply { numberFactDataSource = numberFactDs }


Follow Android Studio hints to add the necessary library imports (Alt-Enter/Command-Enter). Once done, pay special attention to the following:

  1. The @Named annotation allows discriminating between classes that conform to the same type. By default, in this sample application, all injections use this technique, although it’s not always necessary.
  2. The function name declared isn’t important, since you won’t invoke it from any part of your code. Dagger will use it internally when a DomainlayerContract.Data.DataRepository instance is required. The function includes a NumberFactDataSource instance as its input argument. It’s your duty to tell Dagger how to construct this named data source.
  3. Dagger is written in Java and that’s why the compiler needs some extra annotations to translate from Kotlin. Due to type erasure, @JvmSuppressWildcards is needed when using generics.
  4. Here’s where you define how the repository instance will be built. In this case, since it’s a singleton, any required variable initialization will take place in an apply block.

You’ll see an error when you try to assign numberFactDataSource since the variable is private. To fix the issue, open NumberDataRepository and replace the private val numberFactDataSource: NumberFactDataSource by lazy { NumbersApiDataSource() } line with lateinit var numberFactDataSource: NumberFactDataSource.

Note: When declaring an interface dependency, Dagger recommends using the @Binds annotation. In this tutorial, you won’t use this approach. The idea is to provide a common way to define modules.

Populing the Data Layer

Now, using the same approach, add the rest of the data-layer dependencies. To do this, copy the following snippet at the end of the DatalayerModule file you just created, below the RepositoryModule object, taking care to include the necessary imports:

private const val TIMEOUT = 10L

class DatasourceModule {

    fun provideNumberFactDataSource(ds: NumbersApiDataSource): NumberFactDataSource = ds // 1

    fun provideRetrofitInstance(): Retrofit = Retrofit.Builder()


fun getHttpClient(): OkHttpClient {
    val interceptor = HttpLoggingInterceptor()
    if (BuildConfig.DEBUG) {
        interceptor.level = HttpLoggingInterceptor.Level.BODY
    } else {
        interceptor.level = HttpLoggingInterceptor.Level.NONE
    return OkHttpClient.Builder()
        .connectTimeout(TIMEOUT, TimeUnit.SECONDS)
        .readTimeout(TIMEOUT, TimeUnit.SECONDS)

You may have seen that certain definitions aren’t yet present. More specifically, provideNumberFactDataSource expects a NumbersApiDataSource as its input argument. You’ll tell Dagger how to build this class instance adding an @Inject annotation to its constructor.

Open NumberFactDataSource.kt and modify NumbersApiDataSource so it looks like the following:

class NumbersApiDataSource @Inject constructor(private val retrofit: Retrofit) :
    NumberFactDataSource {

    override suspend fun fetchNumberFact(request: NumberFactRequest): Response<String> =
              number = request.number.toString(),
              category = request.category.toString().toLowerCase(Locale.ROOT)))


Now the constructor includes retrofit as a dependency. Go ahead and delete the getRetrofitInstance() and provideHttpClient() methods in the file, since you already added them when editing DatasourceModule.

Domain Layer

It’s time to rearrange domian-layer. Navigate to that module and create a folder called “di” and a file called DomainlayerModule.kt. Add all available dependencies from domain-layer.

Copy and paste the following snippet into the file:

object UsecaseModule {

    fun provideFetchNumberFactUc(usecase: FetchNumberFactUc): @JvmSuppressWildcards DomainlayerContract.Presentation.UseCase<NumberFactRequest, NumberFactResponse> =


There’s only one dependency — a use case of type DomainlayerContract.Presentation.UseCase — available for MainPresenter. Later you’ll tell Dagger how to build FetchNumberFactUc.

Open MainPresenter.kt and substitute its constructor with:

class MainPresenter @Inject constructor(
    @Named(MAIN_VIEW_TAG) private val view: MainContract.View, // 1
    @Named(FETCH_NUMBER_FACT_UC_TAG) private val fetchNumberFactUc: @JvmSuppressWildcards DomainlayerContract.Presentation.UseCase<NumberFactRequest, NumberFactResponse> // 2
) : MainContract.Presenter {

In the code above, usecase is injected through the class constructor instead of being initialized internally. In fact, you can see Dagger will inject two definitions:

  1. a view, and
  2. a use case

Remove the line private val fetchNumberFactUc ... from the MainPresenter since you’re now supplying that dependency via constructor arguments.
You also need to remove the line in onDetach() that sets view to null.

Finally, update the constructor for FetchNumberFactUc so dagger can instantiate it. Replace the constructor with the following:

class FetchNumberFactUc @Inject constructor(

You’ve added the @Inject annotation, which will allow Dagger to automatically construct instances of the class.

You haven’t yet told Dagger how to provide the view, but that will come later.

Before you move on to the presentation-layer, you need to quickly tweak the constructor for the FetchNumberFactUc class. Open FetchNumberFactUc and replace the class header with the following:

class FetchNumberFactUc @Inject constructor(
    private val numberDataRepository: @JvmSuppressWildcards DomainlayerContract.Data.DataRepository<NumberFactResponse>
) : DomainlayerContract.Presentation.UseCase<NumberFactRequest, NumberFactResponse> {

Adding the @Inject annotation to the constructor and the @Named annotation to the data repository will give Dagger all of the information it needs to construct an instance of FetchNumberFactUc.

Presentation Layer

Last but not least, the dependencies available in the presentation-layer module are views — in Android, Activity entities — and presenters.

However, stop one moment to declare a few custom scopes. Navigate to presentation-layer and create a folder called “di” and a file called Scopes.kt. Fill it with the following content:

import javax.inject.Scope

annotation class ApplicationScope

annotation class ActivityScope

You’ll be using these scopes throughout the implementation so that Dagger doesn’t leak any instance.

Note: If you want to know more about scopes in Dagger, check out the official documentation about this topic.

Once done, create another file inside “di” called PresentationlayerModule.kt. Since there are two features — “Splash” and “Main” — you’ll declare two different modules:

class SplashModule(private val activity: SplashActivity) {  // 1

    fun provideSplashView(): SplashContract.View = activity // 2

    fun provideSplashPresenter(presenter: SplashPresenter): SplashContract.Presenter = presenter // 3


class MainModule(private val activity: MainActivity) { // 1

    fun provideMainView(): MainContract.View = activity // 2

    fun provideMainPresenter(presenter: MainPresenter): MainContract.Presenter = presenter // 3


Here’s a breakdown of the code above:

  1. There’s an important change in the above definition, since both modules have an argument in the constructor. Don’t worry about this for now. Later, when building the dependency graph, you’ll tell Dagger how to instantiate these modules.
  2. Bear in mind that the functions providing the views use these input arguments.
  3. To properly build the presenters, you’ll modify the constructors editing their definition files.

In fact, you already did this with MainPresenter, so open SplashPresenter and replace the constructor with:

class SplashPresenter @Inject constructor(
    @Named(SPLASH_VIEW_TAG) private val view: SplashContract.View?
) : SplashContract.Presenter {

Don’t forget to remove the line view = null in onDetach(), since this field is no longer mutable.

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:

@Subcomponent(modules = [SplashModule::class]) // 1
interface SplashComponent {
    // 2
    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:

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

    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
    modules = [UtilsModule::class, PresentationlayerModule::class, UsecaseModule::class, RepositoryModule::class, DatasourceModule::class])
interface ApplicationComponent {
    // 3
    interface Factory {
        fun create(modules: UtilsModule): ApplicationComponent
    // 4
    fun splashComponentFactory(): SplashComponent.Factory
    fun mainComponentFactory(): MainComponent.Factory

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

    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() {
        // 2
        appComponent = DaggerApplicationComponent.factory().create(modules = UtilsModule(ctx = this))
    // 3
    override fun provideSplashComponentFactory(): SplashComponent.Factory =

    override fun provideMainComponentFactory(): MainComponent.Factory =


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
lateinit var presenter: SplashContract.Presenter

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

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:

lateinit var presenter: MainContract.Presenter

override fun onCreate(savedInstanceState: Bundle?) {
    viewBinding = ActivityMainBinding.inflate(layoutInflater)

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.


There’s one minor thing you have to do to complete this tutorial: Fix the tests that were failing at the beginning.

There are three test files in the project, one per module: MainPresenterTest, FetchNumberFactUcTest and NumberDataRepositoryTest. All of them comprise unit tests of certain functionalities.

Note: If you’re interested in refreshing what you know about unit tests in Android, check out Android Unit Testing with Mockito.

Testing the Presenter

If you open MainPresenterTest.kt and run the tests, you’ll see one is failing. Scroll down and you’ll find a // TODO: which tells you what the problem is: The use case variable isn’t accessible. But after the changes you made in this tutorial, this should no longer be a problem. Go to the @Before section and replace the mainPresenter initialization with this one:

mainPresenter = MainPresenter(view = mockView, fetchNumberFactUc = mockUsecase)

That’s all! Now, since the presenter receives all the dependencies in the constructor, you can mock anything you want. Note that you haven’t even initialized Dagger whatsoever.

Now that you’ve fixed the main presenter tests, it’s time to fix up the FetchNumberFactUcTest you looked at earlier.

Testing the Use Case

To fix the use case test that was failing — FetchNumberFactUcTest — you only need to make a few changes. First, you’ll see there’s an error when instantiating the use case in the @Before section. Change it to the following:

usecase = FetchNumberFactUc(numberDataRepository = mockRepository)

After that, scroll down to the failing test and stub the repository so that it returns a known value that can be assessed. Place the following below rightParams, in the // given subsection:

whenever(mockRepository.fetchNumberFact(request = rightParams)).doReturn(NumberFactResponse("None").right())

Easy peasy, isn’t it? Now you can run the tests and you’ll see them all pass. This means that the use case is doing what you expect, i.e. validating your logic.

Testing the Repository

Open NumberDataRepositoryTest.kt and run the tests. As you can see, all of them pass. :] You might have not seen it when you started the tutorial, but this file contained two errors (lines 36 and 53). The problem occurred when trying to initialize the repository and passing a value for numberFactDataSource, which was immutable at that time. The changes you made solved these issues.

Where to Go From Here?

You can download the final version of the project using the Download Materials button at the top or bottom of the tutorial.

There are several good references for the main topics addressed in this article:

  • In addition to the links further up, this Clean Architecture Tutorial for Android: Getting Started tutorial is a great primer to this topic.
  • When it comes to dependency injection using Dagger, don’t hesitate to look at the official site. It’s updated often.
  • Implementing multi-module apps isn’t a paradigm, but it’s a good practice if you want a proper separation of concerns. You can read this article for more about it, and don’t forget to look at dynamic features if you feel brave. :]

Recently, Android added Dagger to the official documentation, so there are a good number of references.

Perhaps the best thing you can do after reading this article and completing the exercise is to migrate one of your own projects. In doing so, you’ll see which difficulties and challenges may arise in a real application. Have a look at the tips provided and use them to make your apps more flexible and reusable.

We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!