Kotlin+Dagger2で起きた問題、おそらくJavaではほとんど起こらないだろう問題ではあるが全く起きないわけではなさそう。
前提
Dagger2(に限らずJSR330に準拠したDIコンテナー)で目的の違う同じ型のインスタンスを注入したい場合には @Named
を使うか、 @Qualifier
アノテーションを付与したアノテーションを定義してやる必要がある。
Dagger2の場合、Moduleの @Provides
を付与した依存提供用のメソッドに @Qualifier
を付与したアノテーションを付与する
data class User(val name: String) @Qualifier annotation class PrimaryUser @Qualifier annotation class SecondaryUser class Module { @Provides @PrimaryUser fun providePrimaryUser() : User = User("takuji31") @Provides @SecondaryUser fun provideSecondaryUser() : User = User("takuji24884") }
この提供されている依存をpropertyに注入する時、次のように書きたくなるはずである。
class UserActivity : AppCompatActivity() { @Inject @PrimaryUser lateinit var primaryUser: User @Inject @SecondaryUser lateinit var secondaryUser: User }
こうすることで、fieldがJavaからはpublicに見えるので(internalにしてpackage privateにした方がよいかもしれないが)、Dagger2のfield injectionが使えるはず。
ところがこれをビルドすると、どちらも依存が解決できないと言われてエラーになる。
何が起こっているか
Kotlinのpropertyは基本的に backing field + getter + setter が組み合わさってできている。
そこで、propertyにアノテーションを付与するとコンパイラーはどこに指定すればいいか分からず、getterにアノテーションを付与するのである。
getterの次にfield、最後にsetterになるようだ。
この結果はstub生成の様子を見ると分かる build/tmp/(kapt|kapt3)/stub)
。
今回はfield injectionを想定しているので、fieldに @PrimaryUser
や @SecondaryUser
が付与されている必要があるのだが、これだとfieldではなくgetterにアノテーションが付与されてしまうので、依存が解決できずビルドエラーになるのだ。
回避策
もしこのアノテーションがライブラリーで用意されているものだとしたら、リポジトリに修正のPRを投げつつ以下のようにするとよいだろう。
class UserActivity : AppCompatActivity() { @field:[Inject PrimaryUser] lateinit var primaryUser: User @field:[Inject SecondaryUser] lateinit var secondaryUser: User }
こうすることで明示的にfieldにアノテーションを付与できる。
根本的な解決
そもそもQualifierを付与したアノテーションを付与できる先を限定しておくべきである(意図しないところで使われる可能性がある)ので、そのために @Target
を指定しておくべきである。
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FUNCTION) @Qualifier annotation class PrimaryUser
ここで使っている @Target
は Kotlin側で用意されているもので、Javaのものより柔軟にターゲットを指定できる。
この場合はフィールド、メソッドやコンストラクターの引数、関数(メソッド、setter、getter)そのものに指定できるようになる。
なお、AnnotationTargetの指定順に付与される模様?
最後に
今回はQualifierに限ったような書き方になったが、基本的にアノテーションのターゲットはちゃんと指定してやるべきだろう。