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

Dagger Basic 본문

DI/Dagger2

Dagger Basic

hik14 2020. 11. 30. 17:29

Android 앱에서 수동 종속 항목 삽입 또는 서비스 로케이터는 프로젝트의 크기에 따라 문제가 될 수 있습니다.

Dagger를 사용하여 종속 항목을 관리함으로써 프로젝트가 확장될 때 프로젝트의 복잡성을 제한할 수 있습니다.

 

Dagger는 개발자가 직접 작성했을 코드를 모방하는 코드를 자동으로 생성합니다. 코드가 컴파일 타임에 생성되므로 Guice 같은 다른 리플렉션 기반 솔루션보다 성능이 더 뛰어나며 추적 가능합니다.

Dagger 사용의 이점

Dagger를 사용하면 다음이 가능하므로 지루하고 오류가 발생하기 쉬운 상용구 코드를 작성하지 않아도 됩니다.

  • 수동 DI 섹션에서 수동으로 구현한 AppContainer코드(애플리케이션 그래프)를 자동생성합니다.
  • 애플리케이션 그래프에서 사용할 수 있는 클래스의 팩토리를 만듭니다. 이는 종속 항목이 내부적으로 충족되는 방식입니다.
  • Scope를 사용하여  종속 항목을 재사용하거나 유형의 새 인스턴스를 생성합니다.
  • Dagger sub Component를 사용하여 이전 섹션의 로그인 흐름에서와 같이 특정 흐름의 컨테이너를 만듭니다. 이렇게 하면 더 이상 필요하지 않은 객체를 메모리에서 해제함으로써 앱 성능이 향상됩니다.

개발자가 클래스의 종속 항목을 선언하고 주석을 사용하여 종속 항목을 충족하는 방법을 지정하기만 하면 Dagger는 빌드 타임에 자동으로 이러한 모든 일을 완료합니다.

 

Dagger는 개발자가 수동으로 작성한 것과 유사한 코드를 생성합니다.

내부적으로 Dagger는 클래스의 인스턴스를 제공하는 방법을 찾기 위해 참조할 수 있는 객체의 그래프를 만듭니다.

 

그래프의 모든 클래스와 관련하여 Dagger는 필요한 유형의 인스턴스를 가져오는 데 내부적으로 사용하는 팩토리 유형 클래스를 생성합니다.

 

빌드 타임에 Dagger는 코드를 검토하고 다음을 실행합니다.

  • 종속 항목 그래프를 빌드하고 그 유효성을 검사하여 다음을 확인합니다.
    • 모든 객체의 종속 항목이 충족될 수 있으므로 런타임 예외가 없습니다.
    • 종속 항목 주기가 없으므로 무한 루프가 없습니다.
  • 런타임시 실제 객체 및 종속 항목을 만드는 데 사용되는 클래스를 생성합니다.

Dagger의 간단한 사용 사례: Generating a factory

class UserRepository(
        private val localDataSource: UserLocalDataSource,
        private val remoteDataSource: UserRemoteDataSource
    ) { ... }
// @Inject lets Dagger know how to create instances of this object
    class UserRepository @Inject constructor(
        private val localDataSource: UserLocalDataSource,
        private val remoteDataSource: UserRemoteDataSource
    ) { ... }

 

  1. @Inject 주석이 달린 생성자를 사용하여 UserRepository 인스턴스를 만드는 방법을 알려줍니다.
  2. UserRepository생성을 위해서는 종속 항목은 UserLocalDataSource  UserRemoteDataSource임을 알려줍니다.

이제 Dagger는 UserRepository의 인스턴스를 만드는 방법을 알고 있지만 종속 항목(UserLocalDataSource 및 UserRemoteDataSource)을 만드는 방법은 모릅니다.

다음과 같이 다른 클래스에도 주석을 지정하면 Dagger는 그 클래스를 만드는 방법을 알게 됩니다.

  // @Inject lets Dagger know how to create instances of these objects
    class UserLocalDataSource @Inject constructor() { ... }
    class UserRemoteDataSource @Inject constructor() { ... }

Dagger  Component

Dagger는 종속 항목이 필요할 때 이러한 종속 항목을 가져올 위치를 찾는 데 사용할 수 있는 프로젝트의 종속 항목 그래프를 만든다.

Dagger가  그래프를 만들게 하려면 인터페이스를 만들고 @Component로 지정해 줍니다.

Dagger는 수동 종속 항목 삽입 시와 마찬가지로 컨테이너(그래프)를 만듭니다.

 

@Component 인터페이스 내에서 필요한 클래스의 인스턴스(UserRepository)를 반환하는 함수를 정의할 수 있습니다. 

@Component는 노출하는 유형을 결정하는데 필요한 모든 종속 항목이 있는 컨테이너를 생성하도록 Dagger에 지시합니다.

 

<Component>

Dagger가 제공하는 방법과 각 종속 항목을 알고 있는 객체로 구성된 그래프가 포함되어 있다.

 // @Component는 dagger에게 의존성 그래프 생성하게 한다. 
    @Component
    interface ApplicationGraph {
        //  the component interface의 함수의 반환타입은 컨테이너로 부터 무엇을 제공 받을지를 알려준다.
        fun repository(): UserRepository 
    }

 

프로젝트 빌드 시 Dagger는 자동으로 ApplicationGraph 인터페이스의 구현체, 즉 DaggerApplicationGraph를 생성합니다.

 

Dagger는 annotatioin process(주석) 를 사용하여 하나의 진입점(UserRepository 인스턴스 가져오기)으로 3개의 클래스(UserRepository, UserLocalDatasource  UserRemoteDataSource) 간의 관계로 구성된 종속 항목 그래프를 만듭니다. 

 

아래 처럼 사용

 // Create an instance of the application graph
    val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()
    // Grab an instance of UserRepository from the application graph
    val userRepository: UserRepository = applicationGraph.repository()

 

Dagger는 요청될 때마다 UserRepository의 새 인스턴스를 만듭니다.

    val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()

    val userRepository: UserRepository = applicationGraph.repository()
    val userRepository2: UserRepository = applicationGraph.repository()

    assert(userRepository != userRepository2)

 

때로 container의 종속 항목의 고유한  객체를 가지고 제공을 해야 될때가 있다.

  1. 하나의 LoginUserData를 사용하여 login Flow를 만들때, 서로 다른 ViewModel 객체에 같 동일한 LoginUserData(인스턴스)를 공유해야 한다 가정한다면,   서로 다른 Type의 객체(ViewModel)은 LoginUserData을 종속항목으로 가지면서 그 데이터는 고유함.
  2. 객체는 생성하는 데 비용이 많이 듭니다. 따라서 인스턴스를 종속 항목으로 선언할 때마다 새 인스턴스를 생성하지는 않으려고 합니다.

UserRepository를 요청할 때마다 항상 동일한 인스턴스를 얻도록 그래프에서 사용 가능한 고유한 UserRepository 인스턴스를 가지려고 할 수 있습니다.

 

더 복잡한 애플리케이션 그래프가 있는 실제 애플리케이션에서 

 

하나의 고유한 UserRepository에 여러 ViewModel 객체가 의존성을 가질 수 있다.

UserRepository를 제공해야 할 때마다 UserLocalDataSource 및 UserRemoteDataSource도 새 객체를 생성하지는 않고UserRepository를 제공한다. 

 

수동 종속 항목 삽입에서는 동일한 UserRepository 인스턴스를 ViewModel 클래스의 생성자에 전달함으로써 이 작업을 완료합니다. 그러나 Dagger에서는 코드를 수동으로 작성하지 않으므로 동일한 인스턴스를 사용하려는 것을 Dagger에 알려주기 위해서는

@Scope사용하면 됩니다.

Dagger로  @Scope 지정

@Scope을 사용하여 객체의 lifetime(동일한 객체를 주입하는 시간) Component의 lifeTime으로 제한할 수 있습니다.

즉,  객체를 제공해야 할 때마다 종속 항목의 동일한 인스턴스를 사용합니다.

 

ApplicationGraph의 Repository를 요청할 때 UserRepository의 고유한 인스턴스를 가지려면 @Component 인터페이스 및 UserRepository에 동일한 범위 주석을 사용합니다.

 Dagger에서 사용하는 javax.inject 패키지와 함께 이미 제공된 @Singleton 주석을 사용할 수 있습니다.

	
    // @Component 인터페이스의 @Scope은 Dagger에게 Scope를 가지는 클래스가 있음을 알려준다.
    // 주석(예 : @Singleton)은 "그래프의 수명에 바인딩"됩니다.
    // Type(객체)가 요청 될 때마다 해당 Type의 동일한 인스턴스가 제공됩니다. 
    @Singleton
    @Component
    interface ApplicationGraph {
        fun repository(): UserRepository
    }

    // Scope this class to a component using @Singleton scope (i.e. ApplicationGraph)
    @Singleton
    class UserRepository @Inject constructor(
        private val localDataSource: UserLocalDataSource,
        private val remoteDataSource: UserRemoteDataSource
    ) { ... }
 val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()

    val userRepository: UserRepository = applicationGraph.repository()
    val userRepository2: UserRepository = applicationGraph.repository()

    assert(userRepository == userRepository2)

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

Android Code Lab(Dagger2) 정리1  (0) 2021.12.20
Android app에서 Dagger 사용  (0) 2021.05.03
수동 종속성 삽입  (0) 2020.11.30
Android의 종속 항목 삽입  (0) 2020.11.24
Dagger2 (안드로이드 의존성 주입 프레임워크)란?  (0) 2020.08.04