Help us understand the problem. What is going on with this article?

Kotlin x Daggerで具象クラスのInjectionを防止する

More than 1 year has passed since last update.

はじめに

Androidのレイヤードアーキテクチャにおいて、各層の依存方向を強制させるためにモジュールを分割するのはよく採用される手法です。

domain/build.gradle
dependencies {
    // data には依存しない!
}
data/build.gradle
dependencies {
    implementation project(':domain')
}

また、DIP (依存性逆転原則) の概念にのっとって domain に用意したインターフェースを data で実装し、それをDaggerを使ってInjectionするというのも、これまたお馴染みの手法です。

domain/user/UserRepository.kt
interface UserRepository { ... }
data/repository/UserRepositoryImpl.kt
@Singleton
class UserRepositoryImpl @Inject constructor() : UserRepository { ... }
app/di/modules/DataModule.kt
@Module
interface DataModule {
    @Binds
    fun bindsUserRepository(repository: UserRepositoryImpl): UserRepository
}

問題

上記の状況下において、Daggerのオブジェクトグラフを構築するモジュールはすべてのモジュールへ依存している必要があります。

app/build.gradle
dependencies {
    implementation project(':domain')
    implementation project(':data')
}

これは以下のコードが書けてしまうことを意味します。

app/ui/users/UsersViewModel.kt
class UsersViewModel @Inject constructor(
    private val userRepository: UserRepositoryImpl // <- 具象クラスをInjectionできる!
)

app モジュールでは data で定義されている具象クラスをInjectionしたいケースはないはずです。そのため、気持ちとしては data に依存させたくないところですが、それだとDaggerがインスタンスを作ることができないというジレンマに陥ってしまいます。

解決法

抽象と具象の @Bindsdata モジュールに移し、具象クラスの可視性を internal に設定します。

data/di/RepositoryModule.kt
@Module
abstract class RepositoryModule {
    @Binds
    abstract internal fun bindsUserRepository(repository: UserRepositoryImpl): UserRepository
}
app/di/modules/DataModule.kt
@Module(includes = arrayOf(RepositoryModule::class))
interface DataModule
data/repository/UserRepositoryImpl.kt
@Singleton
internal class UserRepositoryImpl @Inject constructor() : UserRepository { ... }

Kotlinのinternalクラスは、Javaから見るとpublicに見えます (メソッドは難読化されますが)。
このため、Java世界の住人であるDaggerは UserRepositoryImpl のインスタンスを作成することができます。
一方、我々Kotlin世界の住人にとって UserRepositoryImpl は別モジュールのinternalクラスのため、参照することはできません。

app/ui/users/UsersViewModel.kt
class UsersViewModel @Inject constructor(
    private val userRepository: UserRepositoryImpl // <- コンパイルエラー!
)

このように internal の挙動差を利用することで、モジュールとしては依存しつつも具象クラスのInjectionを防止することができます。

おわりに

この手法は最近ジョインしたプロジェクトの同僚が使っていたものなのですが、非常に便利で応用が効くなと思ったので許可を得て記事化しました。

お役にたてれば幸いです。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away