일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- builderPattern
- 디자인패턴 #
- designPattern
- ㅋㅁ
- Singleton
- F
- 추상 팩토리
- factory method
- 함수형프로그래밍
- r
- 프로토타입 패턴
- ㅓ
- Abstract Factory
- a
- PrototypePattern
- 코틀린
- Kotlin
- 싱글톤
- El
- 추상팩토리패턴
- Design Pattern
- 디자인패턴
- Observer Pattern
- Functional Programming
- 빌터패턴
- 옵저버 패턴
- 팩토리 메소드
- Today
- Total
오늘도 더 나은 코드를 작성하였습니까?
Hilt을 사용한 종속성 주입 본문
Hilt는 프로젝트에서 수동 종속성 주입을 수행하는 상용구를 줄여주는 Android 용 종속성 주입 라이브러리입니다.
수동 종속성 주입을 수행하려면 모든 클래스와 해당 종속성을 수동으로 생성하고 Container 사용하여 종속성을 재사용하고 관리해야합니다.
Hilt는 프로젝트의 모든 Android 클래스에 Container를 제공하고 수명주기를 자동으로 관리하여 애플리케이션에서 DI를 사용하는 표준 방법을 제공합니다.
Hilt는 Dagger가 제공하는 컴파일 시간 정확성, 런타임 성능, 확장 성 및 Android Studio 지원의 이점을 누릴 수 있도록 인기있는 DI 라이브러리 Dagger 위에 빌드되었습니다.
안드로이드 프로젝트에 종속성 추가.
프로젝트 수준 Gradle
buildscript {
...
ext.hilt_version = '2.35'
dependencies {
...
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
}
}
모듈수준 Gradle
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"
}
Hilt application class
Hilt를 사용하는 모든 앱은 @HiltAndroidApp으로 주석이 달린 Application 클래스를 포함해야합니다
@HiltAndroidApp는 애플리케이션 수준 종속성 Container 역할을하는 애플리케이션의 기본 클래스를 포함하여 Hilt의 코드 생성을 트리거합니다.
@HiltAndroidApp
class ExampleApplication : Application() { ... }
Hilt Component 는 Application 객체의 수명주기에 연결되고 종속성을 제공합니다.
또한 앱의 parent component이므로 other components가 제공하는 종속성에 접근 할 수 있습니다.
Android 클래스에 종속성 삽입
Hilt가 Application 클래스에 설정되고 Application 수준 component를 사용할 수있게되면,
Hilt는 @AndroidEntryPoint 주석이있는 다른 Android 클래스에 종속성을 제공 할 수 있습니다.
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() { ... }
Hilt는 현재 다음 Android 클래스를 지원합니다
- Application (by using @HiltAndroidApp)
- ViewModel (by using @HiltViewModel)
- Activity
- Fragment
- View
- Service
- BroadcastReceiver
@AndroidEntryPoint를 사용하여 Android 클래스에 주석을 달면 이에 종속 된 Android 클래스에도 주석을 달아야합니다.
예를 들어 Fragment에 @AndroidEntryPoint을 달면 해당 Fragment을 사용하는 Activity에도 @AndroidEntryPoint에도 주석을 달아야 한다.
*참고
Hilt는 AppCompatActivity와 같이 ComponentActivity를 확장하는 Activity만 지원합니다.
Hilt는 androidx.Fragment를 확장하는 Fragment만 지원합니다.
Hilt does not support retained fragments.(뭔 말인지 모르겠다.)
@AndroidEntryPoint는 프로젝트의 각 Android 클래스에 대해 개별적인 Hilt component 를 생성합니다.
즉, @AndroidEntryPoint를 1개 Android클래스에 붙히면 1개의 component를 생성하는것.
이러한 component는 Component hierarchy에 설명 된대로 해당 부모 클래스에서 종속성을받을 수 있습니다.
component에서 종속성을 얻으려면 @Inject 주석을 사용하여 필드 주입
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
@Inject lateinit var analytics: AnalyticsAdapter
...
}
Hilt가 주입 한 필드는 비공개(private)가 될 수 없습니다.
Hilt를 사용하여 비공개 필드를 삽입하려고하면 컴파일 오류가 발생합니다.
Hilt가 주입하는 클래스는 또 다른 클래스의 의존성을 지닐수 있다.
그것이 추상클래스라면 @AndroidEntryPoint 애노테이션을 붙힐 필요가 없다.
Hilt bindings 정의하기
필드 주입을 수행하려면 Hilt는 해당 component 에서 필요한 종속성 인스턴스를 제공하는 방법을 알아야합니다
bindings에는 해당 인스턴스를 종속성으로 제공하는 데 필요한 정보가 포함되어 있습니다.
Hilt에 바인딩 정보를 제공하는 한 가지 방법은 생성자 주입입니다.
클래스 생성자에서 @Inject 주석을 사용하여 Hilt에게 해당 클래스의 인스턴스를 제공하는 방법을 알려줍니다.
class AnalyticsAdapter @Inject constructor(
private val service: AnalyticsService
) { ... }
@Inject 주석이 달린 생성자 클래스의 매개 변수는 해당 클래스의 종속성입니다.
예에서 AnalyticsAdapter에는 AnalyticsService가 종속성으로 있습니다. 따라서 Hilt는 AnalyticsService의 인스턴스를 제공하는 방법도 알고 있어야합니다.
*빌드시간에 Hilt는 Android 클래스용 Dagger component 를 생성합니다. 이후 Dagger는 코드를 살펴보고 다음 단계를 수행합니다.
- 종속성 그래프를 작성하고 유효성을 검사하여 충족되지 않은 종속성과 종속성주기가 없는지 확인
- 실제 객체 및 해당 종속성을 만들기 위해 런타임에 사용하는 클래스를 생성합니다.
Hilt modules
특정 타입은 생성자 주입이 불가능할 때가 있습니다. 이는 여러 가지 이유로 발생할 수 있습니다
예를 들어 인터페이스를 생성자 주입 할 수 없습니다. 또한 외부 라이브러리의 클래스와 같이 소유하지 않은 유형을 생성자 주입 할 수 없습니다. 이럴때, Hilt 모듈을 사용하여 바인딩 정보를 Hilt에 제공 할 수 있습니다.
Hilt 모듈은 @Module로 주석이 달린 클래스입니다. Dagger 모듈과 마찬가지로 Hilt에게 특정 유형의 인스턴스를 제공하는 방법을 알려줍니다. Dagger 모듈과 달리 Hilt 모듈에 @Installin으로 주석을 추가하여 각 모듈이 사용되거나 설치 될 Android 클래스를 Hilt에 알려야합니다.
Hilt 모듈에서 제공하는 종속성은 Hilt 모듈을 Installin하는 Android 클래스와 연결된 모든 생성 된 component 에서 사용할 수 있습니다.
Hilt의 코드 생성은 Hilt를 사용하는 모든 Gradle 모듈에 접근 가능해야하므로, Application 클래스를 컴파일하는 Gradle 모듈도 전이 종속성에 모든 Hilt 모듈과 생성자 삽입 클래스를 포함해야합니다.
@Binds를 사용하여 인터페이스 인스턴스 삽입
AnalyticsService 예제를 살펴보면, AnalyticsService가 인터페이스 인 경우 생성자 삽입이 불가능하다.
대신 Hilt 모듈 내에 @Binds로 주석이 달린 추상 함수를 만들어 Hilt에 바인딩 정보를 제공한다.
@Binds 주석은 인터페이스의 인스턴스를 제공해야 할 때 사용할 구현을 Hilt에 알려줍니다.
- 함수 반환 유형( :AnalyticsService)은 함수가 인스턴스를 제공하는 인터페이스를 Hilt에 알려줍니다.
- 함수 매개 변수(analyticsServiceImpl: AnalyticsServiceImpl)는 구현하여 제공할 클래스을 Hilt에 알려줍니다.
interface AnalyticsService {
fun analyticsMethods()
}
// 생성자 주입
// Hilt는 AnalyticsServiceImpl 객체 주입법을 알 필요가 있기 때문에
class AnalyticsServiceImpl @Inject constructor(
...
) : AnalyticsService { ... }
@Module // 제공 방법을 정의
@InstallIn(ActivityComponent::class) // 주입받을 클래스 Type.
abstract class AnalyticsModule {
@Binds // 인터페이스 주입시 제공방법.
abstract fun bindAnalyticsService(
analyticsServiceImpl: AnalyticsServiceImpl
): AnalyticsService
}
Hilt 모듈 AnalyticsModule은 Hilt가 해당 종속성을 ExampleActivity에 주입하기를 원하기 때문에 @InstallIn (ActivityComponent :: class)로 주석 처리됩니다. AnalyticsModule의 모든 종속성을 모든 앱 Activity에서 사용할 수 있음을 의미합니다.
@Provides로 인스턴스 삽입
인터페이스는 Type을 생성자 주입 할 수없는 유일한 경우가 아니고 또 있는데, 생성자 삽입은 외부 라이브러리 (Retrofit, OkHttpClient 또는 Room 데이터베이스와 같은 클래스)에서 가져 오기 때문에 클래스를 소유하지 않거나 인스턴스를 빌더 패턴으로 만들어야하는 경우에도 불가능합니다.
AnalyticsService 클래스를 직접 소유하지 않은 경우 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)
}
}
동일한 유형에 대해 여러 바인딩 제공
@Qualifiers는 해당 유형에 여러 바인딩이 정의되어있는 경우 특정 유형의 바인딩을 식별하는 데 사용하는 주석입니다
AnalyticsService에 대한 호출을 가로 채야하는 경우 인터셉터와 함께 OkHttpClient 객체를 사용할 수 있습니다.
다른 AnalyticsService 경우 다른 방식으로 호출을 가로 채야 할 수 있습니다. 이 경우 Hilt에게 OkHttpClient의 두 가지 다른 구현을 제공하는 방법을 알려야합니다.
1. @Qualifier 정의
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttpClient
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherInterceptorOkHttpClient
2. 각 Qualifier 마다 주입할 타입을 제공하는 방법을 알필요가 있다. 이 경우 @Provides와 함께 Hilt 모듈을 사용할 수 있습니다. 두 메서드 모두 동일한 반환 유형을 갖지만 Qualifier는 두 가지 다른 바인딩으로 메서드에 레이블을 지정합니다.
@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()
}
}
3. 필드 또는 매개 변수에 해당 Qualifier로 주석을 달아 상응하는 필요한 특정 유형을 삽입 할 수 있습니다.
// As a dependency of another class.
@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {
@Provides
fun provideAnalyticsService(
// 매개변수에 @Qualifier 붙힘
@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
}
모범 사례로, Qualifier를 Type에 추가하는 경우 해당 종속성을 제공하는 가능한 모든 방법에 한정자를 추가합니다.
한정자없이 기본 또는 공통 구현을 그대로두면 오류가 발생하기 쉬우 며 Hilt가 잘못된 종속성을 주입 할 수 있습니다
(가능하면 한정자를 사용하는 것이 좋다.)
Hilt의 사전 정의 된 한정자
Hilt는 몇 가지 미리 정의 된 한정자를 제공합니다. 예를 들어, APP이나 Activity에서 Context 클래스가 필요할 수 있으므로
Hilt는 @ApplicationContext 및 @ActivityContext 한정자를 제공합니다
예제의 AnalyticsAdapter 클래스에 Actvity의 context가 필요하다고 가정하면, 다음 코드는 AnalyticsAdapter에 Actvity의 context를 제공하는 방법을 보여줍니다
class AnalyticsAdapter @Inject constructor(
@ActivityContext private val context: Context,
private val service: AnalyticsService
) { ... }
또 다른 Hilt 사전에 정의된 바인딩을 확인하기 --> Component default bindings.
Android 클래스 용으로 생성 된 Components
필드 삽입을 할 수있는 각 Android 클래스에 대해 @InstallIn 주석에서 참조 할 수있는 관련 Hilt component가 있습니다.
각 Hilt component 는 해당하는 Android 클래스에 Bindings 을 삽입하는 역할을합니다.
* BroadcastReceiver Components가 없는것은 Hilt는 SingletonComponent에서 직접 브로드 캐스트 수신기를 주입하기 때문에 Hilt는 브로드 캐스트 수신기에 대한 구성 요소를 생성하지 않습니다.
Component 생명주기.
Hilt는 해당 Android 클래스의 수명주기에 따라 생성 된 구성 요소 클래스의 인스턴스를 자동으로 만들고 제거합니다.
ActivityRetainedComponent는 구성변경(예: 화면 방향, 키보드 가용성 및 사용자가 다중 창 모드를 활성화할 경우). 전반에 걸쳐 존재하므로 첫 번째 Activity # onCreate ()에서 생성되고 마지막 Activity # onDestroy ()에서 삭제됩니다.
Component scopes
Hilt의 모든 바인딩은 scope 가 지정되지 않습니다. 즉, 앱에서 바인딩을 요청할 때마다 Hilt는 필요한 유형의 새 인스턴스를 만듭니다.
이 예에서 Hilt는 다른 타입에 대한 종속성으로 또는 필드 삽입을 통해 AnalyticsAdapter를 제공 할 때마다 (예 : ExampleActivity에서), Hilt는 AnalyticsAdapter의 새 인스턴스를 제공합니다.
그러나 Hilt는 바인딩이 특정 component로 scope를 지정할 수도 있습니다.
Hilt는 바인딩 범위가 지정된 component의 인스턴스 단 한 번만 scope바인딩을 만들고 해당 바인딩에 대한 모든 요청은 동일한 인스턴스를 공유합니다.
@ActivityScoped를 사용하여 AnalyticsAdapter의 범위를 ActivityComponent로 지정하면 Hilt는 해당 Activtiy의 수명 동안 동일한 AnalyticsAdapter 인스턴스를 제공합니다.
@ActivityScoped
class AnalyticsAdapter @Inject constructor(
private val service: AnalyticsService
) { ... }
*** Component에 대한 바인딩 범위를 지정하는 것은 제공된 개체가 해당 구성 요소가 파괴 될 때까지 메모리에 남아 있기 때문에 비용이 많이들 수 있습니다. 애플리케이션에서 범위가 지정된 바인딩 사용을 최소화해야한다.
특정 범위 내에서 동일한 내부 상태의 인스턴스를 사용해야하는 바인딩, 동기화가 필요한 바인딩 또는 생성 비용이 많이 드는 바인딩의 경우.
에는 구성 요소 범위 바인딩을 사용하는 것이 적절합니다.
AnalyticsService에 ExampleActivity뿐만 아니라 매번 동일한 인스턴스를 사용해야하는 객체 내부 상태가 있다고 가정해보면, 앱 어디에서나. 이 경우 AnalyticsService의 범위를 SingletonComponent로 지정하는 것이 적절합니다.
그 결과 구성 요소가 AnalyticsService의 인스턴스를 제공해야 할 때마다 매번 동일한 인스턴스를 제공합니다.
Hilt 모듈의 구성 요소에 대한 바인딩 범위를 지정하는 방법을 보여줍니다.
바인딩의 Scope는 Install 되는 Component의 Scope와 일치해야합니다.
ActivityComponent 대신 SingletonComponent에 AnalyticsService를 설치해야합니다.
// AnalyticsService가 인터페이스인 경우 @Binds이용.
@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {
@Singleton
@Binds
abstract fun bindAnalyticsService(
analyticsServiceImpl: AnalyticsServiceImpl
): AnalyticsService
}
// 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 구성 요소 범위에 대한 자세한 내용은 참조 -> Scoping in Android and Hilt
Component 계층구조
components에 Module을 설치하면 해당 components 또는 components 계층 구조에서 subComponents에 있는 다른 바인딩의 종속성으로 상위 바인딩에 액세스 할 수 있습니다.
* 기본적으로 View 에서 필드 삽입을 수행하는 경우 ViewComponent는 ActivityComponent에 정의 된 바인딩을 사용할 수 있습니다.
FragmentComponent에 정의된 바인딩도 사용해야하고 View가 Fragment의 일부인 경우 @AndroidEntryPoint와 함께 @WithFragmentBindings 주석을 사용하세요.
즉 View에 필드 삽입시 상위 ActivityComponent 정의된 바인딩을 쓸수 있고,
View가 Fragment에 일부라면 @WithFragmentBindings 주석을 사용하면 된다.
Component default bindings
Hilt Component에는 Hilt가 사용자 지정 바인딩에 종속성(Qulifer를 이용한.)으로 삽입 할 수있는 default 바인딩 세트가 함께 제공됩니다.
이러한 바인딩은 특정 하위 클래스가 아니라 일반 Activity 및 Fragment Type에 해당합니다.
Hilt가 한개의 Activity Component 정의를 사용하여 모든 Activity에 주입하기 때문입니다.
각 Activity에는이 Component의 다른 객체가 있습니다.
@ApplicationContext를 사용하여 application context binding 도 사용할 수 있습니다.
class AnalyticsServiceImpl @Inject constructor(
@ApplicationContext context: Context
) : AnalyticsService { ... }
// 한정자가 없다면 application bindings을 이용한다.
class AnalyticsServiceImpl @Inject constructor(
application: Application
) : AnalyticsService { ... }
activity context binding 은 @ActivityContext를 사용하여 사용할 수도 있습니다
class AnalyticsAdapter @Inject constructor(
@ActivityContext context: Context
) { ... }
// 특별한 한정자가 없다면 Activity bindings을 사용한다.
class AnalyticsAdapter @Inject constructor(
activity: FragmentActivity
) { ... }
'DI > Hilt' 카테고리의 다른 글
Android Code Lab(Hilt) 정리3 (0) | 2021.12.23 |
---|---|
Android Code Lab(Hilt) 정리2 (0) | 2021.12.23 |
Android Code Lab(Hilt) 정리1 (0) | 2021.12.23 |