Android / Annotations / Kotlin / Programming

ザクっと理解する @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 –

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です