ザクっと理解する @Qualifier – Android Dagger Hilt
今回の記事ではQualifierアノテーションについて紹介しています。
アノテーション自体についてはこちらに書いています。
@Qualifier
パッケージ: javax.inject
識別のために使用されます
上記は公式文章をかなり簡単に要約したものですが、私はこれだけでは全然理解できませんでした。
そのため色々試して、行き着いた結論を下記にまとめています。
@Qualifierの出番
Daggerの公式文章によると、依存関係の挿入の際にどのクラスをインスタンス化するかは、指定された型をもとに決定しています。
例えば下記の場合、モジュールで「@Provides」で宣言されている、StringとCoroutineDispatcherの型の内容がそれぞれDIされます。
// -- ExampleModule.kt --
@Module
@InstallIn(SingletonComponent::class)
object DispatchersModule {
@Provides
fun providesString(): String = "Hello World"
@Provides
fun providesDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
}
// -- Class01.kt --
// コンストラクタにString型を指定
class Class01 @Inject constructor(
private val greeting: String // Hello World がDIされる
) {
...
}
// -- Class02.kt --
// コンストラクタにCoroutineDispatcher型を指定
class Class02 @Inject constructor(
private val ioDispatcher: CoroutineDispatcher // Dispatchers.Default がDIされる
) {
...
}
モジュール内に同じ型が1つしかなければ問題ありません。
しかし、下記のように同じ型が複数存在している場合は、DaggerはどれをDIするべきか判断できません。
// -- ExampleModule.kt --
@Module
@InstallIn(SingletonComponent::class)
object DispatchersModule {
@Provides
fun providesString(): String = "Hello World"
@Provides
fun providesNewString(): String = "Hello Kotlin"
@Provides
fun providesDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
}
// -- Class01.kt --
// コンストラクタにString型を指定
class Class01 @Inject constructor(
private val greeting: String // Hello World? Hello Kotlin? どっち?
) {
...
}
// -- Class02.kt --
// コンストラクタにCoroutineDispatcher型を指定
class Class02 @Inject constructor(
private val ioDispatcher: CoroutineDispatcher // Dispatchers.Default がDIされる
) {
...
}
これを実行すると下記のようなエラーが表示されます。
[Dagger/DuplicateBindings] java.lang.String is bound multiple times:
エラー内容が続きます
...
...
...
このように、どのクラスをインスタンス化すれば良いのかの判断が型情報だけでは不十分な時に、qualifierアノテーションを使用します。
@Qualifierの使用
先ほどのエラーを解消するために、@Qualifierを使用して、どれをDIすればよいかをDaggerに伝えます。
// -- Greetings.kt --
import javax.inject.Qualifier
@Qualifier
annotation class WorldGreeting
@Qualifier
annotation class KotlinGreeting
// -- ExampleModule.kt --
@Module
@InstallIn(SingletonComponent::class)
object DispatchersModule {
@Provides
fun providesString(): String = "Hello World"
@Provides
fun providesNewString(): String = "Hello Kotlin"
@Provides
fun providesDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
}
// -- Class01.kt --
// コンストラクタにString型を指定
class Class01 @Inject constructor(
@WorldGreeting private val greeting: String // Hello World がDIされる
) {
...
}
// -- Class02.kt --
// コンストラクタにCoroutineDispatcher型を指定
class Class02 @Inject constructor(
private val ioDispatcher: CoroutineDispatcher // Dispatchers.Default がDIされる
) {
...
}
Kotlinファイルを追加し、そこに@Qualifierアノテーションを付けたアノテーションクラスを作成します。
これをコンストラクタの引数の頭につけることで、どれをDIしたいのかDaggerに伝えることができます。
上記の例だと、Greetings.ktに@Qualifierを用いたアノテーションクラスを定義しています。
ここで作成したアノテーションを25行目で指定することで、Daggerに伝えています。
おまけ
nowinandroidのように、引数の違いで伝えることもできます。
// -- Dispatchers.kt --
@Qualifier
@Retention(RUNTIME)
annotation class Dispatcher(val exampleDispatcher: ExampleDispatchers)
enum class ExampleDispatchers {
Default,
IO,
}
// -- DispatchersModule.kt --
@Module
@InstallIn(SingletonComponent::class)
object DispatchersModule {
@Provides
@Dispatcher(ExampleDispatchers.IO)
fun providesIODispatcher(): CoroutineDispatcher = Dispatchers.IO
@Provides
@Dispatcher(ExampleDispatchers.Default)
fun providesDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
}
// -- Class01 --
class Class01 @Inject constructor(
@Dispatcher(ExampleDispatchers.IO) private val ioDispatcher: CoroutineDispatcher
) {
...
}
26行目でDispatchers.IOとDispatchers.Defaultのどちらを渡すかを示しています。
上記では、@Dispatcher(ExampleDispatchers.IO)が指定されているため、17行目で宣言されているDispatchers.IOの方が挿入されます。
参考文献
・Annotation Type Qualifier – ORACLE –
・nowinandroid – GitHub Repository –
・SATISFYING DEPENDENCIES – Dagger –