본문 바로가기

Android/DI

[Dagger2] Dagger2를 사용하여 의존성 주입을 해보자

728x90

처음 학습하면서 작성한 글입니다. 필요시 추후 내용을 수정할 예정입니다.

틀린 부분이 있으면 언제든 지적해주면 감사하겠습니다 :)


가장 대중적으로 사용하는 DI 인 Dagger2의 기본적인 사용 방법에 대하여 작성하고자 한다.

DI를 사용할 때, 그냥 여기서 선언하고 저기서 가져오고 라는 형식으로만 알고 사용했기 때문에 기초부터 정리하면서 다시 생각해보고자 한다.


우선,

DI 가 뭔지 짚고 넘어가자.

Dependency Injection 란, 외부에서 의존 객체를 생성하여 넘겨주는 것

 

간단히 말해서, A Class 가 B Class를 의존할 때(사용할 때)

B Class를 A Class가 직접 생성하여 사용하지 않고 외부에서 생성하여 넘겨주어 사용하도록 하는 것이 의존성 주입이라고 할 수 있다.

 

그렇다면,

DI를 위해 사용하는 Dagger2는 당연히 DI를 구현하기 위해 사용하는 라이브러리 중 하나.

라고 할 수 있다.

 

DI와 Dagger가 무엇인지 알아보았으니,

우선 사용하는 이유에 대해서 생각해 보아야 한다.

DI를 왜 사용하는가?

  • 코드의 재사용성을 높여준다.
  • 리팩터링이 쉽다.
  • 객체 간의 의존성을 줄일 수 있다.
  • 객체 간의 결합을 낮출 수 있다.

이러한 장점이 있다.

찾아보면 더 많은 장점들을 확인할 수 있지만, 필자가 느끼는 큰 장점들은 이와 같다고 생각한다.

 

물론, DI 를 사용했을 때의 단점도 존재하는데

  • 코드의 추적이 어려우며 가독성이 떨어질 수 있다.
  • 불필요하게 사용될 수 있다.

가장 큰 단점은 역시 코드의 추적이 어렵다는 것이라고 생각한다.

DI에 대한 이해도가 부족한 필자 같은 사람은, DI를 사용한 코드를 봤을 때 내가 찾고자 하는 코드가 어디에 있는지 확인하는데 다른 코드에 비하여 더 큰 시간이 걸린다.

사용하는 곳에서 직접 생성하여 쓰지 않기 때문에, 주입을 해주는 부분을 명확히 알지 못하면 이곳을 찾는데 시간이 소요되기 때문이다.

 

그렇다면,

다양한 라이브러리 중 Dagger2를 사용하는 이유는 무엇일까?

 

  • 의존성 컨테이너를 통해 의존성을 쉽게 관리할 수 있다.
  • Scope를 이용하여 의존성 재사용을 도와준다.
  • 난독화에 대한 이슈가 없다.
  • 런타임 오류를 방지한다.

와 같은 장점들이 있다고 한다.

필자는 Dagger에 대한 지식이 얕기 때문에 위에 대한 장점을 느끼지는 못하였다.

사용하면서 Dagger2라기 보다는 단순히 DI 자체의 장점만 느낄 수 있었다.

 

그리고,

Dagger2의 단점

 

러닝커브가 상당히 높다.

 

이것 하나로 설명이 다 되는 것 같다.

다른 DI에 비하여 스터디하는 것에 상당한 난이도가 있었다.

기본적인 것에 대해서만 공부를 하고 적용을 해보려 했는데도 이해하는 것에 시간이 상당히 걸렸던 기억이 있다.

기본적인 것만으로도 시간이 걸리는데, 실 프로젝트에서 심화시켜 사용한다고 생각하면.. 머리가 아파온다.


개념에 대해서 알아보았으니,

실제로 Dagger2를 사용해보자.

 

다음과 같이 Module 범위의 Gradle에 Dagger2를 추가해주면 된다.

해당 상태에서 sync 시 오류가 발생하면 Plugins에서 다음 부분이 빠져있지 않은가 확인해 보자.

 

id 'kotlin-kapt'

 

Gradle에 추가가 완료되었다면, 사용할 준비는 끝났다.

 

다음으로 의존성 주입을 할 간단한 Class를 생성해주자.

 

 

 

 

Tool, Material 2개의 Class를 사용하는 BluePrint Class를 생성해 주었다.

 

보통 의존성 주입을 하지 않으면

MainActivity에서 위의 객체들을 생성해주고 사용하면 되는데, DI를 사용하기 위해서는 추가적인 작업이 필요하다.

 

DI를 사용하기 위해서는 ModuleComponent가 필요하니 이것들을 생성하도록 하자.

 

 

Module 클래스에서는 @Module 어노테이션을 반드시 선언해주어야 한다.

Module 클래스에서는 DI에 필요한 객체들을 Provide 메서드를 통해서 관리한다.

 

여기서 Provide 메서드들은 @Provides 어노테이션을 선언해주어야 하며, 해당 어노테이션은 @Module 어노테이션이 선언된 class 내부의 메서드에서만 사용해야 한다.

 

Provide 메서드를 사용하여 DI가 필요한 부분에서 해당 객체의 의존성을 주입받는다.라고 생각하면 된다.

 

 

Component는 interface로 선언하며 @Component 어노테이션을 선언하여 사용한다.

@Component 어노테이션은 interface나 abstract 클래스에 붙일 수 있으며,

해당 어노테이션이 추가된 경우 Dagger라는 접두어가 붙은 클래스가 생성된다. 즉, 위의 경우 DaggerBluePrintComponent 가 된다.

 

@Component 어노테이션을 선언하고 해당 Component에서 사용할 Module을 작성해 준다.

필자는 위에 선언한 BluePrintModule을 사용할 것이기 때문에 해당 모듈을 추가해 주었다.

 

fun inject(activity: MainActivity)

 

해당 메서드를 의존성 주입이 필요한 곳에서 호출함으로써 이루어지게 되는데, 이 메서드는 보통 inject 라는 이름을 사용하지만 마음대로 변경해도 상관은 없다.

마음대로 명명하여 사용해도 되지만 Member-Injection Method 이기 때문에 inject라는 이름은 들어가도록 명명하는 것이 좋다.

 

다음으로는

의존성 주입을 실제로 받고, 사용하는 부분을 확인해 보자.

 

 

우선,

innerBluePrint 객체는 DI를 사용하지 않고 MainActivity에서 객체를 생성하여 사용하는 방법이다.

 

DI를 통해 객체를 주입받으려면, @Inject 어노테이션lateinit 를 사용하여 객체를 선언하면 된다.

@Inject 어노테이션은 생성자, 메서드, 필드에 붙여 객체를 주입받을 수 있게 해주는 어노테이션이다.

선언 후에, onCreate 내부에서 주입을 해주면 되는데, 여기서 확인할 코드는 다음과 같다.

 

DaggerBluePrintComponent.builder()
    .bluePrintModule(BluePrintModule()).build()
    .inject(this)

 

여기서 사용하는 Dagger~Component 객체는 위에서 언급한 @Component 어노테이션이 달려있는 interface의 클래스이다.

자신이 선언한 Module class와 같은 이름으로 선언하여 사용하며,

마지막에 호출한 inject 메서드가 Component에 선언한 메서드이다.

 

해당 클래스를 사용하려고 하면, 처음에는 빨간 줄이 뜨면서 정상적으로 사용할 수 없을 것이다.

이러한 이유는 해당 클래스는 빌드 시에 생성이 되기 때문에 아무런 작업을 하지 않고 사용하려면 생성이 되어있지 않기 때문에 사용할 수 없다.

따라서, Build > Rebuild Project를 통해서 해당 클래스를 만들어주면 정상적으로 사용이 가능하다.

 

이처럼 의존성 주입을 받는 다면,

가장 하단의 로그를 찍은 것처럼 해당 객체를 생성하지 않고 객체를 사용할 수 있게 된다.

 


위와 같은 방법으로 Class 자체를 주입하여 사용할 수 있지만,

실 업무를 진행할 때는 주로 viewModel 같은 곳에서 의존성 주입을 하여 사용하고는 한다.

 

그렇다면,

VM을 생성하여 의존성 주입을 해보자.

 

 

정말 간단하게 MainActivity에서 사용할 MainViewModel을 만들어 보았다.

 

그 후, VM Module 과 Component 생성해주면 되는데,

Component는 위에 언급한 것과 완전히 동일하므로 패스하도록 한다.

 

 

ViewModelModule이다.

딱 봐도 위에 선언한 모듈과는 차이가 있다는 것을 알 수 있다.

 

천천히 확인해보자.

우선 ViewModel을 생성하기 위해서는 material, tool, bluePrint 객체가 필요하게 된다.

따라서 ViewModel 모듈에서 객체를 생성해서 넣어주기 위해서는 위의 3개의 객체가 우선적으로 생성되어야 하고, 그 객체를 사용하여 VM 객체를 생성하여 넣어주어야 한다는 것이다.

 

그렇다면, 선행되어야 하는 3가지의 객체는 어떻게 만들어서 넣어주는가?

필자는 맨 처음 테스트를 위해 만들어둔 BluePrintModule을 사용하여 주입해 주었다.

BluePrintModule까지 작성해보면 자동 완성으로 나오는 Factory Class 들을 볼 수 있는데, 각 클래스들은 해당 Module에 선언한 @Provides 어노테이션을 사용해 선언한 메서드들이 나오게 된다.

해당 클래스를 통해 메서드를 호출하면 Module에 대한 매개 변수가 하나 더 추가되어 있는 메서드를 호출할 수 있는데, 해당 메서드를 통해 해당 객체를 주입받게 된다.

 

이처럼 필요한 객체를 주입받고, 그 주입받은 객체를 사용하여 ViewModel 객체를 주입해주는 방식이다.

 

 

MainActivity에서 사용하는 방법은 동일하다.

여기서, @Named 어노테이션을 사용해 보았는데, @Provides 선언한 부분과 @inject 선언한 부분이 한 쌍이 되어서 사용하는 것을 명시적으로 표시하기 위해 사용한다.

inject 받아야 할 함수의 name을 잘못 기입할 경우 빌드 시 오류가 발생하여 잘못된 객체를 주입받는 것을 방지하기 위하여 사용한다.

 

이렇게 선언하고 빌드를 해보면,

정상적으로 빌드가 되지 않는다.

 

오류가 발생하는 이유는 같은 MainActivity에 2번의 의존성 주입을 하고 있기 때문이다.

따라서, BluePrintComponent를 주석 처리를 하거나 제거한 후에 빌드를 하면 정상적으로 동작되는 것을 확인할 수 있다.


필자가 Dagger2를 적용하면서 가장 헷갈렸던 부분은

생성했던 각 클래스들이 어떠한 역할을 하는가?이다.

 

1. Module : 객체를 만들어 주입해주는 역할
2. Inject : 의존 객체를 주입받는 역할
3. Component : Module과 Inject를 연결해주는 역할

 

이렇게 생각하면 조금 더 이해가 편할 것이다.

 

Module > Component > Inject

 

의 순서로 작업이 이루어진다.

 

필자가 해당 글을 작성하면서 만들어 본 예제는 기본적인 사용 방법이다.

VM을 주입해주는 과정에서 조금 심화된(?) 것을 사용해 보았는데, 기본에서 조금 벗어난 범위라고 바로 이해가 안 되는 부분이 많았다.

러닝 커브가 높다는 말을 한 번에 이해할 수 있는 부분이었다.

 

아직 어떻게든 맨땅에 헤딩하며 붙여서 사용은 가능하지만, 원하는 기능과 동작을 사용하기에는 스터디가 많이 부족하다.

추후 시간이 되면 Dagger2를 조금 더 깊게 공부해보고 적용을 해봐야겠다.

 

해당 게시글에 사용한 예제는 GitHub 에 올려두었다.

https://github.com/HeeGyeong/DiSample

 

GitHub - HeeGyeong/DiSample: DiSample

DiSample. Contribute to HeeGyeong/DiSample development by creating an account on GitHub.

github.com

 

 

 

728x90