hilt는 의존성 주입 라이브러리 중에 하나이다.
의존성 주입 라이브러리에는 잘 알려진 것으로 Dagger, Hilt, koin이 있다.
Dagger, Hilt, koin를 비교해 보자면 다음과 같다.
Dagger | Hilt | koin | |
난이도(러닝 커브) | 상 | 중 | 하(복잡한 설정이나 어노테이션 없이 사용 가능) |
에러 발견 시점 | 컴파일 시 | 컴파일 시 | 런타임 시 |
호환 | 자바, 코틀린 | 자바, 코틀린 | 코틀린 |
개발자가 직접 컴포넌트를 생성해야 함 > 보일러 플레이트가 많아짐 | 표준화된 컴포넌트, 스코프 제공 |
왜 의존성 주입 라이브러리를 사용해야 하는가?
- 코드의 가독성과 재사용성
- 클래스간 결합도를 느슨하게 함으로써 테스트 용이
Hilt 사용법
힐트 라이브러리를 buil.gradle에 설정해줍니다.
다음으로 Application에 @HiltAndroidApp 어노테이션을 적어줘야 힐트를 사용할 수 있습니다.
Dagger에서는 개발자가 직접 컴포넌트를 생성해서 Application에 종속성을 부여해주지만 이 부분을 Hit에서는 바이트 변환을 통해 @HiltAndroidApp 어노테이션을 간단하게 붙이는 것만으로 종속성을 부여해줍니다. Application에 붙여줌으로써 Application의 수명주기와 종속성을 부여받습니다.
위 과정을 해주었으면 힐트를 사용할 준비가 되었습니다.이어서 Hilt에서 사용되는 어노테이션에 대한 설명을 적도록 하겠습니다.
@AndroidEntryPoint이 어노테이션은 안드로이드 클래스인 Activity, Fragment, View, Service, BroadCastReceiver에서만 사용이 가능하며 다른 클래스는 @EntryPoint라는 어노테이션을 사용합니다. @AndroidEntryPoint를 사용할 경우 위에서 @HiltAndroidApp 설정한 Application에 대한 종속성을 부여받고 접근할 수 있습니다.
@Inject
@Inject는 생성자 주입과 필드 주입 방법이 있습니다. 생성자 주입 방법은 classMainActivity @Inject constructor ( val userRepository : UserRepository) < 이런 식으로 사용합니다. 필드 주입 방법은 hilt가 onCreat() 시점에 주입되기 때문에 변수명 앞에 lateinit var 를 붙여 @Inject lateinit var 변수명처럼 적어줍니다.
@Module
모듈이란 생성자나 메소드에 추상적인 것을 주입하려고 할 때 객체로 만들어 주입하기 위해 필요한 어노테이션입니다.
@Module 어노테이션은 항상 @InstallIn 어노테이션과 함께 써 줘야 합니다. @InstallIn(SingletoneComponent::class)처럼
괄호 안에는 해당 모듈을 설치할 컴포넌트를 적어줍니다.
@Provides 와 @Binds
둘의 큰 차이는 프로젝트가 소유하지 않은 외부 라이브러리의 클래스일 경우 @Provides 어노테이션을 사용하고 그 외에는 @Binds를 사용합니다.
사용법
1. 종속 항목 추가
build.gradle에 다음과 같이 추가 후 sync now를 해줍니다.
참고: Hilt와 데이터 결합을 모두 사용하는 프로젝트에는 Android 스튜디오 4.0 이상이 필요합니다.
2. Hilt 애플리케이션 클래스
Hilt를 사용하는 모든 앱은 @HiltAndroidApp으로 주석이 지정된 Application 클래스를 포함해야 합니다. (반드시필요!)
* 앱의 수명 주기에 연결된 컨테이너를 추가하기 위함
@HiltAndroidApp은 애플리케이션 수준 종속 항목 컨테이너 역할을 하는 애플리케이션의 기본 클래스를 비롯하여 Hilt의 코드 생성을 트리거합니다. 이 생성된 Hilt 구성 요소는 Application개체의 수명 주기에 연결되고 종속성을 제공
*트리거란? 데이터베이스에 어떠한 일이 일어나면 자동으로 실행되는 개체를 의미합니다.
3. Android 클래스에 종속 항목 삽입
Application 클래스에 Hilt를 설정하고 애플리케이션 수준 구성요소를 사용할 수 있게 되면 Hilt는 @AndroidEntryPoint 주석이 있는 다른 Android 클래스에 종속 항목을 제공할 수 있습니다.
@AndroidEntryPoint 주석을 달면 Android 클래스 수명 주기를 따르는 종속 항목 컨테이너가 생성됩니다.
Hilt는 현재 다음 Android 클래스를 지원합니다.
- Application(@HiltAndroidApp을 사용)
- ViewModel(@HiltViewModel을 사용)
- Activity
- Fragment
- View
- Service
- BroadcastReceiver
Android 클래스에 @AndroidEntryPoint로 주석을 지정하면 이 클래스에 종속된 Android 클래스에도 주석을 지정해야 합니다. 예를 들어 fragment에 주석을 지정하면 이 fragment를 사용하는 activity에도 주석을 지정해야 합니다.
4. Hilt 결합 정의
다음과 같이 클래스의 생성자에서 @Inject 주석을 사용하여 클래스의 인스턴스를 제공하는 방법을 Hilt에 알려줍니다.
유형의 인스턴스 제공 방법을 Hilt에 알리려면 삽입하려는 클래스의 생성자에 @Inject 주석을 추가하세요.
5. Hilt 모듈
Hilt 모듈은 @Module과 @InstallIn 주석이 달린 클래스
@Module은 Hilt에 모듈임을 알려 주고 @InstallIn은 어느 컨테이너에서 Hilt 구성요소를 지정하여 결합을 사용할 수 있는지 Hilt에 알려 줍니다.
Hilt 모듈은 인터페이스를 삽입할 수 없을 때,
외부 라이브러리의 클래스를 소유하지 않아서 생성자를 삽입할 수 없을 때,
사용하여 Hilt에 결합 정보를 제공할 수 있습니다.
6. 인스턴스 삽입
인스턴스 삽입 방법에는 @Binds와 @Provides가 있습니다.
제가 생각했을 때 어떤 것을 사용할지는 '클래스를 소유하고 있냐, 없냐' 에 따라 사용한다고 생각합니다.
클래스를 가지고 있는 인터페이스의 인스턴스를 제공할 경우 @Binds를 사용합니다.
클래스가 외부 라이브러리에서 제공되기 때문에 클래스를 소유하지 않은 경우(Retrofit, OkHttpClient 또는 Room 데이터베이스와 같은 클래스) 또는 빌더 패턴으로 인스턴스를 생성해야 하는 경우에는 @Provides를 사용합니다.
7. 인스턴스 범위를 컨테이너로 지정
주석을 사용하여 인스턴스의 범위를 컨테이너로 지정할 수 있습니다. Hilt는 수명 주기가 다른 여러 컨테이너를 생성할 수 있으므로 이러한 컨테이너로 범위가 지정된 다양한 주석이 있습니다. 인스턴스 범위를 애플리케이션 컨테이너로 지정하는 주석은 @Singleton입니다. 이 주석을 사용하면 유형이 다른 유형의 종속 항목으로 사용되는지 또는 삽입된 필드여야 하는지와 관계없이 애플리케이션 컨테이너에서 항상 같은 인스턴스를 제공합니다.
* Hilt는 @Inject를 요청할 때마다 새 인스턴스를 생성합니다. 동일한 인스턴스를 원할 경우는 Component 별로 정의된 Scope을 지정해 줘야 합니다.
===================== 위 내용 까지가 실제 개인 프로젝트에 적용한 어노테이션입니다 ======================
======================== 하단 내용부터는 공식 문서 예제 그대로 추가하였습니다. =======================
동일한 유형에 대해 여러 결합 제공
종속 항목과 동일한 유형의 다양한 구현을 제공하는 Hilt가 필요한 경우에는 Hilt에 여러 결합을 제공해야 합니다. 한정자를 사용하여 동일한 유형에 대해 여러 결합을 정의할 수 있습니다.
한정자는 특정 유형에 대해 여러 결합이 정의되어 있을 때 그 유형의 특정 결합을 식별하는 데 사용하는 주석입니다.
다음 예를 생각해 보세요. AnalyticsService 호출을 가로채야 한다면 인터셉터와 함께 OkHttpClient 객체를 사용할 수 있습니다. 다른 서비스에서는 호출을 다른 방식으로 가로채야 할 수도 있습니다. 이 경우에는 서로 다른 두 가지 OkHttpClient 구현을 제공하는 방법을 Hilt에 알려야 합니다.
먼저 다음과 같이 @Binds 또는 @Provides 메서드에 주석을 지정하는 데 사용할 한정자를 정의합니다.
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttpClient
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherInterceptorOkHttpClient
그런 다음, Hilt는 각 한정자와 일치하는 유형의 인스턴스를 제공하는 방법을 알아야 합니다. 이 경우에는 @Provides와 함께 Hilt 모듈을 사용할 수 있습니다. 두 메서드 모두 동일한 반환 유형을 갖지만 한정자는 다음과 같이 두 가지의 서로 다른 결합으로 메서드에 라벨을 지정합니다.
@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가 잘못된 종속 항목을 삽입할 수 있습니다.
Hilt의 사전 정의된 한정자
Hilt는 몇 가지 사전 정의된 한정자를 제공합니다. 예를 들어 애플리케이션 또는 활동의 Context 클래스가 필요할 수 있으므로 Hilt는 @ApplicationContext 및 @ActivityContext 한정자를 제공합니다.
예의 AnalyticsAdapter 클래스에 활동 컨텍스트가 필요하다고 가정해 보겠습니다. 다음 코드는 AnalyticsAdapter에 활동 컨텍스트를 제공하는 방법을 보여줍니다.
class AnalyticsAdapter @Inject constructor(
@ActivityContext private val context: Context,
private val service: AnalyticsService
) { ... }
Android 클래스용으로 생성된 구성요소
필드 삽입을 실행할 수 있는 각 Android 클래스마다 @InstallIn 주석에 참조할 수 있는 관련 Hilt 구성요소가 있습니다. 각 Hilt 구성요소는 해당 Android 클래스에 결합을 삽입해야 합니다.
이전 예에서는 Hilt 모듈에서 ActivityComponent를 사용하는 방법을 보여주었습니다.
Hilt는 다음 구성요소를 제공합니다.
Hilt 구성요소인젝터 대상
Hilt 구성요소 | 인젝터 대상 |
SingletonComponent | Application |
ActivityRetainedComponent | N/A |
ViewModelComponent | ViewModel |
ActivityComponent | Activity |
FragmentComponent | Fragment |
ViewComponent | View |
ViewWithFragmentComponent | @WithFragmentBindings 주석이 지정된 View |
ServiceComponent | Service |
구성요소 전체 기간
Hilt는 해당 Android 클래스의 수명 주기에 따라 생성된 구성요소 클래스의 인스턴스를 자동으로 만들고 제거합니다.
생성된 구성요소 | 생성 위치 | 소멸 위치 |
SingletonComponent | Application#onCreate() | Application 소멸됨 |
ActivityRetainedComponent | Activity#onCreate() | Activity#onDestroy() |
ViewModelComponent | ViewModel 생성됨 | ViewModel 소멸됨 |
ActivityComponent | Activity#onCreate() | Activity#onDestroy() |
FragmentComponent | Fragment#onAttach() | Fragment#onDestroy() |
ViewComponent | View#super() | View 소멸됨 |
ViewWithFragmentComponent | View#super() | View 소멸됨 |
ServiceComponent | Service#onCreate() | Service#onDestroy() |
구성요소 범위
Hilt는 구성요소로 범위 지정도 가능합니다.
Android 클래스 | 생성된 구성요소 | 범위 |
Application | SingletonComponent | @Singleton |
Activity | ActivityRetainedComponent | @ActivityRetainedScoped |
ViewModel | ViewModelComponent | @ViewModelScoped |
Activity | ActivityComponent | @ActivityScoped |
Fragment | FragmentComponent | @FragmentScoped |
View | ViewComponent | @ViewScoped |
@WithFragmentBindings 주석이 지정된 View | ViewWithFragmentComponent | @ViewScoped |
Service | ServiceComponent | @ServiceScoped |
참고: 공식 문서- https://developer.android.com/training/dependency-injection/hilt-android?hl=ko#kotlin
'android' 카테고리의 다른 글
[Kotlin] Geckoview tutorial (0) | 2022.12.19 |
---|---|
디자인 패턴 MVC, MVP, MVVM (0) | 2022.11.10 |
startActivityForResult() deprecated (0) | 2022.10.06 |
NestedScrollView 중첩 스크롤뷰 (0) | 2022.09.23 |
클린 아키텍처란? SOLID 원칙 (1) | 2022.08.16 |