Identifies scope annotations. A scope annotation applies to a class containing an injectable constructor and governs how the injector reuses instances of the type.
Dependency injection을 할 때, 필요할 때 마다 생성하는 것이 아니라 동일한 dependency를 주입해주어야 할 때가 있습니다. 그런 경우에 Scope
를 정의하여 동일한 Component객체내에서 같은 객체를 재사용 하도록 할 수 있습니다.
@Scope
annotation은 직접 Component나 dependency에 적용하는 것이 아니라 사용자가 원하는 Scope annotation 클래스를 정의할 때 사용하는 것입니다.
// ActivityComponent에서 사용할 Scope annotation class
@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class ActivityScope
위와 같이 Scope를 정의하였으면 적용해야할 dependency와 Component에 적용을 해주어야 합니다.
@Module
object ActivityModule {
// Dependency를 ActivityScope가 적용된 Component에서
// 단일 객체를 재사용하도록 지정
@Provides
@ActivityScope
fun provideMyDependency(): MyDependency = MyDependency()
}
// Component에서도 ActivityScope를 적용
@Subcomponent(modules = [ActivityModule::class])
@ActivityScope
interface MainActivityComponent {
@Subcomponent.Factory
interface Factory {
fun create(): MainActivityComponent
}
fun inject(activity: MainActivity)
}
이렇게 MainActivityComponent와 적용이 필요한 dependency에 ActivityScope
를 적용하면 MyDependency
객체는 동인한 MainActivityComponent객체에서 provide할 때 동일한 객체를 재사용하여 제공하게 됩니다.
Identifies qualifier annotations.
하나의 type에 대해 다른 객체를 주입해야 하는 경우가 발생할 수 있습니다. 하지만 하나의 type에 대해 두가지 이상의 dependency를 provide하는 방법이 존재할 경우 bound multiple times에러가 발생하게 됩니다.
예를 들어 Coroutine의 dispatcher를 provide하는 module이 아래와 같이 정의되어 있다고 가정해봅시다.
@Module
object DispatcherModule {
@Provides
fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
@Provides
fun provideDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
}
하나는 IO dispatcher를 provide하고 하나는 Default dispatcher를 provide합니다. 그러나 두 dispatcher의 type은 같아서 빌드시 에러가 나게 됩니다.
error: [Dagger/DuplicateBindings] kotlinx.coroutines.CoroutineDispatcher is bound multiple times
동일한 type에 대해 dependency를 구분지어주기 위해서 Qualifier
annotation 클래스를 정의해주어야 합니다.
// IO dispatcher를 구분하기 위한 qualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class IoDispatcher
이제 정의해준 qualifier를 provide와 주입받을 곳에 적용해주면 됩니다.
@Module
object DispatcherModule {
@Provides
@IoDispatcher
fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
@Provides
fun provideDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
}
class ConversionRepositoryImpl @Inject constructor(
private val api: ExchangeRateApi,
// IO dispatcher를 주입받음
@IoDispatcher private val ioDispatcher: CoroutineDispatcher
) : ConversionRepository {
...
}
위의 repository에서 @IoDispatcher
annotation을 제거하게 되면 IO dispatcher가 아니라 Default dispatcher를 주입받게 됩니다.
T라는 type의 dependency를 주입받을 때 T라는 type으로만 주입받을 수 있는 것이 아니라 Provider<T> 혹은 Lazy<T>로도 주입받을 수 있습니다.
Provides instances of T.
Provider<T>
는 type T에 대해서 다수의 객체를 생성할 때 사용됩니다. 대표적으로 factory에서 생성할 객체마다 다른 dependency를 주입해야 할 때 사용할 수 있습니다.
class Factory @Inject constructor(
private val provider: Provider<MainViewModel>
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return provider.get() as T
}
}
위는 MainViewModel을 생성할 때 사용할 MainViewModel.Factory입니다. 여기서 Factory가 Provider<MainViewModel>
을 주입받아서 새로운 MainViewModel을 생성할 때 사용합니다.
Provider는 무작정 새로운 instance를 생성하는 것이 아니라 Scope에 맞게 생성합니다.
@Module
object ActivityModule {
// Dependency를 ActivityScope가 적용된 Component에서
// 단일 객체를 재사용하도록 지정
@Provides
@ActivityScope // @ActivityScope는 MainActivityComponent에도 적용
fun provideMyDependency(): MyDependency = MyDependency()
}
class MainActivity : AppCompatActivity() {
@Inject
lateinit var myDependencyProvider: Provider<MyDependency>
@FlowPreview
override fun onCreate(savedInstanceState: Bundle?) {
(application as ConversionApplication).appComponent
.getMainActivityComponentFactory()
.create(this)
.inject(this)
super.onCreate(savedInstanceState)
setContentView(binding.root)
// 아래의 둘은 같은 객체를 갖게 된다.
myDependencyProvider.get()
myDependencyProvider.get()
}
}
위의 예제에서 myDependencyProvider는 get()을 호출하였을 때 같은 객체를 return하게 됩니다. 그 이유는 MyDependency와 MainActivityComponent에 @ActivityScope
가 적용이 되었으므로 동일한 Component 객체내에서 같은 MyDependency를 반환하기 때문입니다.
만약 MyDependency에 아무런 Scope가 적용되지 않았다면 get()을 호출할 때 마다 새로운 객체를 생성해서 return하게 됩니다.
A handle to a lazily-computed value. Each Lazy computes its value on the first call to get() and remembers that same value for all subsequent calls to get().
Lazy<T>
는 Kotlin의 lazy와 비슷하게 type T의 객체를 필요할 때 생성하기 위해서 사용됩니다. Provider와 마찬가지로 get()을 이용하여 객체를 가져올 수 있습니다.
Lazy와 Provider의 차이점은 Provider의 경우 get()을 호출할 때 마다 Scope에 맞게 새로운 객체를 생성합니다. 하지만 Lazy의 경우 처음 한번만 객체를 생성한 이후에는 생성한 객체를 memory에 cache하고 해당 객체를 return합니다.
[1] "Using Scopes," Android Developers, last modified n.d., accessed Jun 19, 2022, https://developer.android.com/codelabs/android-dagger#8.
[2] "@Provides annotation and Qualifiers," Android Developers, last modified n.d., accessed Jun 19, 2022, https://developer.android.com/codelabs/android-dagger#14.