Dagger는 DI Framework입니다. DI는 Dependency Injection의 준 말입니다. 의존성 주입이란 간단하게 설명하자면 A라는 객체를 B라는 객체에서 호출하여 생성하게 된다면 B는 A에 대해서 의존성을 갖게되는 것입니다. A의 값이 변경이 된다면 B에서도 영향을 끼칠 수 있게 된다는 것입니다.
그렇기에 지금 설명하고 있는 Dagger나 Hilt Koin과 같이 DI Framework들을 이용해서 객체를 외부에서 생성해 그 객체를 사용하는 곳에다가 주입하는 것입니다. 중간에 다리로 연결 시켜준다고 생각하시면 쉽게 이해될 것 같습니다.
의존성 파라미터를 생성자에 작성하지 않아도 되므로 보일러 플레이트를 많이 줄일 수 있습니다. 그렇기에 유연한 프로그래밍이 가능합니다.
Interface에 구현체를 쉽게 교체하면서 상황에 따라 적절한 행동을 정의할 수 있습니다. 이것은 특히 Mock 객체와 실제 객체를 바꿔가며 테스트할 때 유용합니다.
Scope 어노테이션을 통해 특정 그래프 Scope내에서 객체의 재사용여부를 지정할 수 있습니다.
일반적으로 @Singleton 어노테이션을 사용하여 Scope를 지정하고 객체를 재사용합니다.
모듈 클래스에서 @Provides 어노테이션 또는 인젝트 가능한 클래스에서 @Singleton 어노테이션을 사용하면 됩니다.
다음과 같이 사용할 수 있습니다.
@Singleton
@Provides
fun getUserDao(appDatabase: AppDatabase) : UserDao{
return appDatabase.getUserDao()
}
마찬가지로 Component에도 @Singleton 어노테이션을 붙여야합니다.
@Singleton
@Component(modules = [AppModule::class])
interface AppComponent {
fun inject(mainActivityViewModel: MainActivityViewModel)
}
컴포넌트에 붙은 Scope내에서 객체들을 관리하게 되고 처음 요청시에만 객체를 생성하고 그 다음부터는 처음에 제공한 같은 인스턴스를 제공합니다.
@Reusable도 @Singleton을 비롯한 다른 커스텀 스코프와 비슷한 역할을 합니다. 특정 컴포넌트 스코프에 종속되지 않기 때문에 컴포넌트에 @Reusable을 선언하지 않아도 됩니다. 이전 객체를 재사용 가능하면 재사용하고 아니면 새로 생성합니다. 즉 다른 스코프 어노테이션처럼 같은 인스턴스임을 보장하진 않습니다. 항상 동일한 인스턴스를 사용해야 하는게 아니라면 메모리 관리측면에서 조금 더 효율적입니다.
물론 Singleton 대신 Custom Scope을 만들 수도 있습니다. Scope내에서 더 작은 Scope를 지정할 때 Singleton 어노테이션 하나로 Scope를 나눌 순 없을테니 아래와 같이 커스텀 스코프를 만들 수 있습니다.
@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class ActivityScope
다음과 같이 Scope를 Custom해서 만들었다면 @Singleton과 같이 사용하면 됩니다.
@ActivityScope
@Component(modules = [AppModule::class])
interface AppComponent {
fun inject(mainActivityViewModel: MainActivityViewModel)
}
의존성 주입을 요청합니다. Inject 어노테이션으로 주입을 요청하면 연결된 Component가 Module로 부터 객체를 생성하여 넘겨줍니다.
종류 설명에 앞서 설명에 사용될 공통의 모듈을 작성하겠습니다.
@Module
class AppModul {
var next = 100
@Provides
fun provideInteger() {
println("computing...")
next++
}
class DirectCounter {
@Inject var value
fun print(){
println("printing")
println(value)
println(value)
println(value)
}
}
결과는 다음과 같습니다.
computing...
printing
100
100
100
한번 주입받은 객체는 여러번 참조해도 처음 주입받은 같은 객체입니다.
객체가 초기화하는데 시간이 필요하다면 Lazy 인젝션을 고려해볼 수 있습니다. binding 될 타입을 위해 Lazy<type.>을 만들어주기만 하면됩니다. 해당 객체의 경우는 get() 메서드를 호출해주기 전까지 객체가 초기화 되는것을 늦츨 수 있습니다.
class LazyCounter {
@Inject
lateinit var value : Lazy<Integer>
fun print(){
println("printing")
println(value.get())
println(value.get())
println(value.get())
}
}
결과에는 Direct와 변함이 없습니다.
printing
computing...
100
100
100
Lazy<타입>를 사용하면 객체의 생성을 get()함수의 호출까지 지연시킬 수 있습니다.
첫번째 get()시점에 객체가 생성되며 이후에 get()을 호출할 경우 동일한 객체가 반환되기에 Direct와 같은 결과가 나오게 됩니다.
지금까지 생성했던 객체들은 하나의 객체를 생성해서 채워주는 형태로만 사용했습니다.
다만 Dagger를 통해서 생성되는 객체가 여러개가 필요하다면 객체를 생성해주는 Provider 자체를 받아올 수 도있습니다.
class ProvideCounter {
@Inject
lateinit var value : Provider<Integer>
fun print(){
println("printing")
println(value.get())
println(value.get())
println(value.get())
}
}
그렇기에 결과는 다음과 같이 출력이 됩니다.
printing
computing...
100
computing...
101
computing...
102
이번 글은 여기까지 작성하고 다음 글로 찾아오겠습니다 읽어주셔서 감사합니다.