# Lock Your Dagger in Gradle Modules for Stability

[Thomas Mlynaric](https://www.strv.com/blog/authors/tomas) Android Engineer

---

You finally decided to split your monolithic Gradle module, into smaller ones. But have you considered splitting your almighty Dagger AppComponent as well?

The time has come. You've finally decided to split your monolithic Gradle module into smaller ones. But have you considered splitting your almighty Dagger **AppComponent** as well? Everything works, so why bother, right?

**Note:** In the article, references to modules refer to Gradle modules and not to Dagger modules.

## Dependency Encapsulation (API vs. Implementation)

Imagine you have 3 modules `:app`, `:feature` and `:library` as in the picture below.

Your `:app` module depends on the `:feature` module in the usual way:

`implementation(project(":feature"))`

and the `:feature` module depends on some external `:library`:

`implementation(project(":library"))`

**Note:** You may have a similar setup if you follow layer-based modularization.

You chose the `:library` because it contains some awesome `LibraryManager` you want to use in your codebase. Suddenly, your inner voice reminded you of the [Dependency inversion principle](https://en.wikipedia.org/wiki/Dependency_inversion_principle?ref=strv.ghost.io), so you encapsulated the `LibraryManager` into your own class `FeatureRepository`. Right after you stopped writing, the inner voice whispered again. “This time check [Inversion of Control](https://en.wikipedia.org/wiki/Inversion_of_control?ref=strv.ghost.io),” it said. And so you added `@Inject` to the constructor of the `FeatureRepository`.

```kotlin
class FeatureRepository @Inject constructor(
	private val libraryManager: LibraryManager
) {

    fun doSomething() {
        libraryManager.doSomething()
    }
}
```

Since there was no more whispering, you compiled the project… and it failed.

`LibraryManager cannot be provided without an @Inject constructor or an @Provides-annotated method.`

Oh yeah, you forgot — Dagger doesn’t know anything about `LibraryManager`.

You need to create Dagger `@Module` inside your `:feature` Gradle module which `@Provides` the `LibraryManager`.

```kotlin
@Module
object FeatureModule {
    @Provides
    fun provideLibraryManager() = LibraryManager()
}
```

Now it should be fine, right? You don’t have any `import` from the library in `:app` module — only your `FeatureRepository`.

You compile again … and it fails, again.

`DaggerAppComponent.java:26: error: cannot access LibraryManager`

I'll spare your time chasing the error reason. The problem is that **Dagger generates the code composition only in a class implementing** **`@Component`** **interface**. The code is generated within the `:app` module and attempts to instantiate `LibraryManager` from the `:library`. It doesn't “see” it though, because of Gradle's **`implementation(:library)`**.

The same would happen if you wanted to access the class manually from your `:app` module. But in this case, IDE error inspection would give you a hint.

The picture below explains what happens graphically.

**What can you do about it?**

The simplest solution is to change `implementation` to `api` in Gradle file and it's done 🎤 ⤵️. But please, don't 🙏. It breaks encapsulation — making `FeatureManager` visible to all of your modules! Also, each time `:library` ABI changes, Gradle will recompile each module depending on the `:feature` module!

The proper solution is a bit harder than a one-line fix. I’ll show you how to do it — but first, I’d like to throw more wood into the fire by describing a similar situation: Encapsulating your own code.

## Implementation Encapsulation (Public vs. Internal)

Imagine the same project with `:app` and `:feature` modules (we'll forget about `:library` for this example). You want your `FeatureRepository` to be `public` interface and implementation `FeatureRepositoryImpl` to be `internal`, so that nothing but the `:feature` module can access its guts.

The picture and code below represent the situation:

```kotlin
interface FeatureRepository {
    fun doSomething()
}

internal class FeatureRepositoryImpl @Inject constructor() : FeatureRepository {
    override fun doSomething() {
        // omitted
    }
}
```

In this case, you don’t have to explicitly use `@Provides` for your `FeatureRepository`, but you need to tell Dagger the relation between implementation and its interface with `@Binds` (`@Provides` would also work, but it's less performant).

```kotlin
@Module
abstract class FeatureModule {
    @Binds
    abstract fun bindsFeatureRepository(impl: FeatureRepositoryImpl): FeatureRepository
}
```

As you’ve guessed by now, you will face the same problem as before. But this time, even the IDE error inspection tells you that you can’t do that:

`public function exposes its internal parameter type FeatureRepositoryImpl`

**What can you do about it?**

Again, the simplest solution is to make `FeatureRepositoryImpl` as `public`. The question then is whether you get any benefit from implementing the interface at all. Also, if you wanted to hide some different implementation of the same interface, you can't. It has to be `public`.

## Encapsulate Dagger in Gradle Module

To encapsulate dependencies in Gradle modules, you need to create a separate Dagger `@Component` in each module. You can't create `@Subcomponent` for this, because the subcomponent generates code within its parent component (and therefore still in the `:app`).

Technically, encapsulating `internal` implementations would work with `@Subcomponent`. Java doesn't have `internal` visibility modifier and therefore classes [compiled from Kotlin are](https://kotlinlang.org/docs/java-to-kotlin-interop.html?ref=strv.ghost.io#visibility) `public`. But if you want to encapsulate a 3rd-party dependency, the problem will be the same — it won't compile.

To kill two birds with one stone, we have to stick to component dependencies.

First, we need to create `FeatureComponent` and explicitly specify what it can provide outside of the component — this is Dagger's kind of `public`/`private` visibility modifier:

```kotlin
@Component(modules = [FeatureModule::class])
interface FeatureComponent {
    @Component.Factory
    interface Factory {
        fun create(): FeatureComponent
        // explicitly specify, otherwise can't provide outside of the component
        val featureRepository: FeatureRepository
    }
}
```

Then, add the component to `dependencies` list in `AppComponent`:

```kotlin
@Component(
    dependencies = [FeatureComponent::class]
)
@Singleton
interface AppComponent {
    @Component.Factory
    interface Factory {
        fun create(
            featureComponent: FeatureComponent,
        ): AppComponent
    }
}
```

And finally, instantiate both components in your `Application` and pass `FeatureComponent` to `AppComponent`'s factory method:

```kotlin
val featureComponent = DaggerFeatureComponent.factory().create()

val appComponent = DaggerAppComponent
    .factory()
    .create(featureComponent)
```

You can now enjoy encapsulated dependencies 🎉. You no longer have to worry about someone using an implementation instead of an abstraction. Nor do you need to worry about polluting your codebase with some library code.

This setup allows both examples from the beginning of the article to work.

## Need Scope?

There’s one catch though (there always is, right? 😅). If you want to have your `FeatureComponent` scoped (e.g. `@FeatureScope`), the compilation will fail again.

`AppComponent.java:7: error: This @Singleton component cannot depend on scoped components: @FeatureScope FeatureComponent`

This looks like a dead-end, right? Well, not entirely.

Dagger component is a simple annotated interface (or abstract class) with generated implementation. We can [trick Dagger compiler to skip](https://github.com/google/dagger/issues/1225?ref=strv.ghost.io#issuecomment-405602591) the scope checking by passing dependency to a “contract” instead of the real component.

It works like this: `AppComponent` depends on `FeatureComponentContract`, which is implemented by the `FeatureComponent`. This way, `AppComponent` doesn't depend directly on the `FeatureComponent`. Since the contract is just a plain interface, Dagger won’t complain about scopes. At the same time, it will honor the annotations of the components and will generate their implementations.

However, we will have to move the `featureRepository: FeatureRepository` definition to the contract interface. This way, `AppComponent` knows that it can get the `featureRepository` from the contract and doesn't care about what implements the contract.

There won't be any change in your `Application` class, because everything accepts the same interface.

Visually, it looks like this:

Code representation of `FeatureComponent` with `FeatureComponentContract` looks like this:

```kotlin
interface FeatureComponentContract {
    val featureRepository: FeatureRepository
}

@Component(modules = [FeatureModule::class])
@FeatureScope
interface FeatureComponent : FeatureComponentContract {

}
```

and finally, `ApplicationComponent`:

```kotlin
@Component(
    dependencies = [FeatureComponentContract::class] // dependency on contract
)
@Singleton
interface AppComponent {
    val featureComponent: FeatureComponentContract // dependency on contract
}
```

This way, you can have Dagger component in each Gradle module and properly encapsulate your code. Each of your components can hold “local singletons.” Your `FeatureComponent` can hold objects annotated with `@FeatureScope`, and `AppComponent` can hold `@Singleton`.

There's one trick for those who’ve just started to modularize their projects and have `@Singleton` across the whole codebase. You can have the `FeatureComponent` annotated with `@Singleton` instead of a custom scope. **Be careful, though!** This may result in your app containing multiple instances of the same class annotated with `@Singleton`, because **each scoped class is held in its component**. If you have some `@Module` which `@Provides` the same `@Singleton` object inside of multiple components, it will be held as a singleton in each of those components.

## Conclusion

Nowadays, many apps have multiple Gradle modules — but stick to one Dagger component. If you want more control over the visibility of your dependencies, you should consider locking Dagger component in each Gradle module.

PS: This setup is also great for dynamic feature modules, but more about that next time. 🤫

**Thank you to Iveta Jurcikova and Marek Abaffy for reviewing the article.**

---

**Don't miss anything**