본문으로 바로가기

[Dagger 2] Multibinding @IntoSet, @IntoMap #5

category 개발이야기/Kotlin 2020. 6. 25. 17:34
반응형

이글은 Dagger 2 v2.25.2 를 기반으로 설명하며, Kotlin으로 예제 코드를 설명합니다.

또한 공식 Dagger site를 참고하였습니다.

https://dagger.dev/dev-guide/multibindings

Dagger를 이용하여 컬렉션을 반환하도록 하게 할수 있습니다.

또한 컬렉션 자체를 반환하도록 할수 도 있습니다.


시작하기에 앞서, 선행되는 지식이 필요하므로 순서대로 아래 링크를 먼저 보고 오시기 바랍니다.

2020/06/01 - [개발이야기/Kotlin] - [Dagger 2] Dependency injection, Dagger2 사용 - 기초 #1

2020/06/01 - [개발이야기/Kotlin] - [Dagger 2] Qualifier, instance binding 사용, @Named, @BinsInstance #2

2020/06/03 - [개발이야기/Kotlin] - [Dagger 2] Provider injection, Lazy Injection #3

2020/06/07 - [개발이야기/Kotlin] - [Dagger 2] Scope - SubComponents and Singleton #4


Set multibinding

아래와 같이 HerosFriends라는 class를 정의 합니다.

class HeroFriends {

    @Inject
    lateinit var heroFriends: Set<@JvmSuppressWildcards Person>

    @Inject
    lateinit var heroWeapons: Set<@JvmSuppressWildcards Weapon>

}


Heros들의 person 객체를 주입받고, hero들이 사용하는 무기들도 주입 받습니다.

즉, 단순 객제차가 아니라 collection을 주입받는 형태입니다.


그럼 이들을 채워줄수 있는 Module을 작성해 봅니다.

@Module
class HerosSetModule {

    @Provides
    @IntoSet
    fun provideIronMan(): Person = IronMan()

    @Provides
    @IntoSet
    fun provideCaptainAmerica(): Person = CaptainAmerica()

    @Provides
    @IntoSet
    fun provideHulk(): Person = Hulk()


    @Provides
    @ElementsIntoSet
    fun provideWeapons() = setOf(Suit(), Shield(), HulkBuster())
}


@IntoSet annotation을 이용하여 Person객체를 담는 set에 해당 객체를 담겠다고 선언합니다.

한번에 여러개의 인자를 담으려면 @ElementsIntoSet을 이용하면 됩니다.

두개의 예시를 위해 Person set은 @IntoSet을 이용하고 Weapon set은 @ElementsIntoSet을 이용했습니다.


Component를 아래와 같이 작성합니다.

@Component (modules = [HerosSetModule::class])
interface HerosSetComponent {

    fun heroFriends(): Set<Person>

    fun heroWeapons(): Set<Weapon>

    fun inject (heroFriends: HeroFriends)

}


여기서 HeroFriends 각채에 inject 하기위해서 inject함수를 정의했습니다.

Set 자체를 반환하는 heroFriends()heroWeapons()는 호출부분에서 직접 set을 꺼내오기위해 추가로 정의했을뿐 inject하는데 필요하지는 않습니다.


준비는 끝났으니 rebuilt를 한후에 아래와 같이 main 함수를 작성합니다.

 val setComponent = DaggerHerosSetComponent.create()
        val heroFriends = HeroFriends()

        setComponent.inject(heroFriends)

        heroFriends.heroFriends.forEach {
            println("name: ${it.name}")
        }

        heroFriends.heroWeapons.forEach {
            println("type: ${it.type}")
        }

        println("${setComponent.heroFriends().size},  ${setComponent.heroWeapons().size}")
}


위와 같이 inject() 함수를 통해 해당 set이 멤버변수로 잘 채워 졌는지 확인할 수 있습니다.

또한 component에서 추가 정의한 heroFriends()heroWeapons()를 이용하여 바로 set을 받아올 수도 있습니다.


참고로 Inject을 받는 HeroModel에서 @JvmSupressWildcards 를 사용했습니다.

이는 kotlin에서 사용시 명확한 제너릭 타입을 요구하기 때문에 필요한 annotation이며, 이를 붙이지 않으면 rebuilt시 dagger에서 에러가 발생하여 컴파일되지 않습니다.

(자바라면 해당 annotation을 붙일 필요가 없습니다.)


Map multibinding

이번에 key/value가 존재하는 map 형태로 사용해 봅니다.

@Module
class HerosMapModule {

    @Provides
    @IntoMap
    @StringKey("ironMan")
    fun provideWeaponsForIronMan() : Weapon {
        return Suit()
    }

    @Provides
    @IntoMap
    @StringKey("captainAmerica")
    fun provideWeaponsForCaptain() : Weapon {
        return Shield()
    }

    @Provides
    @IntoMap
    @ClassKey(Shield::class)
    fun providePersonForShield() : Person {
        return CaptainAmerica()
    }

    @Provides
    @IntoMap
    @ClassKey(Suit::class)
    fun providePersonForSuit() : Person {
        return IronMan()
    }
}


Module을 위와같이 만들었습니다.

@IntoMap을 사용하여 Map에 넣을 정보라는걸 표기해 준 후에 @StringKey를 이용하여 키값을 정해 줍니다.

만약 키를 Class 형태로 사용하려면 @ClassKey를 사용합니다.


지원하는 key 타입은 하기 링크에서 확인 가능합니다.

https://dagger.dev/api/latest/dagger/multibindings/package-summary.html


@Component (modules = [HerosMapModule::class])
interface HerosMapComponent {

    fun personMap(): Map<String, Weapon>

    fun weaponMap(): Map<Class<*>, Person>
}


이번엔 dagger를 통해서 map을 받아올 수 있도록 component에 함수를 정의합니다.


호출부분은 아래와 같습니다.

val mapComponent = DaggerHerosMapComponent.create()
println(mapComponent.personMap()["ironMan"]?.type())
println(mapComponent.personMap()["captainAmerica"]?.type())

println(mapComponent.weaponMap()[Suit::class.java]?.name())
println(mapComponent.weaponMap()[Shield::class.java]?.name())


이로써 Dagger의 사용 방법에 대한 포스트를 마무리 합니다.

실제 Dagger 공식 페이지의 예제를 따라하다 보면 컴파일 오류가 나는 상황이 빈번하게 발생합니다.

이는 kotlin을 사용하면서 호환성으로 인한 에러인 경우가 많은데, Set binding에 사용된 @JvmSupressWildcards  를 추가했듯이 다른 방법을 추가하여 사용해야 합니다.


그냥 간단한 프로젝트에 사용하기엔 진입장벽이 있는게 사실이나, 한번 틀을 잡아 놓으면 모듈이나, component에 함수 하나씩 추가하는건 어렵지 않습니다.

그리고 Dagger에서는 Android에서의 사용을 위한 방법 또한 제시하고 있으나 따로 포스팅 할 예정은 아니기에 하기 링크에서 직접 원문을 확인하시기 바랍니다.

https://dagger.dev/dev-guide/android


추가적으로 Kotlin에서는 DI를 위한 koin을  제공하니, 필요하다면 해당 부분도 검색하여 사용하면 됩니다.



반응형