본문 바로가기

안드로이드

[Android] Dagger2를 활용한 Dependency Injection

개요 : 의존성 주입이란?

이 글은 구글 코드랩을 참조하여 작성한 글 입니다. 의존성 주입(DI;Dependency Injection)라이브러리는 제어의 역전을 활용하여 개발자가 직접 객체를 제어하는 대신 라이브러리를 통해 의존 관계를 정리해주어 느슨한 결합이 가능하게 한다. 즉  DI의 장점은 다음과 같다.

  • 재사용하기 좋은 코드
  • 쉬운 리팩토링
  • 쉬운 테스팅

먼저 '의존성'이란 한 객체가 다른 객체에 의존한다는 것을 의미한다. 예를 들어 아래는 Car 객체가 Engine 객체에 의존하는 코드이다.

class Engine{
    private int Fuel = 0
    
    public setFuel(int Fuel){
    	this.Fuel=Fuel;
    }
    public getFuel(){
    	return Fuel;
    }
}

class Car{
    private Engine engine;
    
    Car(){
    	engine = new Engine();
    }
    
    ...
}

위는 Car 내부에서 Engine을 생성하기 때문에 의존성을 주입받은 것이 아닌, 의존성을 스스로 만든 결과이다.

 

반대로 아래는 의존성을 외부에서 주입받는 예시이다.

class Engine{
    private int Fuel = 0
    
    public setFuel(int Fuel){
    	this.Fuel=Fuel;
    }
    public getFuel(){
    	return Fuel;
    }
}

class Car{
    private Engine engine;
    
    Car(Engine engine){
    	this.engine = engine();
    }
    
    ...
}

engine 객체를 외부에서 주입 받음으로써 결합 관계가 약해진다. 즉, engine이 교체 되는 일이 이떠라도 Car 객체는 수정할 필요가 적어진다.

 

 

Dagger2의 핵심 구성요소

Dagger2는 크게 3가지 요소로 구성된다.

  • Module: 주입할 객체 생성을 담당
  • Component: Module을 지정, 로딩하여 필요한 곳에 적절히 주입한다.
  • Inject: 주입 받을 객체를 가져오는 역할

가장 간단한 예시

아래는 Car객체가 Wheel, Engine객체를 통해 생성된다. MainActivity는 현재 내부에서 Car, Wheel, Engine이 생성되어 세 객체에 의존하는 상태이다.

public class MainActivity extends AppCompatActivity {

    Car car;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Wheel wheel = new Wheel();
        Engine engine = new Engine();

        car = new Car(wheel, engine);

    }

 

의존성 주입하기

 

먼저 Car 관련된 객체를 생성하는 Module 클래스를 생성해준다.

@Module
public class CarModule {

    @Provides
    Car provideCar(Engine engine, Wheel wheel) {
        return new Car(engine, wheel);
    }

    @Provides
    Engine provideEngine() {
        return new Engine();
    }

    @Provides
    Wheel provideWheel() {
        return new Wheel();
    }
}

 

다음으로 Module을 지정하여 실제 주입을 도와주는 인터페이스인 Component를 정의한다.

@Component(modules = CarModule.class)
public interface CarComponent {

    void inject(MainActivity activity);
}

 

최종적으로 메인 액티비티에서 Car 객체를 @Inject로 지정하고, DaggerCarComponent의 빌더 패턴을 활용하여 CarComponent를 생성하고 해당 컴포넌트의 inject 메서드를 수행하여 최종적으로 주입한다.

참고로 위 모듈, 컴포넌트 생성 이후 프로젝트를 한번 빌드해야 아래 DaggerCarComponent가 생성된다.
public class MainActivity extends AppCompatActivity {

    @Inject
    Car car;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        CarComponent component = DaggerCarComponent.builder()
                .carModule(new CarModule())
                .build();

        component.inject(this);

        Log.d("MyTag","Car engine : " + Car.getEngine())
    }
}

 

추가로 위 세가지 요소 외에도 Subcomponent, Scope 등이 존재한다.

  • Subcomponent: Component의 하위 계층으로, Dagger에서 그래프를 형성하게 된다. Inject를 통해 주입을 요청받으면 Subcomponent를 먼저 검색하고, 없으면 그의 부모 Component로 올라가며 검색한 뒤 주입하게 된다.
  • Scope: 생성된 객체의 Lifecycle(생명주기)를 의미한다. 주로 PerActivity, PerFragment 등으로 화면의 생명주기와 동일시하며, Module에서 Scope을 보고 객체를 관리한다. 

 

 

작업 흐름

Inject → Subcomponent → Module →Scope에 있을 경우 return, 없으면 생성.

못 찾았을 경우 상위 Component → Module → Scope에 있을 경우 return, 없으면 생성...