Android Jetpack
의 라이브러리DI 라이브러리인 Dagger
를 기반으로 빌드되어, Dagger
가 제공하는 컴파일 시간 정확성, 런타임 성능, 확장성 및 Android Studio
지원의 이점을 누릴 수 있다.
안드로이드 애플리케이션에서 Dagger
사용과 관련된 많은 보일러플레이트 코드를 줄일 수 있다.
컴포넌트에 사용할 Scope 어노테이션을 자동으로 생성해준다.
Application
및 Activity
와 같은 Android 클래스를 나타내는 미리 정의된 바인딩이 있다.
@ApplicationContext
및 @ActivityContext
를 나타내는 미리 정의된 qualifier가 있다.
DI 구현의 장점
build.gradle
파일에 hilt-android-gradle-plugin
추가buildscript {
...
ext.hilt_version = '2.33-beta'
dependencies {
...
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
}
}
app/build.gradle
파일에 apply plugin
과 dependencies
추가Hilt
와 DataBinding
을 모두 사용하는 프로젝트의 경우, Android Studio 버전이 4.0 이상이어야 한다....
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
android {
...
}
dependencies {
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-compiler:$hilt_version"
}
app/build.gradle
에 Java 8 compileOptions
추가android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
Hilt
를 사용하는 모든 앱에는 @HiltAndroidApp
어노테이션이 있는 Application
클래스가 존재해야 한다.@HiltAndroidApp
은 애플리케이션 레벨의 종속성 컨테이너를 제공하는 베이스 클래스를 포함하는 Hilt
의 코드를 생성한다.@HiltAndroidApp
class ExampleApplication : Application() { … }
Hilt
가 Application 클래스에 설정되고 애플리케이션 레벨의 구성요소를 사용할 수 있게 되면, Hilt
는 @AndroidEntryPoint
어노테이션이 있는 다른 Android 클래스에 종속성을 제공할 수 있다.Hilt
가 지원하는 Android 클래스@HiltAndroidApp
사용)@HiltViewModel
사용)@AndroidEntryPoint
를 이용하여 Android 클래스에 어노테이션을 달아주었다면, 해당 클래스에 종속된 Android 클래스에도 어노테이션을 추가해주어야 한다.@AndroidEntryPoint
는 프로젝트의 Android 클래스에 대해 개별적인 Hilt
컴포넌트를 생성한다.Hilt
컴포넌트는 상위 클래스로부터 종속성 주입을 받을 수 있다.@Inject
를 사용해야 한다.Hilt
에 의해 주입된 필드는 private
일 수 없다. Hilt
를 통해 private
필드를 주입한 경우, 컴파일 에러가 발생한다.Hilt
가 주입한 클래스는 주입을 사용한 다른 베이스 클래스를 지닐 수 있다.@AndroidEntryPoint
어노테이션이 필요하지 않다.@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
@Inject lateinit var analytics: AnalyticsAdapter
...
}
Hilt
가 해당 컴포넌트에서 필요한 종속성의 인스턴스를 제공하는 방법을 알아야 한다.Hilt
에 바인딩 정보를 제공하기 위해서 constructor injection을 사용한다.@Inject
어노테이션을 사용하여 해당 클래스에 인스턴스를 제공하는 방법을 Hilt
에 알린다.class AnalyticsAdapter @Inject constructor(
private val service: AnalyticsService
) { ... }
@Inject
어노테이션으로 처리된 생성자의 파라미터들은 해당 클래스의 종속성이다.AnalyticsAdapter
는 AnalyticsService
를 종속성으로 지니므로, Hilt
는 AnalyticsService
의 인스턴스를 어떻게 제공하는지에 대한 방법을 반드시 알고있어야 한다.Hilt module
을 이용하여 바인딩 정보를 Hilt
에 제공할 수 있다.Hilt module
은 @Module
어노테이션이 있는 클래스이며, Hilt
에 특정 타입의 인스턴스를 제공하는 방법을 알린다.Dagger module
과는 달리 Hilt module
에는 각 모듈이 사용되거나 설치될 Android 클래스를 Hilt
에 알리는 @InstallIn
어노테이션을 반드시 추가해야 한다.Hilt module
에서 제공하는 종속성은 Hilt module
을 설치하는 Android 클래스와 연결된 모든 생성된 컴포넌트에서 사용할 수 있다.Hilt
의 코드 생성은 Hilt
를 사용하는 모든 Gradle module
에 접근해야 하므로, Application 클래스를 컴파일하는 Gradle module
역시 모든 Hilt module
과 constructor-injected 클래스를 지니고 있어야 한다.@Binds
를 사용하여 인터페이스 인스턴스 주입Hilt module
내의 @Binds
어노테이션을 이용하여 추상 함수를 만들어서 바인딩 객체와 함께 Hilt를 제공할 수 있다.@Bind
어노테이션은 Hilt
가 인터페이스의 인스턴스를 필요로 할 때 어떤 implementation을 사용해야할지 알려준다.@Bind
어노테이션이 있는 함수는 다음의 정보를 Hilt
에 알려준다Hilt
에 알려준다.Hilt
에 알려준다.interface AnalyticsService {
fun analyticsMethods()
}
// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
class AnalyticsServiceImpl @Inject constructor(
...
) : AnalyticsService { ... }
@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {
@Binds
abstract fun bindAnalyticsService(
analyticsServiceImpl: AnalyticsServiceImpl
): AnalyticsService
}
Hilt module
인 AnalyticsModule
은 Hilt
가 ExampleActivity
로 종속성을 주입하도록 하기 위해 @InstallIn(ActivityComponent::class)
어노테이션으로 처리 되었다.@InstallIn(ActivityComponent::class)
어노테이션은 AnalyticsModule
의 모든 종속성을 앱의 모든 액티비티에서 사용할 수 있음을 의미한다.@Provides
를 사용하여 인스턴스 주입Retrofit
, Room
등)의 클래스 등과 같은 소유하고 있지 않은 클래스 및 빌더 패턴으로 생성된 인스턴스에서도 불가능하다.@Provides
어노테이션으로 처리하여 타입 인스턴스를 제공하는 방법을 Hilt
에 알릴 수 있다.@Provides
어노테이션이 있는 함수는 다음의 정보를 Hilt
에 알려준다Hilt
에 알려준다.Hilt
에 알려준다.Hilt
에 알려준다. 타입의 인스턴스를 제공할 필요가 있을 때마다 Hilt
는 함수의 바디를 실행한다.@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {
@Provides
fun provideAnalyticsService(
// Potential dependencies of this type
): AnalyticsService {
return Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(AnalyticsService::class.java)
}
}
Hilt
가 필요한 경우, Hilt
에 어려 종류의 바인딩을 제공해야 한다.Hilt
가 잘못된 종속성을 주입할 수 있다.OkHttpClient
의 두 가지 다른 구현을 Hilt에 제공하기 위해서는 @Qualifier
어노테이션을 이용하여 @Binds
또는 @Provides
메서드에 사용할 qualifier를 정의해야 한다.@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttpClient
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherInterceptorOkHttpClient
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@AuthInterceptorOkHttpClient
@Provides
fun provideAuthInterceptorOkHttpClient(
authInterceptor: AuthInterceptor
): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(authInterceptor)
.build()
}
@OtherInterceptorOkHttpClient
@Provides
fun provideOtherInterceptorOkHttpClient(
otherInterceptor: OtherInterceptor
): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(otherInterceptor)
.build()
}
}
// As a dependency of another class.
@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {
@Provides
fun provideAnalyticsService(
@AuthInterceptorOkHttpClient okHttpClient: OkHttpClient
): AnalyticsService {
return Retrofit.Builder()
.baseUrl("https://example.com")
.client(okHttpClient)
.build()
.create(AnalyticsService::class.java)
}
}
// As a dependency of a constructor-injected class.
class ExampleServiceImpl @Inject constructor(
@AuthInterceptorOkHttpClient private val okHttpClient: OkHttpClient
) : ...
// At field injection.
@AndroidEntryPoint
class ExampleActivity: AppCompatActivity() {
@AuthInterceptorOkHttpClient
@Inject lateinit var okHttpClient: OkHttpClient
}
Hilt
qualifierContext
클래스가 필요한 경우 Hilt
는 @ApplicationContext
와 @ActivityContext
qualifier를 제공한다.class AnalyticsAdapter @Inject constructor(
@ActivityContext private val context: Context,
private val service: AnalyticsService
) { ... }
@InstallIn
어노테이션으로부터 참조할 수 있는 관련 Hilt
컴포넌트가 있다.Hilt
컴포넌트는 해당 Android 클래스에 바인딩을 주입하는 역할을 한다.Hilt component | Inject for |
---|---|
SingletonComponent | Application |
ActivityRetainedComponent | N/A |
ViewModelComponent | ViewModel |
Activityomponent | Activity |
FragmentComponent | Fragment |
ViewComponent | View |
ViewWithFragmentComponent | View annotated with @WithFragmentBindings |
ServiceComponent | Service |
Hilt
는 SingletonComponent
로부터 바로 BroadcastReceiver
를 주입하기 때문에 BroadcastReceiver
에 대해서는 컴포넌트를 생성하지 않는다.Hilt
는 생성된 컴포넌트 클래스의 인스턴스를 해당 Android 클래스의 생명주기에 따라 자동으로 생성하고 파괴한다.Generated component | Created at | Destroyed at |
---|---|---|
SingletonComponent | Application#onCreate() | Application#onDestroy() |
ActivityRetainedComponent | Activity#onCreate() | Activity#onDestroy() |
ViewModelComponent | ViewModel created | ViewModel destroyed |
Activityomponent | Activity#onCreate() | Activity#onDestroy() |
FragmentComponent | Fragment#onAttach() | Fragment#onDestroy() |
ViewComponent | View#super() | View destroyed |
ViewWithFragmentComponent | View#super() | View destroyed |
ServiceComponent | Service#onCreate() | Service#onDestroy() |
ActivityRetainedComponent
는 Activity#onCreate()
의 시작에 생성되고 Activity#onDestroy()
의 끝에 파괴되므로, 구성 변경 전체에 걸쳐 살아있다.Hilt
의 모든 바인딩은 Scope가 지정되지 않으며, 앱에서 바인딩을 요청할 때마다 Hilt
는 필요한 유형의 새 인스턴스를 만든다.Hilt
의 바인딩을 특정 컴포넌트 Scope로 지정할 경우, 바인딩 범위가 지정된 컴포넌트의 인스턴스당 한 번만 Scope 바인딩을 생성하고 해당 바인딩에 대한 모든 요청은 동일한 인스턴스를 공유한다.Android class | Generated component | Scope |
---|---|---|
Application | SingletonComponent | @Singleton |
Activity | ActivityRetainedComponent | @ActivityRetainedScoped |
ViewModel | ViewModelComponent | @ViewModelScoped |
Activity | ActivityComponent | @ActivityScoped |
Fragment | FragmentComponent | @FragmentScoped |
View | ViewComponent | @ViewScoped |
View annotated with @WithFragmentBindings | ViewWithFragmentComponent | @ViewScoped |
Service | ServiceComponent | @ServiceScoped |
// If AnalyticsService is an interface.
@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {
@Singleton
@Binds
abstract fun bindAnalyticsService(
analyticsServiceImpl: AnalyticsServiceImpl
): AnalyticsService
}
// If you don't own AnalyticsService.
@Module
@InstallIn(SingletonComponent::class)
object AnalyticsModule {
@Singleton
@Provides
fun provideAnalyticsService(): AnalyticsService {
return Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(AnalyticsService::class.java)
}
}
Hilt
컴포넌트에는 Hilt
가 사용자 커스텀 바인딩에 종속성으로 주입할 수 있는 기본 바인딩이 함께 제공된다.Hilt
는 single activity 컴포넌트 정의를 바탕으로 모든 activity를 주입하기 때문에 이러한 바인딩은 특정 하위 클래스가 아닌 일반 Activity
및 Fragment
유형이다.Activity
는 이 컴포넌트의 다른 인스턴스를 지닌다.Android component | Default bindings |
---|---|
SingletonComponent | Application |
ActivityRetainedComponent | Application |
ViewModelComponent | SavedStateHandle |
ActivityComponent | Application , Activity |
FragmentComponent | Application , Activity , Fragment |
ViewComponent | Application , Activity , View |
ViewWithFragmentComponent | Application , Activity , Fragment , View |
ServiceComponent | Application , Service |
@ApplicationContext
어노테이션을 통해서도 가능하다.class AnalyticsServiceImpl @Inject constructor(
@ApplicationContext context: Context
) : AnalyticsService { ... }
// The Application binding is available without qualifiers.
class AnalyticsServiceImpl @Inject constructor(
application: Application
) : AnalyticsService { ... }
@ActivityBinding
어노테이션을 통해서도 가능하다.class AnalyticsAdapter @Inject constructor(
@ActivityContext context: Context
) { ... }
// The Activity binding is available without qualifiers.
class AnalyticsAdapter @Inject constructor(
activity: FragmentActivity
) { ... }
Hilt
는 일반적인 Android 클래스에 대한 지원을 제공하지만 Hilt
가 지원하지 않는 클래스에서 필드 주입을 수행해야 하는 경우가 있을 수 있다.@EntryPoint
어노테이션을 사용하여 entry point를 만들 수 있다.Hilt
에서 관리하는 코드와 그렇지 않은 코드 사이의 경계이며, Hilt가 관리하는 객체의 그래프에 코드가 처음 입력되는 지점이다.Hilt
가 종속성 그래프 내의 종속성을 제공할 수 있도록 Hilt
에서 관리하지 않는 코드를 사용할 수 있게 한다.Hilt
가 지원하지 않는 Content Provider
가 Hilt
를 사용해서 일부 종속성을 가져오도록 하려면 각 바인딩 타입에 @EntryPoint
어노테이션과 함께 인터페이스를 정의하고 qualifier를 포함해야 한다.@InstallIn
어노테이션을 추가하여 entry point를 설치할 컴포넌트를 특정해주어야 한다.class ExampleContentProvider : ContentProvider() {
@EntryPoint
@InstallIn(SingletonComponent::class)
interface ExampleContentProviderEntryPoint {
fun analyticsService(): AnalyticsService
}
...
}
EntryPointAccessors
의 적절한 정적 메서드를 사용해야 한다.@AndroidEntryPoint
객체여야 한다.EntryPointAccessors
정적 메서드가 @EntryPoint
인터페이스의 @InstallIn
어노테이션이 있는 Android 클래스와 일치하는지 확인해야 한다.