깨알 개념/Android

[Android] Dagger2 - 기초

interfacer_han 2024. 6. 24. 05:49

#1 이전 글

 

의존성 주입 (Dependency Injection)

#1 의존성 주입(Dependency Injection)이란?#1-1 Dependent와 Dependencyfun main() { val car = Car() car.startCar() } class Car { private val engine = Engine() private val airbag = Airbag() private val battery = Battery() fun startCar() { engine.startE

kenel.tistory.com

본 게시글을 이해하려면 먼저, 의존성 주입에 대해 쓴 이전 게시글을 읽어야 한다. 또, 이전 게시글의 코드를 Migration해서 본 게시글의 코드를 작성했다.

 

#2 dagger

 

GitHub - google/dagger: A fast dependency injector for Android and Java.

A fast dependency injector for Android and Java. Contribute to google/dagger development by creating an account on GitHub.

github.com

프로젝트에서 Dependent - Dependency 관계가 점점 많아지면 언젠간 프로그래머가 감당하기 힘든 수준에 이를 것이다.
 
이 때 필요한 Dependency의 암시적 주입, 의존성 코드들의 직관화, 모듈화, 유지보수성 증대를 위한 라이브러리가 바로 dagger다. dagger1에서 2로 넘어갈 때 많은 변화가 있었기에 본 게시글의 제목에 Dagger2로 사용할 버전을 명시했다. 이제, dagger 사용의 기초에 대해 서술해보겠다.
 

#3 이전 게시글 안드로이드 프로젝트로 Migration

#3-1 개요

먼저, dagger를 적용할 코드가 필요하다. 이전 게시글 #2-1 부분의 코드를 안드로이드 프로젝트로 옮겼다. 그 과정에서 각 클래스를 하나하나 파일로 만들었고,
 

// package com.example.dagger2basics

import android.util.Log

class Car(
    private val engineInstance: Engine,
    private val airbagInstance: Airbag,
    private val batteryInstance: Battery
) {
    fun startCar() {
        engineInstance.startEngine()
        airbagInstance.startAirbag()
        batteryInstance.startBattery()
        Log.i("interfacer_han", "${this::class.simpleName} is ready")
    }
}

위와 같이, println() 대신 Log.i()로 메시지를 출력하게 수정했다.

#3-2 MainActivity.kt

// package com.example.dagger2basics

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

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

        val crankshaft = Crankshaft()
        val cylinder = Cylinder()
        val piston = Piston(crankshaft, cylinder)
        val engine = Engine(pistonInstance)
        val airbag = Airbag()
        val battery = Battery()

        val car = Car(engine, airbag, battery)
        car.startCar()
    }
}

MainActivity에는, 원래 코드의 main()에서 정의했던 동작을 집어넣는다.
 
이제, 이 프로젝트가 dagger를 이용해 Dependency(의존성)을 주입(Inject)받도록 수정해본다.
 

#4 dagger 사용하기

#4-1 프로젝트 수준 build.gradle 수정

plugins {
    ...
    id("com.google.devtools.ksp") version "1.9.0-1.0.13" apply false
}

ksp 플러그인 추가
 

#4-2 모듈 수준 build.gradle 수정

plugins {
    ...
    id("com.google.devtools.ksp")
    id("org.jetbrains.kotlin.kapt")
}

android {
    ...
}

dependencies {

    ...

    // Dagger2
    val daggerVersion = "2.51.1"
    implementation("com.google.dagger:dagger:$daggerVersion")
    kapt("com.google.dagger:dagger-compiler:$daggerVersion")
}

ksp 및 kapt 플러그인 추가 및 Dagger2 라이브러리 다운로드
 

#4-3 @Inject의 이해

dagger는 어노테이션(@)을 기반으로 작동한다. 그 중 @Inject는 dagger의 기본적이고 핵심적인 역할을 하는 어노테이션이다. Inject(주입)의 사전적 정의 그대로, (생성자를) 주입한다는 의미의 어노테이션이다.
 
이전 게시글의 #2-1에 의하면, 어떤 클래스의 생성자에 정의된 또 다른 클래스는 Dependency다. 마침, dagger 또한 '생성자 주입' 방식에 기반하여 생성자 주입을 수행한다. 따라서 dagger는 프로그래머가 생성자에 붙인 @Inject를 보고 Dependency임을 판단하도록 설계되었다. 아래 코드를 보자.
 

class Car @Inject constructor(
    private val engine: Engine,
    private val airbag: Airbag,
    private val battery: Battery
) {
    fun startCar() {
        engine.startEngine()
        airbag.startAirbag()
        battery.startBattery()
        Log.i("interfacer_han", "${this::class.simpleName} is ready")
    }
}

Car의 생성자에 @Inject가 붙었다. 이제, Engine, Airbag, Battery 클래스는 Dagger 라이브러리에 의해 암시적으로 객체가 생성되는 명단에 등록된다. 이 말은, #3-2의 코드에서 engine, airbag, battery의 객체 생성 및 car = Car(engine, airbag, battery)가 암시적으로 수행된다는 얘기다.
 

class Crankshaft @Inject constructor() { // 얼핏 보기엔, 이 @Inject는 아무런 의미가 없어보인다.
    fun startCrankshaft() {
        Log.i("interfacer_han", "${this::class.simpleName} is ready")
    }
}

헷갈리지 말아야 하는 것은, @Inject가 붙는 대상은 Class가 아니라 생성자라는 사실이다. 위 코드에서 Crankshaft 클래스는 Dependent이지, Denpendency가 아니다. 하지만 그렇다고 생성자 인수를 요구하지 않는 클래스, 즉 의존성 그래프(이전 게시글의 #1-3 참조)에서 말단 부분을 담당하고 있는 클래스에 @Inject를 붙이지 않아야 하는 건 아니다. 왜냐하면, 비어있는 생성자에 @Inject를 붙임으로서 해당 클래스는 Dendency가 없다는 걸 (dagger 입장에서) 명시적으로 파악할 수 있기 때문이다.
 

#4-4 @Inject 붙이기

의존성 그래프에 나와 있는 모든 클래스에 @Inject를 붙인다. 이제, dagger의 'Denpendency 암시적 전달 명단'에는 Engine, Airbag, Battery, Piston, Crakshift, Cylinder의 6개 클래스가 추가된다.
 

#4-5 @Component

// package com.example.dagger2basics

import dagger.Component

@Component
interface CarComponent {
    fun getCar(): Car
}

@Component 어노테이션이 붙은 인터페이스는 @Inject가 붙은 생성자나 @Provide가 붙은 메소드를 스캔하고, 의존성을 주입하는 역할을 수행한다. @Component에 추상 함수를 추가하고 컴파일을 하면, dagger에 의해 Dagger + 인터페이스 이름으로 명명된 클래스가 생성된다 (여기선 DaggerCarCompenent). 그리고 인터페이스에 정의했던 추상 함수들도 구현되는데, 추상 함수들이 지녔던 반환 타입(여기서는 Car)의 객체를 만들기 위한 의존성 주입 코드가 구현 함수에 들어간다.
 

#4-6 의존성 그래프

@Component 클래스는 위와 같은 의존성 그래프를 토대로 의존성 주입을 자동으로 수행할 것이다. @Inject 또는 @Provide로 위에서 말했던 명단을 구성해두면 dagger는, 더 정확히는 @Component 클래스는 위와 같은 의존성 그래프를 알아서 그려낸다.
 

#4-7 컴파일 하기

MainActivity를 수정하기 전 먼저 @Component 클래스를 만들기 위한 컴파일을 진행한다. [Build] - [Clean Project] 후 [ Build ] - [Rebuild Project]를 차례대로 클릭한다.
 

#4-8 MainActivity에서 수동적 의존성 주입 코드 제거

...

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedState: Bundle?) {
        super.onCreate(savedState)
        setContentView(R.layout.activity_main)
/*
        val crankshaft = Crankshaft()
        val cylinder = Cylinder()
        val piston = Piston(crankshaft, cylinder)
        val engine = Engine(piston)
        val airbag = Airbag()
        val battery = Battery()

        val car = Car(engine, airbag, battery)
*/
        val car = DaggerCarComponent.create().getCar()
        car.startCar()
    }
}

기존에 썼던 수동으로 의존성을 주입하는 코드를 제거한다. Component 클래스를 생성하고 #4-5에서 만들어두었던 추상 함수의 구현 함수인 getCar()를 호출하면, Compoenent 클래스가 그려낸 의존성 그래프에 기반하여 의존성이 연쇄적으로 주입되고 최종적으로 Car 객체가 만들어진다.
 

#5 작동 확인 (로그 메시지)

Crankshaft is ready
Cylinder is ready
Piston is ready
Engine is ready
Airbag is ready
Battery is ready
Car is ready

 

#6 요약

생성자에 Dependency를 명시하는 순간부터, Dagger와 같이 편리한 자동화 라이브러리는 언젠간 나올 운명이었다.

#7 완성된 앱

 

android-practice/dagger2/Dagger2Basics at master · Kanmanemone/android-practice

Contribute to Kanmanemone/android-practice development by creating an account on GitHub.

github.com

 

#8 이어지는 글

#8-1 @Provides 사용하기

 

[Android] Dagger2 - @Provides

#1 이전 글 [Android] Dagger2 - 기초#1 이전 글 의존성 주입 (Dependency Injection)#1 의존성 주입(Dependency Injection)이란?#1-1 Dependent와 Dependencyfun main() { val car = Car() car.startCar() } class Car { private val engine = Engine

kenel.tistory.com

클래스의 생성자에 @Inject를 붙일 수 없는 경우의 의존성 주입을 구현해본다.

 

#8-2 인터페이스 구현체를 의존성 주입하기

 

[Android] Dagger2 - 인터페이스 구현체 주입 (@Binds)

#1 이전 글 [Android] Dagger2 - 기초#1 이전 글 의존성 주입 (Dependency Injection)#1 의존성 주입(Dependency Injection)이란?#1-1 Dependent와 Dependencyfun main() { val car = Car() car.startCar() } class Car { private val engine = Engine

kenel.tistory.com

Interface를 Dependency로 주입해본다.

 

#8-3 Activity에 의존성 주입하기

 

[Android] Dagger2 - Activity에 Dependency 주입

#1 이전 글 [Android] Dagger2 - 기초#1 이전 글 의존성 주입 (Dependency Injection)#1 의존성 주입(Dependency Injection)이란?#1-1 Dependent와 Dependencyfun main() { val car = Car() car.startCar() } class Car { private val engine = Engine

kenel.tistory.com

Activity에 Field Injection을 수행해본다.

 

#8-4 Component 인스턴스 싱글톤으로 관리하기

 

[Android] Dagger2 - Application 활용하기

#1 이전 글 [Android] Dagger2 - 기초#1 이전 글 의존성 주입 (Dependency Injection)#1 의존성 주입(Dependency Injection)이란?#1-1 Dependent와 Dependencyfun main() { val car = Car() car.startCar() } class Car { private val engine = Engine

kenel.tistory.com

Application 클래스를 통해 Component 인스턴스를 싱글톤으로 관리한다.