본문 바로가기

[Android][Kotlin] Dagger2 #3 - @Named @Qualifier

Dagger2 #3 - @Named @Qualifier

 

Dagger 관련 글


 

@Named

Dagger2에서 제공하는제공하는 @Named annotation입니다

때로는 자료형(Type)만으로는 의존성을 식별하기에 어려운 경우에 @Named annotation을 사용해 의존성을 주입합니다

@Named는 Dagger2에서 제공하는 annotation이지만 Named는 @Qualifier로 annotate 되어있습니다

@Named 정의는 아래와 같습니다

Named.class
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {

    /** The name. */
    String value() default "";
}

 


Dagger2에 제공되는 Named 클래스에서 중요한 점은 @Qualifier annotation입니다

@Qualifierjavax.inject Package에서 제공되는 annotation으로 Named는 @Qualifier로 annotate 되어있는 구조입니다

즉, @Qualifier는 provides 메서드 구분을 위한 커스텀 Named annotation 클래스를 만들기 위한 주석이라고 생각하시면 됩니다  

@Named가 필요한 이유

  • 기본으로 @Inject 필드 변수에 의존성 주입하는 @provids 방법은 아래와 같습니다

 

Cat.kt
class Cat(val name: String)

name 프로퍼티를 갖는 Cat이라는 class가 있다고 가정하겠습니다

CatModule.kt
@Module
class CatModule {

    @Provides
    fun provideGarfield(): Cat = Cat("Garfield")

    @Provides
    fun provideHelloKitty(): Cat = Cat("Hello Kitty")
}

Cat의 의존성을 주입하는 CatModule입니다.

같은 Cat 타입을 반환하는 @Provides 메서드가 존재하는데, 서로 name프로퍼티가 다른 객체입니다  

MainActivity.kt
class MainAcitivty : AppCompatActivity() {
    @Inject
    lateinit var garfield: Cat

    @Inject
    lateinit var helloKitty: Cat

    override fun onCreate(savedInstanceState: Bundle?) {
        ...

        DaggerCatComponent.create().inject(this@MainActivity)

        Log.d("test", "Cat name is ${garfield.name}")
        Log.d("test", "Cat name is ${helloKitty.name}")
    }
}        

이제 Cat 의존성 주입을 해보겠습니다

결과는 아래와 같습니다

Error !
error: [Dagger/DuplicateBindings]
packagename.something.something.Cat is bound multiple times:

이유는 CatModule의 @Provides 메서드인 provideGarfield()provideHelloKitty() 모두 같은 Cat Type이기 때문에 Dagger는 어떤 메서드로 의존성 주입을 할지 알 수 없게 되므로 오류를 발생합니다

이렇게 같은 반환Type(여기선 Cat)의 @Provides 메서드가 여러개가 존재할 경우 구분을 위해 @Named annotation을 사용해서 위와 같은 문제를 해결합니다

 

  • 동일 반환Type의 @Provides 메서드를 사용하기위한 @Named annotation 사용
CatModule.kt
@Module
class CatModule {

    @Provides
    @Named("Garfield")
    fun provideGarfield(): Cat = Cat("Garfield")

    @Provides
    @Named("HelloKitty")
    fun provideHelloKitty(): Cat = Cat("Hello Kitty")
}

CatModule의 의존성주입 메서드인 @Provides 메서드에 @Named annotation으로 메서드구분을 위한 이름을 선언합니다

해당 의존성주입 메서드를 사용하려면 @Inject 필드에 @field:Named("name")을 같이 선언해줘서 Dagger가 동일 Type의 @Provides 메서드 중 구분이 가능합니다

MainActivity.kt
class MainAcitivty : AppCompatActivity() {
    @Inject
    @field:Named("Garfield")
    lateinit var garfield: Cat

    @Inject
    @field:Named("HelloKitty")
    lateinit var helloKitty: Cat

    ovverride fun onCreate(savedInstanceState: Bundle?) {
        ...

        DaggerCatComponent.create().inject(this@MainActivity)

        Log.d("test", "Cat name is ${garfield.name}")
        Log.d("test", "Cat name is ${helloKitty.name}")
    }
}        

@Inject로 의존성 주입을 할 멤버변수에 @field:Named() annotation을 사용함으로 Module의 동일한 반환Type의 @Provides 메서드들 중에서 선택해서 의존성 주입을 받을 수 있게됩니다

@field:Named("HelloKitty")provideHelloKitty() 를 의미합니다

즉, @Named annotation은 같은 종류의 반환Type을 가진 Provides 메서드들 중에서 선택해서 종속성을 주입하기 위해 사용하는 annotation입니다

 

Custom Named Class

커스텀 @Named을 사용하기 위해 Custome Named Class를 생성해보겠습니다

Custom Scope를 정의하는 방법처럼 annotation class로 선언해줍니다

NamedClone.kt
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class NamedClone(val value: String = "")

Custom Named 클래스입니다. 해당 클래스는 annotation class로 선언해줘야 합니다

AppleModule.kt : 공급자👇
@Provides
@NamedClone("Red Apple")
fun provideRedApple(): Apple = Apple("red")

@Provides
@NamedClone("Green Apple")
fun provideGreenApple(): Apple = Apple("green")

의존성 주입을 하는 Module내부 provides 메서드입니다

두 메서드 모두 Apple Tpye을 반환하며, 메서드 별로 @NamedClone()으로 이름을 붙여놨습니다

MainActivitiy.kt : Injector👇
@Inject
@field:NamedClone("Red Apple")
lateinit var redApple: Apple

@Inject
@field:NamedClone("Green Apple")
lateinit var greenApple: Apple

사용방법은 이전 @Named 에서 선언한 Custom Named인 @NamedClone 으로 변경된 차이점입니다

 

Enum을 사용하는 Custom Named

위의 NamedClone 클래스의 프로퍼티인 value가 String타입으로 느슨한 문자열에 의존합니다

느슨한 문자열? @Named로 설정된 문자열에 대문자 소문자가 모두 일치해야합니다
Red Apple != red apple 의미

이 방법은 오류가 발생하기 쉽기 때문에 Enum을 사용하도록 수정하겠습니다

먼저 기존의 사용방법일 경우 오류가 발생하는 예시입니다

AppleModule.kt : 공급자👇
@Provides
@NamedClone("Red Apple")
fun provideRedApple(): Apple = Apple("red")
MainActivitiy.kt : Injector👇
@Inject
@field:NamedClone("red apple")
lateinit var redApple: Apple

위와 같이 NamedClone의 value로 Red Apple을 소문자로 red apple이라 입력할 경우 컴파일에서 오류를 발생합니다

Red Apple != red apple 이기 때문입니다

 

그럼 아래는 StringEnum으로 변경하겠습니다

 

NamedClone.kt
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class NamedClone(val color: AppleType)

// Red, Green 두개의 Type만 가진 Enum 클래스
enum class AppleType {
    Red, Green
}

NamedClone의 프로퍼티 Type을 String에서 Enum 클래스 타입인 AppleType으로 변경하였습니다

AppleType은 Red, Green 두개의 값만 가진 Enum 클래스로, 그 와의 값을 입력 시 구문오류가 발생합니다

AppleModule.kt : 공급자👇
@Provides
@NamedClone(AppleType.Red)
fun provideRedApple(): Apple = Apple("red")

@Provides
@NamedClone(AppleType.Green)
fun provideGreenApple(): Apple = Apple("green")
MainActivitiy.kt : Injector👇
@Inject
@field:NamedClone(AppleType.Red)
lateinit var redApple: Apple

@Inject
@field:NamedClone(AppleType.Green)
lateinit var greenApple: Apple

Enum Type을 사용하도록 변경하였습니다.

이제 @NamedClone annotation에 Enum 클래스인 AppleType의 값이 아닌 다른 값을 입력하면 구문오류즉시 발생하기 때문에, 컴파일 전에 수정이 가능합니다