오늘도 더 나은 코드를 작성하였습니까?

Android Code Lab(Dagger2) 정리1 본문

DI/Dagger2

Android Code Lab(Dagger2) 정리1

hik14 2021. 12. 20. 20:05

코드랩을 통해 구현할 application graph 

- 화살표는 객체간 종속성을 표기한다. 

- 애플리케이션 그래프: 앱의 모든 클래스와 그들 사이의 종속성

@Inject annotation

- Dagger에게 이 type의 객체를 주입하는 방법(how)을 알려준다. 

- Dagger는 아래 코드에서 @Inject를 통해 registrationViewModel을 생성하기 위해 userManger이 필요한것을 알고,

또 userManger를 생성을 생성하는 방법또한 알아야한다.

class RegistrationViewModel @Inject constructor(val userManager: UserManager) {
    ...
}

*kotlin에서는 주생성자 앞에 @Inject를 애노테이션을 붙이기전에 constructor 키워드로 주생성자를 명시해주어야 한다.

 

@Inject 를 통해 Dagger는 2가지를 알게 된다. 

 

1. RegistrationViewModel 생성을 위해 UserManger이 필요하다

2. RegistrationViewModel는 UserManger을 종속된 항목을 가진다. 

 

Dagger는 UserManger 생성방법을 아직 모르기 때문에 UserManger의 생성 방법을 알필요가 있다.

class UserManager @Inject constructor(private val storage: Storage) {
    ...
}

위와 같이 @Inject주석을 추가하여 알려준다.

 

또한 UserManager는 의존성(즉, Storage)을 Interface에 가진다.(Interface 타입의 주입은 나중에 설명함)

 

Views(activity, fragment) require objects from the graph

activity/fragment 등의 android os(system)에서 생성 및 초기화를 하는 객체는 dagger가 제공을 해줄수가 없다.

특히 activity는 초기화 코드는 onCreate에서 진행된다.  그렇기 때문에 @Inject을 통해 dagger에게 제공 방법을 알려줄 수 없다.

즉 생성방법을 알려주는 생성자 주입이 아닌 field 주입을 사용한다.

 

아래와 같이 RegistrationViewModel을  onCreate() 메소드에서 직접 생성하여 할당 하는것이 아닌 dagger가 우리 대신 제공을 해줄 필요가 있다. 

class RegistrationActivity : AppCompatActivity() {

    lateinit var registrationViewModel: RegistrationViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_registration)

        registrationViewModel = RegistrationViewModel((application as MyApplication).userManager)
       
    }
}
class RegistrationActivity : AppCompatActivity() {

    // @Inject annotated fields will be provided by Dagger
    @Inject
    lateinit var registrationViewModel: RegistrationViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
       
    }
}

 

@Inject

클래스 생성자 - Dagger에게 객체를 생성하는 법과 의존성 관계를 알려줌. 

클래스 필드 - Dagger에게 필드를 채워야 함을 알려줌. 

@Component annotation

dagger

- 종속성 graph를 생성

- graph를 관리

- graph를 통해 종속성을 주입.

 

Dagger는 수동적 종속성 주입과 동일하게 container를 생성한다. 

 

@Component 주석이 달린 Interface(container)

Dagger가 제공하는 의존성 주입의 매개변수를 충족하는 데 필요한 모든 종속성을 포함하는 코드를 생성하도록 합니다

Interface(container) 내에서 RegistrationActivity가 주입을 필요하다는 것을 Dagger에게 알려줄수 있다.

@Component
interface AppComponent {

    // RegistrationActivity 가 주입을 필요로 한것을 Dagger 에게 알림.
    // @Inject 주석이 달린 종속성을 제공해야 한다.(RegistrationViewModel)
    fun inject(activity: RegistrationActivity)
}

dagger는 RegistrationViewModel을 주입하기 위해 UserManger또한 주입하는 방법을 알고 있어야 한다.

 

이렇게 재귀적으로 종속성을 찾는 프로세스 동안 Dagger가 특정 종속성을 주입하는 방법을 모른다면,  주입을 할수 없는 종속성이 있다는 말은 곧  컴파일 타임에 실패를 의미합니다

 

@Component(Interface)는 Dagger가 컴파일 타임에 종속성 graph를 생성하는 데 필요한 정보를 제공합니다.

인터페이스 메소드의 매개변수는 주입을 요청하는 클래스를 정의합니다.

 

앱을 빌드하면 종속성을 관리하는 데 필요한 코드를 생성하는 Dagger의 주석 프로세서가 트리거됩니다.

 

이후 앱을 빌드해보면, 에러가 발생한다.

앱을 빌드하면 종속성을 관리하는 데 필요한 코드를 생성하는 Dagger의 주석 프로세서가 트리거됩니다.

 

 AppComponent에서 오류가 발생했음을 알려줍니다. 오류는 [Dagger/MissingBinding] 유형이며, 이는 Dagger가 특정 유형을 제공하는 방법을 모른다는 것을 의미합니다. 

오류를 전부 읽다보면

 

 [Dagger/MissingBinding] com.example.android.dagger.storage.Storage cannot be provided without an @Provides-annotated method.

 

@Provides-annotated 메소드 없이는 스토리지를 제공할 수 없습니다.를 발견할 수있다.

 

Storage는 interface고 직접 인스턴스화할 수 없기 때문에 Dagger에 Storage를 제공하는 방법을 알려주는 방식은 기존과 다르다.

Dagger에게 우리가 사용하려는 Storage 구현을 알려주어야 한다.

 

알려주기 위해 Dagger 모듈을 사용해야 한다. Dagger 모듈은 @Module로 주석이 달린 클래스입니다.

 

@ Module  annotation

Dagger @Module은 Dagger에 특정 유형의 인스턴스를 제공하는 방법을 알려줍니다.

종속성은 @Provides 및 @Binds 주석을 사용하여 정의한다.

 

import dagger.Module

// dagger 에게 dagger 모듈임을 알려준다.
@Module
class StorageModule {
    
}

@Binds annotation

 

@Binds를 사용하여 특정 인터페이스 타입을 주입할 때 사용해야 하는 구현을 Dagger에 알려준다.

 

@Binds

- 추상함수 위에 선언

- 파라미터타입은  인터페이스를 구현하는 구체적인 구현 타입. 

- 반환타입은 주입을 해야하는 타입.

// Tells Dagger this is a Dagger module
// Because of @Binds, StorageModule needs to be an abstract class
@Module
abstract class StorageModule {

    // Makes Dagger provide SharedPreferencesStorage when a Storage type is requested
    @Binds
    abstract fun provideStorage(storage: SharedPreferencesStorage): Storage
}

Dagger에게 "Storage 객체가 필요할 때 SharedPreferencesStorage를 사용" 하라고 알려준것이다. 

 

- provideStorage는 단순 메서드 이름이며 무엇이든 상관없다,  Dagger가 관심을 갖는 것은 매개변수와 반환 유형입니다.

- abstract 함수를 포함해야 하기 때문에 클래스 역시 추상 클래스로 변경한다. 

 

Dagger Module은 의미론적 으로 객체를 제공하는 방법을 캡슐화하는 방법입니다.

StorageModule 클래스를 호출하여 스토리지와 관련된 객체를 제공하는 로직을 그룹화했습니다.

애플리케이션이 확장되면 SharedPreferences의 다양한 구현을 제공하는 방법도 포함할 수 있습니다.

 

Storage 객체가 요청되면 SharedPreferencesStorage의 인스턴스를 생성해야 한다고 Dagger에게 알려주었지만,

아직 Dagger에게 어떻게 SharedPreferencesStorage의 인스턴스를 생성하는 방법을 알려주지 않았다.

 

SharedPreferencesStorage 생성자에 @Inject 주석을 추가하여 이전과 동일한 방식으로 알려준다.

 

import android.content.Context
import javax.inject.Inject

class SharedPreferencesStorage @Inject constructor(context: Context) : Storage {

}

application graph(종속성 그래프)는 StorageModule의 정보를 알아야 한다.

이를 위해  @Component 주석 내부에 modules 매개변수를 사용하여 AppComponent에 포함 시킨다.

 

import com.example.android.dagger.registration.RegistrationActivity
import dagger.Component

@Component(modules = [StorageModule::class])
interface AppComponent{

    // RegistrationActivity 가 주입을 필요로 한것을 Dagger 에게 알림.
    // @Inject 주석이 달린 종속성을 제공해야 한다.
    fun inject(activity: RegistrationActivity)
}

 이제 AppComponent는 StorageModule에 포함된 정보에 액세스할 수 있다

더 복잡한 애플리케이션에서는 OkHttpClient를 제공하는 방법이나 Gson 또는 Moshi를 구성하는 방법에 대한 정보를 추가하는 NetworkModule가 필요할 수도 있다.

 

@BindsInstance annotation

Context 제공하는 방법을 Dagger에게 어떻게 알려줄 수 있습니까?

Context는 Android 시스템에서 제공하므로 그래프 외부에서 구성됩니다. 

Context는 Applicaion Graph를 생성할 때 이미 사용 가능하므로 전달할 수 있습니다.

 

전달하는 방법은 Component Factory와 @BindsInstance 주석을 사용한다.

 

@Component(modules = [StorageModule::class])
interface AppComponent {

    // Factory to create instances of the AppComponent
    @Component.Factory
    interface Factory {
        // With @BindsInstance, the Context passed in will be available in the graph
        fun create(@BindsInstance context: Context): AppComponent
    }

    fun inject(activity: RegistrationActivity)
}

@Component.Factory 주석이 붙은 인터페이스를 선언하고, 내부에 Component type의 객체를 반환한다. @BindsInstance 주석이 달린 Context 유형의 매개변수가 있다.

 

@BindsInstance는 그래프에 해당 인스턴스를 추가해야 하고 컨텍스트가 필요할 때마다 해당 인스턴스를 제공해야 한다고 Dagger에 알려줍니다.

 

@BindsInstance - 그래프 외부에서 생성된 객체(예: Context의 인스턴스)를 주입할때 사용한다. 

 

 

* Dagger 가 모든 주입시킬 객체의 생성 방법을 알고 있고 무엇을 어디에 주입시킬지 알고 있으면, Application Graph를 생성 한다.

이렇게 Dagger Annotation Processo에 의해 자동 생성된 클래스를 Dagger{ ComponentName }라고 하면 Application Graph 구현을 포함한다.

fun inject(activity: RegistrationActivity)를 통해 RegistrationActivity 는  Application Graph에 접근하여 Dagger에서 객체(RegistrationViewModel)를 주입받을 수 있습니다. 

 

AppComponent는

RegistrationViewModel를 RegistrationActivity에 주입을 하기위해 RegistrationViewModel의 종속성인 UserManager을 주입 

해야한다. UserManager에는 @Inject 주석이 달린 생성자가 있으므로 Dagger는 이를 사용하여 인스턴스를 생성합니다.

 

UserManager는 Storage에 대한 종속성이 있고 이것은 StorageModule을 통해 생성하여 이미 그래프에 생성되어 있다. 

 

'DI > Dagger2' 카테고리의 다른 글

Android Code Lab(Dagger2) 정리3  (0) 2021.12.21
Android Code Lab(Dagger2) 정리2  (0) 2021.12.21
Android app에서 Dagger 사용  (0) 2021.05.03
Dagger Basic  (0) 2020.11.30
수동 종속성 삽입  (0) 2020.11.30