4 분 소요

Dagger

Dagger는 의존성 주입(DI)을 도와주는 프레임워크이다.
의존성 주입(DI)에 대해서 전 포스팅에서 알아보았다. Dagger를 사용하여 의존성 주입을 자동화하도록 지원하여 코드 작성에 편의성을 제공한다. 전 포스팅에서 만들어 보았던 스마트폰 예시에 Dagger를 적용해 보려고 한다.

시작하기

build.gradle

프로젝트에 Dagger를 사용하려면 build.gradle에 아래의 종속 항복을 추가해야 한다. 최신 버전의 Dagger는 Github 에서 확인 할 수 있다.

plugins {
    .
    .
    id 'kotlin-kapt'
}


dependencies {
    .
    .
    // Dagger2
    implementation "com.google.dagger:dagger:2.41"
    kapt "com.google.dagger:dagger-compiler:2.41"
}

@Inject

@Inject 를 사용하게 되면 Dagger가 해당 타입을 어떻게 생성하는지 알수 있게 한다. @Inject필드, 생성자, 메서드에 붙여 Component로 부터 의존성 객체를 주입 요청하는 어노테이션이다. 객체(인스턴스)의 생성이 클래스에 의존적이지 않고 Component가 생성해 주기 때문에 보일러 플레이트 코드가 줄어들고 테스트하기 수월해 진다.

@Inject 로 의존성 주입을 요청하면 연결된 Component의 Module에서 의존 객체가 생성되어 반환된다.


각 Battery, MemoryCard, SIMCard, SmartPhone에 @Inject 어노테이션을 추가한다. -> 생성자를 통한 의존성 주입

class Battery @Inject constructor() {
    init {
        Log.i("TAG", "배터리 생성완료!")
    }
}

class MemoryCard @Inject constructor(){
    init {
        Log.i("TAG", "메모리 카드 생성완료!")
    }
}

class SIMCard @Inject constructor(){
    init {
        Log.i("TAG", "유심칩 생성완료!")
    }
}

class SmartPhone @Inject constructor(val battery: Battery, val simCard: SIMCard, val memoryCard: MemoryCard) {
    fun start() {
        Log.i("TAG", "스마트폰 동작!")
    }
}

@Component

@ComponentInterface 또는 abstract class에만 사용이 가능하다.

📍 컴파일 타임 에 접두어 DaggerComponent 클래스 이름이 합쳐진 Dagger클래스 자동생성
ex) SmartPhoneComponent -> DaggerSmartPhoneComponent

연결된 Module로 부터 의존성 객체를 생성하고, Inject로 요청받은 인스턴스에 생성한 객체를 주입한다.

Component Methods

@Component 어노테이션이 가지는 인터페이스 또는 abstract class는 하나 이상의 메소드 를 가지고 있어야 한다.

메소드의 방식에는 Provision MethodMember-Injection 이 있다.

1. Provision Method

Provision Method의 경우 매개변수가 없고, Module이 제공하는 객체의 타입을 반환값으로 갖는다.

2. Member-Injection

의존성 주입을 시킬 객체를 파라미터로 넘기는 방식으로, 해당 객체의 클래스 내부에서 @Inject가 붙은 필드에 객체를 주입한다.

@Component
interface SmartPhoneComponent {
    // Provision Method 방식
    fun getSmartPhone(): SmartPhone
}

Component 객체 구현 - create() / build()

Dagger로 생성된 Component는 위에서 말한 형태로 Dagger + Component 클래스 이름으로 자동 생성된다.

📍해당하는 클래스가 생성이 되지않았다면

  1. Rebuild Project 실행
  2. 1번을 수행해도 안된다면 dagger-complier 라이브러리를 포함하는지 확인

Component의 인스턴스를 생성하는 방법은 create(), build()로 2가지 방법이 있다.

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

//        val battery = Battery()
//        val simCard = SIMCard()
//        val memoryCard = MemoryCard()
//        val smartPhone = SmartPhone(battery, simCard, memoryCard)
//        smartPhone.start()

        // 1. create() 방식
        val smartPhone = DaggerSmartPhoneComponent.create()
            .getSmartPhone()
    

        // 2. build() 방식
        val smartPhone = DaggerSmartPhoneComponent.builder()
            .build()
            .getSmartPhone()
            
        smartPhone.start()
    }
}

생성된 스마트폰 Component에 있는 smartPhone.start()를 통하여 실행을 하게되면 동작을 하는것을 볼 수 있다.

@DaggerGenerated
@SuppressWarnings({
    "unchecked",
    "rawtypes"
})
public final class DaggerSmartPhoneComponent implements SmartPhoneComponent {
  private final DaggerSmartPhoneComponent smartPhoneComponent = this;

  private DaggerSmartPhoneComponent() {


  }

  public static Builder builder() {
    return new Builder();
  }

  public static SmartPhoneComponent create() {
    return new Builder().build();
  }

  @Override
  public SmartPhone getSmartPhone() {
    return new SmartPhone(new Battery(), new SIMCard(), new MemoryCard());
  }

  public static final class Builder {
    private Builder() {
    }

    public SmartPhoneComponent build() {
      return new DaggerSmartPhoneComponent();
    }
  }
}

자동으로 생성된 DaggerSmartPhoneComponent 클래스에서 getSmartPhone() 을 상속받아 자동으로 객체들을 주입해준것을 볼 수 있다.

Third Party Library는??

여기서 외부에 존재하는 Third Party Library라면 함수의 내부를 볼 수 없기 때문에 @Inject어노테이션을 사용할 수 없다. 예를 들어 Retrofit client의 경우 Third party Library 이기 때문에 생성자를 통한 의존성 주입이 불가능하다.

// Retrofit
class Retrofit @Inject constructor()??{
}

이러한 해당 객체를 의존성 주입을 사용하기 위해서는 Moduleprovider function 을 사용하여 주입해야한다.

@Module / @Provides

Dagger에서 의존성 관계를 설정하는 클래스를 Module 이라고 부르고 @Module 어노테이션을 붙여준다. 즉 Dagger에서 Module은 의존성 주입에 필요한 객체들을 관리해준다.

@Provide 어노테이션은 @Module클래스에서 선언된 메소드들에만 붙임으로서 Dagger가 메소드에서 해당 타입의 객체를 반환해 준다는것을 알 수 있게 해준다.

Module 클래스는 클래스 이름 + Module으로 생성하고, Provide 메소드는 provide + 메소드 이름 으로 만드는것이 일반적인 컨벤션이다.


위의 예시에서 MemoryCard가 Third Party Library라고 생각하고 Module을 생성해 보면 아래와 같다.

// third party 라이브러리하고 생각
class MemoryCard {
    init {
        Log.i("TAG", "메모리 카드 생성완료!")
    }
}
// Module 클래스 생성 
@Module
class MemoryCardModule {

    @Provides
    fun provideMemoryCard(): MemoryCard {
        return MemoryCard()
    }
}

Component가 생성된 Module을 알 수 있게 하기위해선 @Component에 아래와 같이 추가해 주면 된다.

@Component(modules = [MemoryCardModule::class])
interface SmartPhoneComponent {
    // Provision Method 방식
    fun getSmartPhone(): SmartPhone
}

Field Injection

위의 Inject, Module, Component를 사용하여 의존성 주입을 해보았다. 실제로 의존성 주입을 사용하게되면 SmartPhone과 같은 Component 이외에 다은 의존성 객체가 많이 존재 할 수 있다. 이러한 Component들을 MainActivity에서 의존성 주입을 할때마다 생성해주어야 한다면 getSmartPhone()과 같은 getter 메소드들이 불필요하게 많이 추가가 될것이다.

Component에서 Method-Injection 방식을 사용하여 사용할 Activity / Fragment를 지정해 주고 사용할 View에서 Field Injection을 사용하면 불필요한 코드의 사용을 줄일 수 있다.

@Component
interface SmartPhoneComponent {
    // Provision Method 방식
    // fun getSmartPhone(): SmartPhone

    // Method-Injection 방식
    fun inject(mainActivity: MainActivity)
}
class MainActivity : AppCompatActivity() {
    // Field Injection
    @Inject
    lateinit var smartPhone: SmartPhone
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 1. create() 방식
        // val smartPhone = DaggerSmartPhoneComponent.create()
        //     .getSmartPhone()
    
        DaggerSmartPhoneComponent.create()
            .inject(this)
            
        smartPhone.start()
    }
}

References

맨 위로 이동 ↑

댓글남기기