Hilt
Dagger DI 라이브러리 기반으로 빌드되어 Dagger Android 애플리케이션에 통합하는 표준 방법을 제공.
- Android 앱을 위한 Dagger 관련 인프라 간소화
- 앱간의 설정, 가독성 및 코드 공유를 용이하게 하기 위한 표준 구성요소 및 범위 세트 생성
- 테스트, 디버그 또는 출시와 같은 다양한 빌드 유형에 서로 다른 결합을 프로비저닝하는 쉬운 방법 제공
DI를 구현하면 코드 재사용 가능, 리팩터링 편의성, 테스트 편의성의 이점이 있다.
1. 루트의 build.gradle에 hilt-android-gradle-plugin을 dependency 추가
buildscript {
...
dependencies {
...
class 'com.google dagger:hilt-android-gradle-plugin:2.28-alpha'
}
}
2. app/build.gradle에 다음 dependency 추가.
...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
android {
...
}
dependencies {
implementation "com.google.dagger:hilt-android:2.28-alpha"
kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}
3. Hilt는 자바8 기능을 사용함. app/build.gradle에 다음을 추가.
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
4. Hilt를 사용하는 모든 앱은 @HiltAndroidApp으로 애노테이션이 지정된 Application 클래스를 포함해야 합니다.
@HiltAndroidApp은 애플리케이션 수준 종속 항목 컨테이너 역할을 하는 애플리케이션의 기본 클래스를 비롯하여 Hilt의 코드 생성을 트리거 합니다.
@HiltAndroidApp
class ExampleApplication : Application() { ... }
5. Android Class에 디펜던시 항목 삽입
Application 클래스에 Hilt를 설정하고 애플리케이션 수준 구성요소를 사용할 수 있게 되면 Hilt는 @AndroidEntryPoint 애노테이션이 있는 다른 Android 클래스에 종속 항목을 제공할 수 있습니다.
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() { ... }
- Hilt는 현재 다음 Android Class를 지원합니다.
- Application (@HiltAndroidApp)
- Activity
- Fragment
- View
- Service
- BroadcastReceiver
- Android Class에 @AndroidEntryPoint로 애노테이션을 지정하면 이 클래스에 종속된 Android Class에도 애노테이션을 지정해야 합니다.예를 들면 Fragment에 애노테이션을 지정하면 이 Fragment를 사용하는 활동에도 애노테이션을 지정해야 합니다.
6. 구성요소에서 종속항목을 가져오려면 @Inject 애노테이션을 사용하여 필드 삽입을 실행합니다.
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
@Inject lateinit var analytics: AnalyticsAdapter
...
}
생성자 삽입방법. 생성자에서 @inject 애노테이션을 사용하여 클래스의 인스턴스를 제공하는 방법을 Hilt에 알려줄 수 있습니다.
class AnalyticsAdapter @inject constructor(
private val service: AnalyticsService
)
Hilt 모듈
@Module로 애노테이션된 클래스.
이 모듈은 특정 유형의 인스턴스를 제공하는 방법을 Hilt에 알려줍니다. 그러나 Dagger모듈과는 달리 Hilt모듈에 @InstallIn 애노테이션을 지정하여 각 모듈을 사용하거나 설치할 Android Class를 Hilt에 알려야 합니다.
interface AnalyticsService { fun analyticsMethods() } class AnalyticsServiceImpl @Inject constructor( ... ) : AnalyticsService { ... } @Module @InstallIn(ActivityComponent::class) // AnalyticsModule의 모든 종속 항목을 앱의 모든 활동에서 사용할 수 있음을 의미. abstract class AnalyticsModule { @Binds abstract fun bindAnalyticsService( analyticsServiceImpl: AnayticsServiceImpl ): analyticsService }
7. @Provides를 사용하여 인스턴스 삽입
클래스가 외부 라이브러리에서 제공되므로 클래스를 소유하지 않은 경우(Retrofit, OkHttpClient) 또는 Room 데이터베이스) 또는 빌더 패턴으로 인스턴스를 생성해야 하는 경우에도 생성자 삽입이 불가능합니다.
@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) } }
8. 동일한 유형에 대해 여러 결합제공.
서로 다른 두가지 OkHttpClient 구현을 제공하는 방법을 Hilt에 알려야 한다.
먼저 다음과 같이 @Binds 또는 @Provides 메소드에 애노테이션을 지정하는데 사용할 한정자를 정의합니다.
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttpClient
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherInterceptorOkHttpClient
@Provides와 함께 Hilt 모듈을 사용할 수 있다. 두 메소드는 동일한 반환 유형을 갖지만 한정자는 다음과 같이 두 가지의 서로 다른 결합으로 메서드에 라벨을 지정한다.
@Module
@InstallIn(ApplicationComponent::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()
}
}
다음과 같이 필드 또는 매개변수에 해당 한정자로 애노테이션을 지정하여 필요한 특정 유형을 삽입할 수도 있다.
@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)
}
}
class ExampleServiceImpl @Inject constructor(
@AuthInterceptorOkHttpClient private val okHttpClient: OkHttpClient
) : ...
@AndroidEntryPoint
class ExampleActivity: AppCompatActivity() {
@AuthInterceptorOkHttpClient
@Inject lateinit var okHttpClient: OkHttpClient
}
9. 사전 정의된 한정자
사전 정의된 한정자를 제공하는데 예로 Context 한정자는 아래와 같다.
class AnalyticsAdaptor @Inject constructor(
@ActivityContext private val context: Context,
private val service: AnalyticsService
) { ... }
class AnalyticsServiceImpl @Inject constructor(
@ApplicationContext context: Context
) : AnalyticsService { ... }
// The Application binding is available without qualifiers.
class AnalyticsServiceImpl @Inject constructor(
application: Application
) : AnalyticsService { ... }
// The Activity binding is available without qualifiers.
class AnalyticsAdapter @Inject constructor(
activity: FragmentActivity
) { ... }
Android 구성요소 | 기본 결합 |
---|---|
ApplicationComponent | Application |
ActivityRetainedComponent | Application |
ActivityComponent | Application, Activity |
FragmentComponent | Application, Acitivity, Fragment |
ViewComponent | Application, Activity, View |
ViewWithFragmentComponent | Application, Acitivyt, Fragment, View |
ServiceComponent | Application, Service |
10. Hilt가 지원하지 않는 클래스에 종속 항목 삽입
Hilt가 지원하지 않는 클래스에 필드 삽입을 실행해야 할 경우가 있는데 이때에는 @EntryPoint 애노테이션을 사용하여 만들 수 있습니다. EntryPoint는 Hilt가 관리하는 코드와 그렇지 않은 코드 사이의 경계입니다.
@EntryPoint로 지정된 인터페이스를 정의하고 한정자를 포함해야 합니다. 그리고 다음과 같이 @InstallIn을 추가하여 설치할 구성요소를 지정합니다.
class ExampleContentProvider: ContentProvider() {
@EntryPoint
@InstallIn(ApplicationComponent::class)
interface ExampleContentProviderEntryPoint {
fun anlyticsService(): AnalyticsService
}
}
EntryPoint에 엑세스하기 위해서는 EntryPointAccessors를 사용합니다. 매개변수는 구성요소 인스턴스이거나 구성요소 소유자 역할을 하는 @AndroidEntryPoint객체여야 합니다. 매개변수로 전달하는 구성요소와 EntryPointAccessors 정적 메서드가 모두 @EntryPoint 인터페이스의 @InstallIn 애노테이션에 있는 Android클래스와 일치하는지 확인합니다.
class ExampleContentProvider: ContentProvider() {
...
override fun query(...): Cursor {
val appContext = context?.applicationContext ?: throw IllegalStateException()
val hiltEntryPoint = EntryPointAccessors.
fromApplication(appContext, ExampleContentProviderEntryPoint::class.java)
val analyticsService = hiltEntryPoint.analyticsService()
...
}
}
entryPoint가 ApplicationComponent에 설치되어 있으므로 ApplicationContext를 사용하여 검색한다. 결합이 ActivityComponent에 있다면 ActivityContext를 대신 사용해야 합니다.