본문 바로가기

IT/안드로이드 관련

[dagger] 대거 - 2 정리용

 

범위 지정하기

각각의 컴포넌트는 @Scope 어노테이션을 통해 범위 지정이 가능하다.

gc가 발생하는 상황이 되기전까지 단일 인스턴스를 보유한다는 의미다.

 

@Singleton 사용하기

일반적으로 @Singleton 어노테이션을 사용해서 범위를 지정하여 객체를 재사용 가능하다.

 

@Singleton
@Component(modules = [SingletonModule::class])
interface SingletonComponent{
    fun getString() : String
}

 

@Module
class SingletonModule{
    @Provides
    @Singleton
    fun provideString() : String{
        return ""
    }
}

 

    @Test
    fun testSingleton() {
        val singletonComponent: SingletonComponent = DaggerSingletonComponent.create()
        val temp1 = singletonComponent.getString()
        val temp2 = singletonComponent.getString()
        assertSame(temp1, temp2)
    }

 

@Reusable 사용하기

Reusable도 @Singleton 과 비슷하게 커스텀 스코프와 비슷한 역할을 한다.

특정 컴포넌트 스코프에 종속되지 않아, @Reusable을 선언하지 않아도 된다. 이전 객체를 재사용 가능하다면

재사용하고 아니면 새로 생성한다. 즉 다른 스코프 어노테이션 처럼 인스턴스의 동일성을 보장하진 않지만

필요에 따라 메모리를 해제해주기 때문에 메모리 관리 측면에서 조금 더 효율적이다.

 

@Scope확장하기

커스텀 스코프를 만들어서 확장 가능합니다.

 

@Module
class MyModule {
 @Provides
 @UserScope
 fun provideString() : String{
		return ""
	}
}

 

https://brunch.co.kr/@oemilk/72

 

 

 

 

바인딩의 종류

 

@Binds

어노테이션은 모듈 내의 추상 메서드에 붙일 수 있으며, 반드시 하나의 매개변수만을 가져야한다.

매개 변수를 반환형으로 바인드 할 수 있으며 @Provides 메서드 대신 좀 더 효율적으로 사용할 수 있다.

 

abstract class Binds{
    abstract fun bindRandom(secureRandom: SecureRandom) : Random
}

 

 

@Binds methods must have only one parameter whose type is assignable to the return type

@Binds 자체는 Provide랑 거의 동일한 역할을 하는데, 실제 자동 생성되는 코드를 보면

40퍼정도 적은 양을 보인다.또한 provide는 적지 않은 오버헤드가 생긴다.

하지만 binds는 단일 매개변수만 허용하므로 상황에 맞춰 사용하면 될것 같다.

이미 바인드 된 SecureRandom 을 Random타입으로 한번 더 바인드할 수 있다.

 

 

binds와 provide 상세설명

https://stackoverflow.com/questions/52586940/what-is-the-use-case-for-binds-vs-provides-annotation-in-dagger2

https://medium.com/@elye.project/dagger-2-binds-vs-provides-cbf3c10511b5

@BindOptionalOf

이 어노테이션은 모듈 내의 추상 메서드에 붙일 수 있으며 이 메서드는 매개변수를 가질 수 없다.

무조건 void가 아닌 특정 타입을 반환형으로 가져야하며 예외사항도 던질 수 없다.

 

@Module
abstract class CommonModule{
    @BindsOptionalOf
    abstract fun bindsOptionalString() : String
}

 

@Module
class HelloModule{
    @Provides
    fun provideString() : String{
        return ""
    }
}
class MyFoo {
    @Inject
    lateinit var str : Optional<String>

    @Inject
    lateinit var str2 : Optional<Provider<String>>
    
    @Inject
    lateinit var str3 : Optional<Lazy<String>>
}

 

 

 

만약 컴포넌트 내에 Foo가 바인드 된 적 있다며 Optional의 상태는 present(존재)

그렇지 않으면 absent이다.

 

어떤 타입의 의존성이 바인드 되었는지 여부와 관계없이 @Inject를 이용해 주입할 수 있는것이 특징이다.

* Optional은 null을 포함하는 것을 허용하지 않는다. 그러므로 @Nullable 바인딩에 대해서는 컴파일 타임에 에러를 바생시킨다.

 

즉 바인드 여부랑 다르게 사용이 가능하다

 

@Module
abstract class CommonModule{
    @BindsOptionalOf
    abstract fun bindsOptionalString() : String
}

 

이렇게 옵셔널이 가능한 String 어노테이션을 작성한 뒤

 

@Component(modules = [CommonModule::class])
interface NoStrComponent {
    fun inject(myFoo: MyFoo)
}

 

    @Test
    fun testMyFoo(){
        val myFoo = MyFoo()
        val daggerNoStrComponent = DaggerNoStrComponent.create()
        daggerNoStrComponent.inject(myFoo)
        println(myFoo.str.isPresent)
        println(myFoo.str)

    }

 

이렇게 코드를 작성해서 테스트 해보면 실제 present인지 absent 상태인지 확인이 가능합니다.

 

 

@BindInstance

BindInstance 어노테이션은 컴포넌트의 빌더의 세터 메서드 혹은 팩토리의 매개변수에 붙일 수 있다

 

모듈이 아닌 외부로 부터 생성된 인스턴스를 빌더 또는 팩토리를 통해 넘겨줌으로써 컴포넌트가 해당 인스턴스를

바인드 하게 된다. 이러한 인스턴스들은 모듈로부터 제공되는 인스턴스와 동일하게 @Inject가 붙은 필드, 생성자, 메서드에 주입 가능하다. 

 

@Component
interface BindsComponent {
    fun inject(myFoo: MyFoo)
    
    @Component.Builder
    interface Builder{
        @BindsInstance
        fun setString(str : String) : Builder 
        fun build() : BindsComponent
    }
}

 

 

class BindInstanceTest {
    @Test
    fun testBindsInstance(){
        val hello = "Hello World"
        val foo = BindFoo()
        val component : BindsComponent = DaggerBindsComponent.builder()
            .setString(hello)
            .build()
        component.inject(foo)

    }
}

@bindInstance라는 키워드를 설정함으로써 

 Builder 내부에 setString에 있는 메소드에 hello 라는 String type을 주입했습니다.

 

 

 

멀티바인딩 하기

 

멀티바인딩은 여러 모듈에 있는 같은 객체의 타입을 하나의 Set이나 Map의 형태로 관리 할 수 있습니다.

 

Set을 통해 구현하기

@IntoSet과 @ElementsIntoSet을 @provides 메소드와 함께 사용할 수 있다. 

 

1. IntoSet

 

일단 IntoSet이라는 키워드를 통해 Set<String>에 대입가능하고

@ElementIntoSet 키워드를 통해 Set 리스트를 대입이 가능하다.

 

@Component(modules = [SetModule::class])
interface SetComponent{
    fun inject(setFoo: SetFoo)
}

 

@Module
class SetModule{
    @Provides
    @IntoSet
    fun provideHello() : String{
        return "hello"
    }

    @Provides
    @IntoSet
    fun provideWorld() : String{
        return "world"
    }

    @Provides
    @ElementsIntoSet
    fun provideSet() : Set<String>{
        return HashSet(listOf("namget","test"))
    }
}

 

class SetFoo {
    @Inject
    lateinit var set: Set<String>

    fun print() {
        for (itr in set.iterator()) {
            println(itr)
        }
    }
}

 

class MultibindingTest {
    @Test
    fun testMultibindingSet() {
        val setFoo = SetFoo()
        DaggerSetComponent.create().inject(setFoo)
        setFoo.print()
    }
}

 

2. intoMap

@intoMap 키워드를 통해 Map 이기 때문에 Key가 필요하다. 따라서 별도의 Key 어노테이션이 필요하다.

 

Key를 등록하기 위한 키워드로는 네가지가 있다.

@StringKey, @ClassKey, @IntKey, @LongKey

 

class MapFoo {}
@Component(modules = [MapModule::class])
interface MapComponent{
fun getLongByString() : Map<String,Long>
fun getStringByClass() : Map<Class<*>,String>
}

 

@Module
class MapModule {
    companion object {
        @Provides
        @IntoMap
        @StringKey("foo")
        fun provideFooValue() : Long{
            return 100L
        }

        @Provides
        @IntoMap
        @ClassKey(MapFoo::class)
        fun provideFooStr() : String{
            return "foo"
        }
    }
}

 

를 이용하면 Map에 ClassKey 혹은 StringKey를 통해 해당 객체를 불러올 수 있다.

 

 

사용자의 키를 만드는 방법

직접 키를 만드는 방법

 

enum class Animal {
    CAT,DOG
}

Animal에 대한 키를 설정

 

@MapKey
@interface AnimalKey {
    Animal value();
}
@MapKey
@interface NumberKey {
    Class<? extends Number> value();
}

 

AnimalKey를 생성, NumberKey를 생성

 

 

@Component(modules = [KeyModule::class])
interface MayKeyComponent {
    fun getStringByAnimal(): Map<Animal, String>
    fun getStringByNumber(): Map<Class<out Number>, String>
}

 

@Module
class KeyModule{
    @IntoMap
    @AnimalKey(Animal.CAT)
    @Provides
    fun provideCat() : String{
        return "Meow"
    }

    @IntoMap
    @AnimalKey(Animal.DOG)
    @Provides
    fun provideDog() : String{
        return "Bow-bow"
    }

    @IntoMap
    @NumberKey(Float::class)
    @Provides
    fun provideFloatValue() : String{
        return "100f"
    }

    @IntoMap
    @NumberKey(Integer::class)
    @Provides
    fun provideIntegerValue() : String{
        return "1"
    }

}

키를 생성하고 키를 하는 Provides를 만든다

 

    @Test
    fun testCustomMapKey(){
        val component = DaggerMayKeyComponent.create()
        val cat = component.getStringByAnimal()[Animal.CAT]
        val dog = component.getStringByAnimal()[Animal.DOG]
        val number = component.getStringByNumber()[Float::class.java]
        println(cat)
        println(dog)
        println(number)
    }

해당 키를 통한 value를 위와같이 가져올 수 있다.

 

 

상속 컴포넌트

@Component(modules = [ParentModule::class])
interface ParentComponent {
    fun strings(): Set<String>
    fun childComponentBuilder() : ChildComponent.Builder
}

부모의 컴포넌트를 생성한뒤 자식의 빌더를 생성한다.

 

부모의 모듈

@Module(subcomponents = [ChildComponent::class])
class ParentModule {
    @Provides
    @IntoSet
    fun string1() : String{
        return "parent string 1"
    }

    @Provides
    @IntoSet
    fun string2() : String{
        return "parent string 2"
    }
}

 

자식 컴포넌트에 서브컴포넌트의 빌더라는 어노테이션을 통해 빌더를 생성한다

 

@Subcomponent(modules = [ChildModule::class])
interface ChildComponent{
    fun strings() : Set<String>

    @Subcomponent.Builder
    interface Builder{
        fun build() : ChildComponent
    }

}

 

@Module
class ChildModule{
    @Provides
    @IntoSet
    fun string3() : String{
        return "child string1"
    }

    @Provides
    @IntoSet
    fun string4() : String{
        return "child string2"
    }
}

 

이렇게 해당 모듈을 생성하면 하위의 subComponent가 된다.

 

 

멀티바인딩

@Module
abstract class MultibindsModules{
    @Multibinds
    abstract fun strings() :Set<String>
}
@Component(modules = [MultibindsModules::class])
interface MultibindsComponent {
    fun getStrings() : Set<String>
}

 

@Multibinds라는 키워드를 통해 

 

** 확인해보기