클래스가 필요한 객체를 얻는 3가지 방법
- 클래스가 필요한 의존성을 가짐 (ex) val engine = Engine()
- 다른 곳에서 가져옴 (ex) Context — getSystemService()
- 파라미터로 전달받음 (ex) Car(engine: Engine) { … }
- 3번이 Dependency Injection으로, 아래의 예제를 보자.
/*
아래는 1번의 경우, 직접적인 의존성을 가진다.
*/
class Car {
private val engine = Engine()
fun start() {
engine.start()
}
}fun main(args: Array) {
val car = Car()
car.start()
}/*
아래는 3번의 경우, 파라미터를 통해 전달받으며 이렇게 툴을 사용하지 않고
직접 DI를 하는 것을 manual DI라고 한다.
*/
class Car(private val engine:Engine) {
fun start() {
engine.start()
}
}fun main(args: Array) {
val engine = Engine()
val car = Car(engine)
car.start()
}
/*
Field Injection 또는 Setter Injection이라고 한다. 특정 Android 프레임워크 클래스(Activity 또는 Fragment와 같은)는 시스템에서 초기화되기 때문에 constructor injection이 불가능하다. field injection은 의존성이 class으 생성 이후에
이루어진다.
*/
class Car {
lateinit var engine: Engine
fun start() {
engine.start()
}
}fun main(args: Array) {
val car = Car()
car.engine = Engine()
car.start()
}
Benefits of using Dagger
- DI를 위해 직접 작성했던 코드를 Dagger에서 생성
- 내부적으로 Dagger는 클래스의 객체를 제공할 방법을 찾는 객체 그래프를 생성
- 그래프 안의 모든 클래스를 위해, Dagger 는 내부적으로 객체를 얻기 위한 타입으로 사용되는 factory-type 클래스를 생성
- 빌드 시간에 Dagger는 모든 객체의 의존성이 안정적이게 하고 때문에 Runtime Exception이 발생하지 않으며, 의존성 사이클이 없도록 해서 무한 루프에 빠지지 않도록 함
@Inject
- Dagger가 어떻게 이 객체의 인스턴스를 생성할지 알게 함
class UserRepository @Inject constuctor(
private val localDataSource: UserLocalDataSource,
private val remoteDataSource: UserRemoteDataSource) {...}
- @Inject annotated constructor로 UserRepository 인스턴스를 어떻게 생성할지 알려줌
- 의존성이 무엇인지 알려줌: UserLocalDataSource, UserRemoteDataSource
- Dagger는 UserRepository의 인스턴스를 어떻게 생성해야할 지에 대해서 알지만, 이 의존성을 어떻게 생성해야할 지에 대해서는 모름. 만약 다른 클래스에도 annotation을 붙이면 Dagger는 그 클래스들의 생성 방법을 알게 됨
class UserLocalDataSource @Inject constructor() {...}
class UserRemoteDataSource @Inject constructor() {...}
Dagger component
- Dagger가 언제, 어디서 의존성이 필요한지 알 수 있도록 의존성 그래프를 만드는 것을 돕기 위해 @Component 어노테이션이 붙은 interface를 만들어야 함
- Dagger는 manual dependency injection 으로 했을 때 처럼 container를 만듦
- @Component interface는 필요한 클래스의 인스턴스를 반환하는 함수를 정의. @Component 어노텡테이션은 Dagger가 모든 의존성이 안전하게 요구되도록 container를 정의할 수 있도록 함. 이것을 Dagger Container라고 하고, 각자의 의존성과 어떻게 제공할지에 대해 알고있는 객체로 되어있는 그래프를 가짐.
@Component
interface ApplicationGraph {
fun repository(): UserRepository
}
@Component: Dagger가 의존성 그래프를 생성하도록 함
- Dagger가 적용된 프로젝트를 빌드하면 Dagger는 ApplicationGraph interface의 구현을 생성: DaggerApplicationGraph
Scoping
- 같은 인스턴스의 의존성이 항상 사용된다는 것을 의미하며 그 타입은 제공되어야 함
- 유일한 인스턴스를 얻기 위해 @Singletone annotation을 사용할 수 있음
- @Component interface 위의 @Singletone scope annotation은 Dagger에 그래프 내 생명주기와 항상 같은 인스턴스를 제공해야 한다는 것을 알려줌
@Singletone
Summary
- @Inject는 constructor injection에서 Dagger graph 에 타입을 추가할 때 언제든 사용할 수 있다.
- @Inject를 사용할 수 없는 경우는 아래 두 가지 경우다
— @Binds를 사사용하여 Dagger에 interface 구현이 되어야한다고 말할 때
— @Provides를 사용하여 Dagger에 어떻게 프로젝트에 없는 class를 제공할지 표현할 때
- component에는 module을 한번만 지정할 수 있음
- scope annotation은 생명주기에 의존하며, 생명주기 종류는 아래와 같음
— @ApplicationScope, @LoggedUserScope, @ActivityScope