깨알 개념/Android

[Android] Dagger2 - @Provides

interfacer_han 2024. 6. 24. 08:48

#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() private val airbag = Airbag() private

kenel.tistory.com

위 게시글의 완성된 앱을 일부 수정해서, @Inject를 사용할 수 없는 경우의 의존성 주입을 해결해본다.

 

#2 @Provides

이전 글에선 생성자에 @Inject를 붙였고, 그것은 dagger가 의존성 주입을 하는 명시적인 표시로서 작동했다. 하지만, 생성자에 @Inject를 붙인다는 것은 그 행동의 범위가 사용자 정의 클래스로 제한된다는 말이기도 하다. 사용자 정의 클래스가 아닌 것은 무엇일까? String, Int 등과 같은 기본 라이브러리부터 Retrofit, Room 등 제 3자 라이브러리다. 읽기 전용으로 만들어진 해당 클래스들을 수정하는 것은 불가능하며, 설령 가능하다고 해도 그 방대한 라이브러리를 건들이는 것은 권장되지 않을 것이다.

 

이럴 때 @Inject 대신 사용하는 것이 @Provides다. 읽기 전용으로 내게 제공(Provides)된 클래스의 인스턴스를 반환하는 것이 목적이다.

 

#3 코드 수정 - @Provides 사용

#3-1 Dependency 추가

위와 같이 기존 의존성 그래프에서 Airbag이 String형인 Dependency를 요구하게 만든다. String은 읽기 전용 클래스다. 즉, @Provides로 제공되어야 한다.

 

// package com.example.provides

import android.util.Log
import javax.inject.Inject

class Airbag @Inject constructor(private val manufacturer: String) {
    fun startAirbag() {
        Log.i("interfacer_han", "${this::class.simpleName} is ready")
        Log.i("interfacer_han", "${this::class.simpleName} made by ${manufacturer}")
    }
}

Airbag 클래스의 코드는 이 정도로 수정한다.

 

#3-2 @Module

// package com.example.provides

import dagger.Module

@Module
class MyModule {

    // TODO: @Provides 메소드 만들기
}

이전 글에 의하면, @Component 클래스는 프로젝트를 스캔하며 @Inject가 붙은 생성자나 @Provides가 붙은 메소드를 찾는다. 암시적 의존성 주입을 하는 주체가 @Component 클래스이기 때문이다. @Provides 메소드는 반드시는 아니지만 거의 대부분 @Module 어노테이션이 붙은 클래스 내부에 정의된다. @Module 역할을 할 클래스를 만든다.

 

#3-3 @Component

// package com.example.provides

import dagger.Component

@Component(modules = [MyModule::class])
interface CarComponent {
    fun getCar(): Car
}

Component 인터페이스에 @Module 클래스를 추가한다. 이 시점부터, Component 클래스는 의존성 주입을 위한 @Inject 및 @Provides의 스캔 범위에 MyModule 클래스를 추가한다.

 

#3-4 @Provides

// package com.example.provides

import dagger.Module
import dagger.Provides

@Module
class MyModule {

    @Provides
    fun providesAirbagManufacturer(): String {
        return "KENEL"
    }
}

MyModule 클래스에 String을 반환하는 함수를 추가한다.

 

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

Crankshaft is ready
Cylinder is ready
Piston is ready
Engine is ready
Airbag is ready
Airbag made by KENEL
Battery is ready
Car is ready

 

#4 코드 수정 - @Named 사용

#4-1 @Provides의 반환형이 중복된다면?

만약, Battery도 Airbag과 같은 데이터형인 Dependency를 요구한다면 어떻게 될까?

 

// package com.example.provides

import android.util.Log
import javax.inject.Inject

class Battery @Inject constructor(private val manufacturer: String) {
    fun startBattery() {
        Log.i("interfacer_han", "${this::class.simpleName} is ready")
        Log.i("interfacer_han", "${this::class.simpleName} made by ${manufacturer}")
    }
}

일단 Battery 클래스를 위와 같이 수정한다.

 

// package com.example.provides

import dagger.Module
import dagger.Provides

@Module
class MyModule {

    @Provides
    fun providesAirbagManufacturer(): String {
        return "KENEL"
    }

    @Provides
    fun providesBatteryManufacturer(): String {
        return "TISTORY"
    }
}

그리고 @Module 클래스는 이렇게 수정해본다.

 

#4-2 프로젝트 빌드 시 뜨는 에러 메시지

error: [Dagger/DuplicateBindings] java.lang.String is bound multiple times

dagger 입장에서, 어떤 @Provides 함수를 사용해야하는 지 결정할 수 없기에 뜨는 에러다. dagger는 기본적으로 함수의 반환형을 기준삼아 암시적 의존성 주입을 수행하기 때문이다. 따라서 반환형이 중복되는 현 시점에선, dagger가 의존성 주입을 수행하는 데 있어 또 다른 기준 요소가 필요하다.

 

여담으로, 함수 이름(여기서는 providesAirbagManufacturer() 및 providesBatteryManufacturer())은 dagger 입장과 당연히 관련이 없다. 메소드 이름은 프래그래머 입장에서의 가독성과 관련된 요소일 뿐이다.

 

#4-3 @Named 사용 - @Module에서

...
import javax.inject.Named

@Module
class MyModule {

    @Provides
    @Named("Airbag")
    fun providesAirbagManufacturer(): String {
        return "KENEL"
    }

    @Provides
    @Named("Battery")
    fun providesBatteryManufacturer(): String {
        return "TISTORY"
    }
}

그 또 다른 요소는 바로 @Named 어노테이션이다. @Named는 동일한 데이터 타입인 Dependency가 여러개 있을 때 각각의 Dependency를 구분하기 위해서 사용된다.

 

#4-4 @Named 사용 - @Inject에서

...
import javax.inject.Named

class Airbag @Inject constructor(@Named("Airbag") private val manufacturer: String) {
    fun startAirbag() {
        Log.i("interfacer_han", "${this::class.simpleName} is ready")
        Log.i("interfacer_han", "${this::class.simpleName} made by ${manufacturer}")
    }
}

@Inject가 붙은 생성자의 인수에도 #4-3와 매칭되는 @Named 어노테이션을 붙여준다.

 

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

Crankshaft is ready
Cylinder is ready
Piston is ready
Engine is ready
Airbag is ready
Airbag made by KENEL
Battery is ready
Battery made by TISTORY
Car is ready

 

#5 요약

@Inject가 불가한 클래스는 @Provides로 제공(Provide)하고, 제공되는 것끼리의 구분은 @Named로 한다.

 

#6 완성된 앱

 

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

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

github.com

 

#7 이어지는 글

 

[Android] Dagger2 - 매개변수

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

kenel.tistory.com

이어지는 글에서 @Provides 또는 @Module이 매개변수를 가지게 만들어본다.