Hilt 사용 이유 → 2025.08.12 - [안드로이드] - [Android] Hilt- Hilt를 사용하는 이유 (1)
이제 본격적으로 Android에서의 프로젝트 세팅과 Hilt의 기본 흐름에 대해 알아보겠다.
프로젝트 세팅
프로젝트 세팅을 위해서는 build.gradle에 먼저 의존성을 추가해야 한다.
// Project level
plugin {
id("com.google.devtools.ksp") version "2.1.21-2.0.1" apply false
id("com.google.dagger.hilt.android") version "2.56.2" apply false
}
// Module level
plugins {
id("com.google.devtools.ksp")
id("com.google.dagger.hilt.android")
}
dependencies {
ksp("com.google.dagger:hilt-android-compiler:2.56.2")
implementation("com.google.dagger:hilt-android:2.56.2")
}
안정화된 최신 버전은 Android Developer 페이지와 ksp 깃허브 를 참고하면 된다.
Hilt 기본 흐름 이해
@HiltAndroidApp
@HiltAndroidApp 어노테이션은 Application 클래스에 지정되는 어노테이션으로,
Hilt를 사용하는 모든 앱이라면 해당 클래스가 반드시 포함되어야 한다!
여기서 중요한 개념은 "각 어노테이션이 지정한 스코프(scope)에 따라 의존성 객체의 생명주기가 달라진다"는 것이다.
따라서 @HiltAndroidApp 어노테이션이 지정된 Application 객체가 생성되면
Hilt가 DI 컨테이너를 자동으로 생성하며, 이는 앱이 실행되는 동안 유지된다.
예시는 다음과 같다.
@HiltAndroidApp
class MainApplication : Application() { ... }
@AndroidEntryPoint
@AndroidEntryPoint는 Application 객체 생성과 함께 DI 컨테이너가 초기화된 이후,
다음과 같은 Android 클래스에 의존성 주입을 가능하게 하기 위한 어노테이션이다.
- Activity
- Fragment
- View
- Service
- BroadcastReceiver
Fragment에 해당 어노테이션을 지정하였다면, 상위 Activity에도 똑같이 어노테이션을 지정해야 한다.
의존성을 주입하고자 하는 Activity에 어노테이션이 지정된 코드이다.
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { ... }
}
Hilt Bindings
다른 클래스의 인스턴스를 이용하기 위해 필드 삽입 방식으로 의존성을 주입하려면, 해당 필드에 @Inject 어노테이션이 필요하다.
다음 예제를 살펴보자.
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@Inject lateinit var logger: LoggerLocalDataSource
@Inject lateinit var dateFormatter: DateFormatter
override fun onCreate(savedInstanceState: Bundle?) { ... }
}
@Inject 어노테이션이 지정된 logger와 dateFormatter은 객체의 인스턴스를 직접 생성하지 않아도,
Hilt가 자동으로 인스턴스를 생성하여 의존성을 주입한다.
Hilt는 어떻게 LoggerLocalDataSource와 DateFormatter 클래스의 인스턴스를 자동으로 생성하여 제공할까?
생성자 삽입(Constructor Injection)
Hilt가 클래스의 인스턴스를 제공하는 방식 중 하나는 "생성자 삽입" 방식이다.
위 예제에서 인스턴스가 필요한 클래스는 LoggerLocalDataSource와 DateFormatter 클래스이다.
따라서 두 클래스의 생성자에 @Inject 어노테이션을 지정하면, Hilt가 해당 클래스를 직접 생성하게 된다.
class LoggerLocalDataSource @Inject constructor() { ... }
class DateFormatter @Inject constructor(
private val timeProvider: TimeProvider
) { ... }
class TimeProvider @Inject constructor() { ... }
추가로 다른 의존성이 필요하지 않은 LoggerLocalDataSource 클래스는 생성자의 인수없이 어노테이션을 지정하면 된다.
DataFormatter 클래스는 TimeProvider라는 의존성이 추가로 필요하기 때문에,
@Inject 어노테이션을 지정함과 동시에 생성자에 TimeProvider를 받는다.
아울러 TimeProvider 클래스에도 @Inject 어노테이션을 지정해주어야 한다.
Hilt Module
만약, 생성자의 인수가 인터페이스 또는 Retrofit과 같은 외부 라이브러리 클래스라면 Hilt는 생성자 내의 클래스를 인식할 수 있을까?
기존과 동일하게 @Inject 어노테이션을 지정하면 될까?
질문에 대한 답은 ❌ 이다.
인터페이스와 외부 라이브러리 클래스에 기존과 같이 생성자 삽입 방식으로는 의존성을 주입하지 못한다.
의존성을 주입하기 위해서는 "Hilt Module"을 사용해야 한다.
Hilt Module은 @Module 어노테이션이 지정된 클래스로,
@Module 어노테이션은 특정 타입의 인스턴스를 제공하는 방법을 Hilt에 알려주는 역할을 한다.
이쯤되면 Hilt가 인식하는 트리거가 "어노테이션"임을 알아챌 수 있을 것이다.
이제 인터페이스의 의존성을 제공해주는 어노테이션에 대해 알아보자.
@Binds
먼저 Hilt 모듈을 살펴보자.
interface AnalyticsService {
fun analyticsMethods()
}
class AnalyticsServiceImpl @Inject constructor(
...
) : AnalyticsService { ... }
@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {
@Binds
abstract fun bindAnalyticsService(
analyticsServiceImpl: AnalyticsServiceImpl
): AnalyticsService
}
@Module이 지정된 AnalyticsModule은 Hilt 모듈이다.
어느 클래스에서 AnalyticsService라는 의존성이 필요하다면 Hilt는 AnalyticsService 타입을 인식해야 한다.
이때, Hilt는 @Module 내에 @Binds 어노테이션을 가지며,
return 타입이 AnalyticsService인 bindAnalyticsService()를 통해 해당 타입과 구현체를 연결(bind)하여 의존성을 제공한다.
❓ @InstallIn 어노테이션은 뭔가요?
⇒ Hilt 모듈을 어느 컴포넌트에 설치할 것인지 정의하는 어노테이션이다.
만약 어노테이션에 ActivityComponent::class 를 설정한다면 Activity의 생명주기 동안 의존성 주입을 관리하게 된다.
ActivityComponent::class 말고도 다양한 생명주기를 가지는 Hilt 컴포넌트가 존재한다.
| 컴포넌트 | 의존성 주입 대상 |
| SingletonComponent | Application |
| ActivityRetainedComponent | N/A |
| ViewModelComponent | ViewModel |
| ActivityComponent | Activity |
| FragmentComponent | Fragment |
| ViewComponent | View |
| ViewWithFragmentComponent | @WithFragmentBindings 주석이 지정된 View |
| ServiceComponent | Service |
@Provides
외부 라이브러리에서 제공되는 클래스의 인스턴스 또는 빌더 패턴의 인스턴스를 생성해주는 어노테이션은 @Provides 이다.
@Provides는 Hilt 모듈 내에 선언되며, Hilt 모듈의 형태는 주로 object이다.
모듈에 생성자 또는 state가 필요한 경우 Hilt 모듈의 형태는 클래스 + companion object이다.
@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {
@Provides
fun provideAnalyticsService( ... ): AnalyticsService {
return Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(AnalyticsService::class.java)
}
}
provideAnalyticsService()의 return 타입인 AnalyticsService 인스턴스는 Activity 생명주기 동안 재사용할 수 있으며,
추가 의존성이 필요할 경우@Provides 함수의 매개변수로 지정하면 Hilt가 자동으로 주입한다.
Qualifier
만약 모듈에서 제공하는 여러 의존성의 반환 타입이 동일하다면 Hilt는 어느 의존성을 택해야 할까?
이 때 사용하는 것이 바로 "Qualifier(한정자)"이다.
하나의 모듈에서 OkHttpClient 타입의 인스턴스를 반환하는 의존성 제공 함수가 2개 있다고 가정해보자.
두 함수에는 정의한 한정자 이름을 어노테이션 형태로 부여해야 하며,
한정자 정의 방식은 다음과 같다.
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttpClient
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherInterceptorOkHttpClient
다음은 Hilt 모듈에 정의한 어노테이션을 부여하는 예시이다.
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@AuthInterceptorOkHttpClient
@Provides
fun provideAuthInterceptorOkHttpClient(
authInterceptor: AuthInterceptor
): OkHttpClient {
return ...
}
@OtherInterceptorOkHttpClient
@Provides
fun provideOtherInterceptorOkHttpClient(
otherInterceptor: OtherInterceptor
): OkHttpClient {
return ...
}
}
정의한 어노테이션은 의존성 제공 함수 위에 부여할 수 있다.
추가로 어노테이션을 사용할 수 있는 경우는 다음과 같다.
- 의존성 제공 함수의 매개변수에 상위 범위의 의존성을 가져다 사용할 경우
- 클래스에서 생성자 주입을 할 경우
- 클래스에서 필드 주입을 할 경우
Hilt에서 사전 정의된 한정자는 @ApplicationContext와 @ActivityContext이다.
어떠한 Activity 클래스에서 context가 필요할 때, @ActivityContext를 통해 context 객체를 제공받아 사용할 수 있다.
@EntryPoint
Hilt를 지원하지 않는 클래스에 의존성을 삽입하려면 @EntryPoint 어노테이션이 필요하다.
이 경우 해당 클래스에는 @AndroidEntryPoint 와 같은 어노테이션을 지정할 수 없기 때문에,
클래스 내에 별도의 진입점을 만들어 의존성을 주입받아야 한다.
대표적으로 Hilt는 ContentProvider를 지원하지 않는다.
예시를 살펴보자.
class LogsContentProvider: ContentProvider() {
@InstallIn(ApplicationComponent::class)
@EntryPoint
interface LogsContentProviderEntryPoint {
fun logDao(): LogDao
}
private fun getLogDao(appContext: Context): LogDao {
val hiltEntryPoint = EntryPointAccessors.fromApplication(
appContext,
LogsContentProviderEntryPoint::class.java
)
return hiltEntryPoint.logDao()
}
}
클래스인 LogsContentProvider 클래스 내에 LogsContentProviderEntryPoint 인터페이스를 선언하고,
@EntryPoint 어노테이션과 함께 @InstallIn을 사용해 진입점이 속할 컴포넌트와 범위를 명시한다.
의존성을 주입하여 사용할 때는
- @InstallIn에 명시된 클래스(LogsContentProviderEntryPoint)가
EntryPointAccessors.fromApplication()의 매개변수로 전달되는 컴포넌트와 일치해야 한다. - 만약 ApplicationComponent가 아닌 ActivityComponent라면,
매개변수로 ApplicationContext 대신 ActivityContext를 전달해야 한다.
Hilt를 위한 프로젝트 세팅과,
다양한 어노테이션을 통해 Hilt가 의존성을 생성하고 관리하는 방식을 알아볼 수 있었다.
지금까지의 개념들은 매우 중요하지만.. 아직 끝나지 않았다! 🤣
다음은 Component와 Scope를 통해 의존성의 생명주기와 계층 구조를 관리하는 방법을 알아보겠다.
:)
https://developer.android.com/training/dependency-injection/hilt-android?hl=ko#kts
https://developer.android.com/codelabs/android-hilt?hl=ko#10
'Android' 카테고리의 다른 글
| [Android] Hilt- Component 계층 구조, Scope (3) (0) | 2025.08.15 |
|---|---|
| [Android] Hilt- Hilt를 사용하는 이유 (1) (1) | 2025.08.12 |
| [Android] 클린 아키텍처(Clean Architecture) (2) | 2025.08.11 |
| [Android] Work Manager(+Job Scheduler) (2) | 2025.08.09 |
| [Android] 안드로이드 권장 아키텍처 (4) | 2025.07.31 |