Android

[Android] hilt 사용 시 동일 객체의 중복 바인딩을 허용하는 방법

참깨빵위에참깨빵_ 2024. 6. 12. 00:06
728x90
반응형

같은 타입인 객체의 중복 바인딩을 유지하면서 사용해야 할 경우가 있다. 예를 들어 base url이 서로 다른 레트로핏 객체를 생성하고 싶을 수 있다.

그러나 hilt는 같은 타입의 객체를 바인딩하려고 하면 DuplicateBindings 태그가 포함된 에러가 발생한다.

이 때 사용할 수 있는 hilt 어노테이션이 2가지 있다.

 

  • Qualifier
  • Named

 

각 어노테이션의 사용 방법을 간단하게 확인한다. 프로젝트에 hilt를 사용하도록 설정하는 건 생략한다.

먼저 Qualifier 어노테이션을 쓰기 전 아래 클래스들을 미리 작성해둔다.

 

import javax.inject.Inject

class Bar @Inject constructor() {}
class Foo constructor(val name: String) {}
import javax.inject.Qualifier

@Qualifier
annotation class MyQualifier
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent

@Module
@InstallIn(SingletonComponent::class)
object FooModule {

    @MyQualifier
    @Provides
    fun provideFirstFoo(): Foo {
        return Foo(name = "first foo")
    }

    @Provides
    fun provideSecondFoo(): Foo {
        return Foo(name = "second foo")
    }

}

 

참고로 annotation class에 @Qualifier 말고도 @Retention(AnnotationRetention.BINARY)를 쓰는 경우가 있다.

이걸 쓰는 이유는 @Qualifier의 기본 Retention 정책은 RUNTIME이기 때문에, 다른 정책을 사용하려면 이 어노테이션을 명시해야 한다.

참고로 BINARY, RUNTIME 값의 효과는 아래와 같다.

 

  • RUNTIME : 런타임에도 어노테이션 정보를 유지. Qualifier 사용 시 반드시 명시할 필요는 X
  • BINARY : 컴파일 타임까지 어노테이션 정보를 유지하지만 런타임에는 유지하지 않음

 

성능 최적화와 관련된 속성이기 때문에 관심이 있다면 따로 찾아보자. 여기선 자세히 확인하지 않고 넘어간다.

이후 액티비티에서 생성한 의존성을 사용한다.

 

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    
    private val TAG = this::class.simpleName
    
    @Inject
    lateinit var foo: Foo
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
    
    @Inject
    fun injectFoo(foo: Foo) {
        Log.e(TAG, "injectFoo() - foo name : ${foo.name}")
        this.foo = foo
    }
    
}

 

이러고 실행하면 어떤 provide 함수가 호출될까?

실행해 보면 second foo가 호출된다. 의도했던 건 provideFirstFoo()를 호출하는 것이었는데 provideSecondFoo()가 호출된 것이다.

 

 

메서드 주입 방식으로 Foo 인스턴스를 주입받았는데, 이 때는 매개변수에도 내가 만든 커스텀 Qualifier를 써줘야 의도한 대로 provideFirstFoo()가 호출될 수 있다.

 

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    private val TAG = this::class.simpleName

    @Inject
    lateinit var foo: Foo

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    @Inject
    fun injectFoo(@CustomQualifier foo: Foo) { // @CustomQualifier 추가
        Log.e(TAG, "injectFoo() - foo name : ${foo.name}")
        this.foo = foo
    }

}

 

 

추가로 CustomQualifier에 간단한 값 지정을 할 수 있다.

User 클래스를 새로 만들고 이 클래스를 제공하는 모듈, 어노테이션 클래스도 새로 만든다.

 

@Qualifier
annotation class UserQualifier(
    val age: Int,
    val height: Double,
)
class User(val name: String)
@Module
@InstallIn(SingletonComponent::class)
object UserModule {

    @Provides
    @UserQualifier(30, 180.0)
    fun provideA(): User {
        return User("A")
    }

    @Provides
    @UserQualifier(20, 160.0)
    fun provideB(): User {
        return User("B")
    }

}

 

UserQualifier 클래스에 지정한 타입대로 값을 입력했다. 이후 액티비티에서 사용할 때도 여기서 설정한 값과 동일한 값을 넣어야 한다.

 

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    private val TAG = this::class.simpleName

    @UserQualifier(30, 180.0)
    @Inject
    lateinit var a: User

    @UserQualifier(20, 160.0)
    @Inject
    lateinit var b: User

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        Log.e(TAG, "user a : ${a.name}")
        Log.e(TAG, "user b : ${b.name}")
    }

}

 

 

이것이 Qualifier의 대략적인 사용법이다. 다음은 @Named의 사용법을 확인해 본다.

마찬가지로 "first foo"가 로그로 출력되도록 한다.

 

@Module
@InstallIn(SingletonComponent::class)
object FooModule {

    @Named("foo1")
    @Provides
    fun provideFirstFoo(): Foo {
        return Foo(name = "first foo")
    }

    @Provides
    fun provideSecondFoo(): Foo {
        return Foo(name = "second foo")
    }

}
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    private val TAG = this::class.simpleName

    lateinit var foo: Foo

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    @Inject
    fun injectFoo(
        @Named("foo1") foo: Foo
    ) {
        Log.e(TAG, "주입된 foo의 이름 : ${foo.name}")
        this.foo = foo
    }

}

 

 

사용법을 보면 알겠지만 @Named의 매개변수로 문자열 값을 전달받는다. 이 문자열 값이 키값이 되며 사용 시 동일하게 작성해야 정상적으로 쓸 수 있다.

반응형