이전글
[Android] Dagger Hilt 집중탐구 - 1
지난글에 이어서 이번엔 Hilt를 적용해보고 Hilt에 대한 개념을 좀 더 자세히 알아보려합니다!
Hilt를 쓸 준비
플러그인 (프로젝트 루트)
plugins {
...
id("com.google.dagger.hilt.android") version "2.44" apply false
}
build.gradle (app 수준)
plugins {
kotlin("kapt")
id("com.google.dagger.hilt.android")
}
android {
...
}
dependencies {
implementation("com.google.dagger:hilt-android:2.44")
kapt("com.google.dagger:hilt-android-compiler:2.44")
}
// Allow references to generated code
kapt {
correctErrorTypes = true
}
참고: Hilt는 자바 8기능을 사용합니다!!
android {
...
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
Hilt를 접하신분들은 정말 많이 보셨을 그림입니다!
먼저 이 계층구조를 파악한 뒤에 이어나가겠습니다
구성요소에 모듈을 설치하면 구성요소의 다른 결합 또는 구성요소 계층 구조에서 그 아래에 있는 하위 구성요소의 다른 결합의 종속항목으로 설치된 모듈의 결합에 엑세스 할 수 있습니다.
즉 그림의 흐름대로 위에서 아래로 접근이 가능하다는 것이죠!
그림에 적혀있는 주석에 대해 좀 더 자세히 알아볼까요?
여기서 구성요소란 무엇일까요? 구성요소는 종속항목(의존성)을 주입하는데 사용되는 부분을 말합니다 예를 들어 앱의 화면을 구성하는 활동(activity)나 프래그먼트가 구성요소가 될 수 있습니다!
즉, Hilt는 앱의 화면을 구성하는 각각의 활동이나 프래그먼트에 종속항목을 주입 할 때, 그 화면의 특성에 맞게 설정을 조절해 사용합니다
Hilt를 사용하는 모든 앱은 @HiltAndroidApp으로 주석이 지정된 Application 클래스를 포함해야합니다.
@HiltAndroidApp은 애플리케이션 수준 종속 항목 컨테이너 역할을 하는 애플리케이션의 기본 클래스를 비롯해 Hilt의 코드생성을 트리거합니다
즉 Hilt의 시작을 알리는 코드라고 이해하면 될 것 같습니다.
@HiltAndroidApp
class ExampleApplication : Application() { ... }
생성된 이 구성요소는 애플리케이션 객체의 수명주기에 연결되어 이와 관련한 종속 항목을 제공합니다. 또한 이는 앱의 상위 구성요소이므로 다른 구성요소는 이 상위 구성요소에서 제공하는 종속 항목에 엑세스 할 수 없습니다.
Application 클래스에 Hilt를 설정하고 애플리케이션 수준 구성요소를 사용할 수 있게되면 Hilt는 @AndroidEntryPoint 주석이 있는 다른 Android 클래스에 종속 항목을 제공할 수 있습니다.
@AndroidEntryPoint
class A : AppCompatActivity() { ... }
Hilt는 현재 Application, ViewModel(@HiltViewModel을 사용), Activity, Fragment, View, Service, BroadcastReceiver를 지원합니다.
만약 Android 클래스에 @AndroidEntryPoint로 주석을 지정하면 이 클래스에 종속된 Android 클래스에도 주석을 지정해야 합니다. (주석을 단 상위클래스를 주입받는 하위클래스도 주석을 달아줘야한다는 의미입니다!)
@AndroidEntryPoint
class B : AppCompatActivity() {
@Inject lateinit var analytics: AnalyticsAdapter
...
}
만약 클래스 A에 analyics라는 종속항목을 가져온다면 @Inject 주석을 사용해 필드 삽입을 실행합니다. Hilt가 이 객체를 효율적으로 관리합니다. 정말 편하죠?
만약 B라는 클래스가 최초로 주입을 받고, 이후 C라는 클래스가 동일한 객체를 주입받는다면 이미 생성된 객체를 반환해줍니다. 자원관리 측면에서도 장점이 느껴지시나요?
위 내용처럼 필드 삽입을 실행하려면 Hilt가 해당 구성요소에서 필요한 종속 항목의 인스턴스를 제공하는 방법을 알아야합니다.
결합에는 특정유형의 인스턴스를 종속항목으로 제공하는 데 필요한 정보가 포함됩니다
Hilt에게 결합정보를 알려주는 방법으론 생성자 삽입입니다.
class BClass @Inject constructor(
private val service: AClass
) { ... }
이런식으로 지정된 클래스 생성자의 매개변수는 그 클래스의 종속 항목입니다.
이 코드의 경우 B 클래스에는 A 클래스가 종속항목으로 되어있습니다.
따라서 Hilt는 A 클래스의 인스턴스를 제공하는 방법도 알아야합니다
때로는 생성자 삽입을 할 수 없는 상황도 있을겁니다
예를들어 인터페이스는 생성자 삽입을 할 수 없고 외부라이브러리 클래스와 같이 소유하지 않은 것들도 생성자 삽입을 할 수 없습니다
이럴땐 Hilt 모듈을 사용해 Hilt에 결합정보를 제공해줄수 있습니다
Hilt 모듈은 @Module로 주석이 지정된 클래스입니다.
이 모듈은 특정 유형의 인스턴스를 제공하는 방법을 Hilt에게 알려줍니다.
또한 @InstallIn 주석을 지정해 각 모듈을 사용하거나 설치할 안드로이드 클래스를 Hilt에게 알려야합니다
@Bind 주석은 인터페이스의 인스턴스를 제공해야 할 때 사용할 구현을 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
}
@InstallIn(ActivityComponent::class) 주석의미는 AnalyticsModule의 모든 종속항목을 앱의 모든 activity에서 사용할 수 있음을 의미합니다
만약 외부라이브러리에서 제공되어 개발자가 소유하지 않은 경우는 어떡할까요?
HTTP 통신에 가장 흔히 쓰이는 Retrofit의 경우는 어떻게 해야할까요?
바로 @Provides 주석을 이용해 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를 다시 처음부터 공식문서를 통해 천천히 배우고나니 왜 사용해야하는지, 또 제가 적용한 프로젝트에 Hilt를 적절히 쓴게 맞는지 깊게 생각해볼 수 있었던 시간을 가진 것 같습니다
참고문헌