티스토리 뷰

07. 컴포넌트

  • 바인딩된 모듈로 부터 오브젝트 그래프를 생성하는 핵심적인 역할
  • @Component 사용
    • interface, abstract class 에만 붙일 수 있음
  • 컴파일 타임에 'Dagger', @Component가 붙은 클래스 이름이 합쳐진 형식의 이름을 가짐
  • @Component가 갖는 속성
    • modules : 컴포넌트에 바인드되는 @Module 이 지정된 클래스 배열
      • 모듈이 다른 모듈을 포함한 경우 해당 모듈까지 컴포넌트에 구현해야함
    • dependencies : 컴포넌트에 다른 컴포넌트의 의존성을 사용하는 경우 클래스 배열

오브젝트 크래프

  • Dagger에서는 컴포넌트, 모듈, 객체 등의 관계를 컨테이너 or (오브젝트)그래프 라고 함

[Hello World 예제의 그래프]

컴포넌트 매서드

  • @Component가 붙은 모든 타입이 하나 이상 가져야 하는 메서드
  • 메서드 이름은 상관없으나 반환 타입 규칙은 무조건 따라야 한다.
  • 프로비전 메서드와 멤버-인젝션 메서드로 구분된다.

프로비전 메서드(Provision Methods)

  • Dagger의 컴포넌트에서 매개변수가 없고, 반환형이 모듈로 부터 제공 또는 주입되는 메서드
@Module
class SomeModule {
    @Provides
    fun provideSomeType(): SomeType = SomeType()
}

class SomeType {}
@Component(modules = [SomeModule::class])
interface SomeComponent {
    fun getSomeType(): SomeType // SomeModule 로 부터 제공 또는 주입 받은 SomeType 객체를 반환
}

멤버-인젝션 메서드(Member-injection methods)

  • Dagger의 컴포넌트에서 하나의 매개 변수를 갖는 메서드.
  • 멤버-인젝션 메서드는 unit을 반환 또는 빌더 패턴 처럼 메서드 체이닝 가능한 메서드를 만들기 위해 매개 변수 타입을 반환 형으로 갖느 메서드로 선언

잠깐 빌더 패턴이 뭔지 알아보자

  • 빌더 패턴(Builder pattern)이란 복합 객체의 생성 과정과 표현 방법을 분리하여 동일한 생성 절차에서 서로 다른 표현 결과를 만들 수 있게 하는 패턴이다.
var customer: Member = Member.build()
    .name("홍길동")
    .age(30)
    .build()
  • 멤버-인젝션 메서드 예시
  • @Component(modules = [SomeModule::class]) interface SomeComponent { fun injectSomeType(someType: SomeType) fun injectAndReturnSomeType(someType: SomeType): SomeType }
  • 실제로 테스트 해보기
class MyClass {
    var str: String? = null
    @Inject set
}
@Module
class MyModule {
    @Provides
    fun provideHelloWorld(): String = "Hello World"

    @Provides
    fun provideInt(): Int = 1234

    @Provides
    fun providePerson(name: String, age: Int): Person = Person(name, age)
}
@Component(modules = [MyModule::class])
interface MyComponent {
    fun getString(): String
    fun getInt(): Int
    fun getPerson(): Person

    fun inject(myClass: MyClass) // 멤버-인젝션 메서드
}
  • 테스트
class ExampleUnitTest {
    @Test
    fun test_memberInjection(){
        val myClass = MyClass()
        var str = myClass.str
        assertNull("조회 결과 null", str)

        val myComponent = DaggerMyComponent.create()
        myComponent.inject(myClass)
        str = myClass.str
        assertEquals("Hello World", str)
    }
}
  • myComponent.inject(myClass) 전에는 null 이었다가 이후에 "Hello World"가 주입된 것을 알 수 있다.
  • 매개 변수 없이 MemberInjector<T>를 반환할 수도 있다.
    • 반환된 MemberInjector 객체의 injectmembers(T) 메서드를 호출하면 멤버-인잭션 메서드와 동일 작업을 수행한다.
@Component(modules = [MyModule::class])
interface MyComponent {
    ...
    fun getInjector():MembersInjector<MyClass>
}
class ExampleUnitTest {
    @Test
    fun tet_memberInjector(){
        val myClass = MyClass()
        var str = myClass.str
        println("result = $str") // null

        val myComponent = DaggerMyComponent.create()
        val injector: MembersInjector<MyClass> = myComponent.getInjector()
        injector.injectMembers(myClass)
        str = myClass.str
        println("result = $str") // Hello World
    }
}

의존성 주입하기

  • Dagger 3가지 의존성 주입 방법
    1. 필드 주입
    2. 생성자 주입
    3. 메서드 주입
  • @Inject 어노테이션이 붙은 필드, 메서드 그리고 생성자에 인스턴스를 주입
  • 실무에서는 주로 필드, 생성자 주입 사용

[PersonModule.kt] : 이름과 나이를 제공

@Module
class PersonModule {

    @Provides
    fun provideName(): String = "SangSuLee"

    @Provides
    fun provideAge(): Int = 100
}

[PersonComponent.kt]

@Component(modules = [PersonModule::class])
interface PersonComponent {

    fun getPersonA():PersonA // 프로비전 매서드 (PersonA 객체를 제공)

    fun inject(personB: PersonB) (PersonB에 멤버-인젝션)
}

[PersonA.kt]

//생성자 주입
class PersonA @Inject constructor(val name: String, val age: Int)

[PersonB.kt]

class PersonB {
    @Inject // 필드 주입
    var name: String? = null

    @set:Inject // 메서드 주입 (setter)
    var age = 0
}
  • 테스트 코드
class ExampleUnitTest {
    @Test
    fun test_Injection(){
        val personComponent = DaggerPersonComponent.create()

        val personA = personComponent.getPersonA()
        println("${personA.name} : ${personA.age}") // SangSuLee : 100 

        val personB = PersonB()
        DaggerPersonComponent.create().inject(personB)
        assertEquals("SangSuLee", personB.name)

        assertEquals(100, personB.age)
    }
}

상속된 클래스에 의존성 주입

  • 멤버-인젝션 메서드를 호출할 때
  • 매개 변수 타입에 서브 클래스의 객체를 넣으면
  • 해당 슈퍼 클래스의 @Inject 멤버만 의존성 주입이 된다.
open class Parent {
    var a: A? = null
        @Inject set
}

open class Self : Parent() {
    var b: B? = null
        @Inject set
}

class Child : Self() {

    var c: C? = null
        @Inject set
}
  • 예시로
  • 컴포넌트에 멤버-인젝션 메서드인 fun inject(Self)가 존재하고
  • Child 의 인스턴스를 멤버-인젝션 메서드의 매개변수로 참조하여 호출하면
  • Child의 인스턴스 에는 a 하고 b 만 주입되며 c에는 주입되지 않는다.

컴포넌트 객체 만들기

  • 생성된 빌더 또는 팩토리를 통해 만들 수 있음
  • 컴포넌트 내의 @Component.Builder 또는 @Component.Factory 타입 선언을 통해 빌더 혹은 팩토리 생성
  • 빌더, 팩토리 어노테이션이 둘 다 없으면 @Component 어노테이션에 선언된 모듈 및 의존성을 참조하여 빌더를 자동으로 생성
@Component(modules = [MyModule::class])
interface MyComponent {
    ...
    @Component.Builder
    interface Builder{
        fun setMyModule(myModule: MyModule): Builder
        fun build():MyComponent
    }

}

컴포넌트 빌더를 만드는 조건

  • @Component.Builder 어노테이션은 컴포넌트 타입 내에 선언되어야 함
  • 반드시 매개변수 가없고 컴포넌트 타입 또는 컴포넌트의 슈퍼 타입을 반환하는 추상 메서드를 하나 포함 해야함 (이를 빌드 메서드 라고 함)
  • 빌드 메서드를 제외한 나머지는 세터 메서드(setter methods)라고 함
  • @Component 어노테이션에 modules, dependencies로 선언된 요소들은 세터 메서드로 선언 해야함
  • 세터 메서드는 반드시 하나의 매개 변수만 가져야 하며, 반환 형으로는 unit, 빌더 또는 빌더의 슈퍼 타입이 될 수 있음
  • 세터 메서드에 @BindsInstance를 붙이면, 해당 컴포넌트에 인스턴스를 넘겨 바인드 시킨다.

[위 조건을 만족하는 컴포넌트 빌더의 예시]

@Component(modules = [BackendModule::class, FrontendModule::class])
interface ExampleComponent{
    fun myWidget(): MyWidget

    @Component.Builder
    interface Builder {
        fun backendModule(bm: BackendModule): Builder
        fun frontendModule(fm: FrontendModule): Builder

        @BindsInstance
        fun foo(foo: Foo): Builder)
        fun build(): MyComponent
    }
}

컴포넌트 팩토리를 만드는 조건

  • @Component.Factory 어노테이션은 컴포넌트 타입 내에 선언되어야 한다.
  • 컴포넌트 팩토리는 컴포넌트 타입 또는 컴포넌트의 슈퍼 타입을 반환하는 하나의 추상 메서드만 존재해야 한다.
    • ex. newInstance()
  • 팩토리 메서드에는 @Component 어노테이션에 modules, dependencies로 지정된 속성들을 반드시 매개변수로 가져야 한다.
  • 메서드에 @BindsInstance 어노테이션이 붙은 매개 변수는 해당 컴포넌트에 인스턴스를 넘겨 바인드 시킨다.
@Component(modules = [BackendModule::class, FrontendModule::class])
interface ExampleComponent {
    fun myWidget(): MyWidget

    @Component.Factory
    interface Factory {
        fun newMyComponent(
            bm: BackendModule,
            fm: FrontendModule,
            @BindsInstance foo: Foo
        ): MyComponent
    }
}
  • 생성되는 컴포넌트 형식에는 factory() 라는 정적 메서드를 갖게됨
  • 팩토리 인스턴스를 반환, 이 팩토리 인스턴스로 컴포넌트를 초기화할 수 있음
 

NetLSS/AndroidCleanArchitecture

안드로이드 클린 아키텍처 (Dagger2, RxJava, JetPack ...). Contribute to NetLSS/AndroidCleanArchitecture development by creating an account on GitHub.

github.com

2021.07.21 - [Android/클린 아키텍처] - [Clean Architecture] 07-Dagger2의 모듈(Module)

2021.07.20 - [Android/클린 아키텍처] - [Clean Architecture] 06-Dagger2 란? (+ Adnroid 적용 샘플)

2021.07.19 - [Android/클린 아키텍처] - [Clean Architecture] 05-의존성 주입(DI)과 그 필요성

해당 글은 '아키텍처를 알아야 앱 개발이 보인다' 를 공부하며 요약 정리한 글 입니다.
댓글
최근에 올라온 글
최근에 달린 댓글
네이버 이웃추가
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함