Dagger에는 단일 데이터를 주입받는 방법 외에도, 자료구조를 이용하여 n개의 데이터를 주입받는 방법이 있습니다. 대표적으로 Set
과 Map
을 이용하는 방법이 존재합니다.
Set MultiBinding
먼저, Set을 이용한 MultiBinding입니다.
SetModule.kt
@Module
class SetModule {
@Provides
@IntoSet
fun provideHello(): String = "Hello"
@Provides
@IntoSet
fun provideWorld(): String = "World"
@Provides
@ElementsIntoSet
fun provideSet(): Set<String> {
return HashSet(listOf("seosh", "817"))
}
}
@IntoSet
을 이용하여 하나씩 넣는 방법과 @ElementsIntoSet
을 이용하여 한번에 넣어주는 방법이 있습니다.
"Hello", "World"는 @IntoSet으로,@ElementsIntoSet은 "seosh", "817"을 모듈에 넣어주도록 하겠습니다.
MultiBindingComponent.kt
@Component(modules = [SetModule::class])
interface MultiBindingComponent {
fun inject(multiBindingComputer: MultiBindingComputer)
}
그리고 난 뒤, Component에서 MultiBindingComputer의 injection Method를 작성해 줍니다.
MultiBindingComputer.kt
import javax.inject.Inject
class MultiBindingComputer {
@Inject
lateinit var stringSet: Set<String>
fun printSets() {
val itr = stringSet.iterator()
while(itr.hasNext()) {
println(itr.next())
}
}
}
주입받은 Set 데이터를 print해주도록 하겠습니다.
실행결과
Set을 모두 출력해보면 주입해주었던 String들이 데이터들이 모두 출력되는 결과를 확인하실 수 있습니다.
Map MultiBinding
Map MultiBinding에서 사용되는 기본적인 Key는 StringKey
, IntKey
, LongKey
, ClassKey
가 존재합니다.
@IntoMap
어노테이션과 같이 사용해 주면 Module에서 각각의 Key와 Value들이 저장되어 주입받을 수 있습니다.
가장 중요한 Key는 @MapKey
를 이용하여 어노테이션을 생성한 후 SubClass 혹은 EnumClass를 Key로 사용하는 방법입니다.
@MapKey를 이용하면 어떠한 복잡한 클래스던지 MultiBinding으로 반환되는 Map의 Key로 사용하여 원하는 결과를 얻어낼 수 있습니다.
예제에서는 기본적인 Key들과 @MapKey를 이용한 Custom Map MultiBinding을 다루어보겠습니다.
기본적인 Key를 이용한 Map MultiBinding
Ram.kt
interface Ram {
val name: String
val company: String
}
class Ram8G @Inject constructor(): Ram {
override val name: String
get() = Name
override val company: String
get() = "Hynix"
companion object {
const val Name ="8G RAM"
}
}
class Ram16G : Ram {
override val name: String
get() = Name
override val company: String
get() = "Samsung"
companion object {
const val Name ="16G RAM"
}
}
MapModule.kt
@Module
class MapModule {
@Provides
@IntoMap
@StringKey("name")
fun provideStringName() : String = "seosh"
@Provides
@IntoMap
@IntKey(817)
fun provideIntToName() : String = "seosh"
@Provides
@IntoMap
@ClassKey(Ram::class)
fun provideRam() : Ram = Ram8G()
}
@StringKey, @IntKey, @ClassKey안에 각각 Key들을 넣어줍니다.
Key: "name", Value: "seosh"
Key: 817, Value: "seosh"
Key: Ram, Value: Ram8G
로 넣어주었습니다.
@Component(modules = [MapModule::class])
interface MultiBindingComponent {
// Map MultiBinding
fun getStringKeyMap(): Map<String, String>
fun getIntKeyMap(): Map<Int, String>
fun getClassKeyMap(): Map<Class<*>, Ram>
@Component.Builder
interface Builder {
fun mapModule(mapModule: MapModule): Builder
fun build(): MultiBindingComponent
}
}
TestCode
@Config(manifest = Config.NONE, sdk = [28])
@RunWith(RobolectricTestRunner::class)
class ComputerUnitTest {
@Test
fun testMultiBindingMap() {
val multiBindingComponent: MultiBindingComponent = DaggerMultiBindingComponent.create()
// Map MultiBinding
println(multiBindingComponent.getStringKeyMap()["name"])
println(multiBindingComponent.getIntKeyMap()[817])
println(multiBindingComponent.getClassKeyMap()[Ram::class.java]?.name)
}
}
결과를 확인하기 위해 넣어준 key를 순서대로 출력해보면 원하는 결과대로 출력되는 것을 확인하실 수 있습니다.
@MapKey를 이용한 CustomKey MultiBinding
@MapKey
를 이용하여 어노테이션을 생성한 후 SubClass 혹은 EnumClass를 Key로 사용할 수 있습니다.
Annotations.kt
enum class MyInfoEnum {
FirstName, LastName,
}
@MapKey
annotation class RamClassKey(val value: KClass<out Ram>)
@MapKey
annotation class MyEnumKey(val value: MyInfoEnum)
어노테이션 클래스에 @MapKey 어노테이션을 달아주고 Ram클래스의 공변으로 인자로 받을 수 있도록 만듭니다.
이렇게 넣어주면 Ram의 SubClass를 Key로 사용할 수 있게 됩니다.
또한, @MapKey는 Enum Class도 인자로 받을 수 있습니다.
MapModule.kt
@Module
class MapModule {
@Provides
@IntoMap
@RamClassKey(Ram8G::class)
fun provideRam8G() : Ram = Ram8G()
@Provides
@IntoMap
@RamClassKey(Ram16G::class)
fun provideRam16G() : Ram = Ram16G()
@Provides
@IntoMap
@MyEnumKey(MyInfoEnum.LastName)
fun provideInfoLastName(): String = "SH"
@Provides
@IntoMap
@MyEnumKey(MyInfoEnum.FirstName)
fun provideInfoFirstName(): String = "Seo"
}
위에서 선언한 어노테이션을 이용하여 @IntoMap 어노테이션과 함께 호출하여 Key와 Value를 지정해줍니다.
@Component(modules = [MapModule::class])
interface MultiBindingComponent {
// CustomKey MultiBinding
fun getCustomKeyMap(): Map<MyInfoEnum, String>
fun getRamClassKeyMap(): Map<Class<out Ram>, @JvmSuppressWildcards Ram>
@Component.Builder
interface Builder {
fun mapModule(mapModule: MapModule): Builder
fun build(): MultiBindingComponent
}
}
Component에서 Injection Method를 작성해줍니다.
TestCode
@Config(manifest = Config.NONE, sdk = [28])
@RunWith(RobolectricTestRunner::class)
class ComputerUnitTest {
@Test
fun testMultiBindingMap() {
val multiBindingComponent: MultiBindingComponent = DaggerMultiBindingComponent.create()
//CustomKey MultiBinding
println(multiBindingComponent.getRamClassKeyMap()[Ram8G::class.java]?.name)
println(multiBindingComponent.getRamClassKeyMap()[Ram16G::class.java]?.name)
println(multiBindingComponent.getCustomKeyMap()[MyInfoEnum.FirstName])
println(multiBindingComponent.getCustomKeyMap()[MyInfoEnum.LastName])
}
}
아래 결과를 통해 원하는 결과대로 나온 것을 확인하실 수 있습니다.