Dagger는 Java 및 Kotlin을 위한 의존성 주입 프레임워크로, 애노테이션 처리를 통해 컴파일 타임에 DI 코드를 생성합니다. Hilt는 Android용 Dagger를 래핑하여 컴포넌트 설정과 라이프사이클 관리를 단순화합니다. Google, 2025에 따르면, Hilt는 Google Play Top-100 Android 앱의 70% 이상에서 사용되며, 미리 정의된 컴포넌트를 통해 Activity, Fragment, ViewModel 및 Service를 지원합니다. 두 프레임워크 모두 컴파일 타임 의존성 그래프 검증을 제공하여 런타임 주입 오류를 제거합니다.
핵심 요점
Dagger는 컴파일 타임 코드 생성을 갖춘 의존성 주입 프레임워크입니다. Square에서 개발되어 이후 Google로 이전된 Dagger는 Java APT 애노테이션 프로세서를 사용하여 의존성 그래프를 분석하고 팩토리 클래스를 생성합니다. 런타임 DI(Guice, Koin)와 달리 Dagger는 리플렉션을 사용하지 않습니다. 모든 코드는 컴파일 타임에 생성되어 최대 런타임 성능과 빌드 타임 오류 감지를 보장합니다.
Hilt는 Dagger 위에 구축된 Google 라이브러리로, Android에 최적화되어 있습니다. Hilt는 Android 컴포넌트 라이프사이클에 해당하는 미리 정의된 컴포넌트를 제공합니다: Application용 @SingletonComponent, Activity용 @ActivityComponent, Fragment용 @FragmentComponent, ViewModel용 @ViewModelComponent. 이는 순수 Dagger에서 필요한 일상적인 Component 및 Module 구성을 제거합니다. Hilt는 또한 @AndroidEntryPoint를 통해 각 Android 컴포넌트의 의존성 그래프를 자동으로 생성합니다.
Google I/O 2024에 따르면, Hilt는 Kotlin으로 작성된 Android 애플리케이션에서 DI를 위한 권장 솔루션입니다. Jetpack 라이브러리(Navigation, Room, WorkManager)는 @HiltViewModel 및 @HiltWorker를 통해 Hilt와 내장 통합을 제공합니다. Android를 사용하지 않는 프로젝트(순수 Java/Kotlin 라이브러리, 서버 애플리케이션)에서는 Hilt 래퍼 없이 순수 Dagger가 사용됩니다.
DI 프레임워크가 없으면 개발자는 생성자나 팩토리를 통해 수동으로 객체를 생성하고 체인을 따라 의존성을 전달합니다. 새로운 요구사항이 생길 때마다 체인의 모든 생성자 시그니처를 변경해야 합니다. Dagger는 이 프로세스를 자동화합니다: 필요한 타입을 선언하기만 하면(@Inject constructor) Dagger가 중첩된 모든 타입을 해결하여 의존성 그래프를 생성합니다. 의존성이 변경되면 Dagger가 생성된 코드를 자동으로 업데이트합니다. 체인에서 실수하는 것은 불가능합니다.
의존성 주입은 객체가 의존성을 스스로 생성하지 않고 외부에서 받는 패턴입니다. DI는 제어의 역전(IoC) 원칙을 구현합니다: 클래스는 자신의 의존성을 생성할 책임이 없으며 생성자, 메서드 또는 필드를 통해 이를 선언합니다. 생성자 주입은 객체가 유효한 상태로 생성됨을 보장하기 때문에 가장 선호됩니다.
| 주입 유형 | Dagger 구문 | 사용 시기 |
|---|---|---|
| Constructor injection | @Inject constructor | 기본 방식 — 모든 사용자 정의 클래스용 |
| Field injection | @Inject lateinit var | Android 컴포넌트만 해당(Activity, Fragment) |
| Method injection | @Inject fun bind() | 생성 후 초기화용 |
DI의 주요 장점으로는 테스트 용이성(의존성을 모의 객체로 대체 가능), 느슨한 결합(클래스가 구현이 아닌 인터페이스에 의존), 스코프를 통한 객체 수명의 명시적 관리가 있습니다. Dagger는 객체가 해당 스코프 내에서 한 번 생성되고 스코프를 벗어나면 소멸됨을 자동으로 보장합니다.
Component는 Dagger 의존성 그래프의 중심 요소입니다. @Component로 애노테이션된 인터페이스로, Module과 주입 대상 간의 브리지를 설명합니다. Dagger는 컴파일 타임에 Component 구현(예: DaggerAppComponent)을 생성합니다. Component는 필요한 타입을 반환하는 추상 메서드 또는 필드 주입을 위한 객체를 받는 inject 메서드를 통해 주입에 사용 가능한 타입을 결정합니다.
// Module: Dagger가 자체적으로 생성할 수 없는 의존성을 제공
@Module
class NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.build()
}
@Provides
@Singleton
fun provideApiService(client: OkHttpClient): ApiService {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
}
// Component: Module과 Injection 대상을 연결
@Component(modules = [NetworkModule::class])
interface AppComponent {
fun inject(activity: MainActivity)
fun getApiService(): ApiService
}
@Module은 @Provides가 있는 메서드를 포함하는 클래스로, 의존성 인스턴스를 반환합니다. Module은 Dagger가 자동으로 생성할 수 없는 타입에 사용됩니다: 타사 라이브러리(OkHttp, Retrofit), 생성자 매개변수가 있는 객체, 구현 선택이 있는 인터페이스. @Binds는 메서드가 인터페이스를 반환하고 단일 구현을 허용하는 경우 @Provides의 대안입니다. Dagger는 메서드를 호출하지 않고 직접 캐스트를 생성합니다.
@Scope는 의존성 그래프에서 객체의 수명을 정의합니다. @Singleton — 객체가 전체 애플리케이션에 대해 한 번 생성됩니다. @ActivityScoped — 객체는 Activity가 살아있는 동안 유지됩니다. @FragmentScoped — Fragment가 살아있는 동안. 스코프가 없으면 Dagger는 주입할 때마다 새 인스턴스를 생성합니다. @Reusable — 싱글톤일 필요는 없지만 생성 비용이 많이 드는 객체를 위한 스코프입니다. Dagger가 인스턴스를 캐시할 수 있지만 보장하지는 않습니다.
Hilt는 미리 정의된 컴포넌트와 자동 기본 그래프 생성을 통해 Android용 Dagger 구성을 단순화합니다. Application 클래스의 @HiltAndroidApp 애노테이션은 Hilt 컴포넌트 생성을 트리거합니다. 이 애노테이션이 없으면 Hilt가 작동하지 않습니다. Hilt를 사용하는 모든 Android 애플리케이션에 필수입니다. @HiltAndroidApp은 부모 SingletonComponent를 생성하며, 애플리케이션의 다른 모든 컴포넌트가 이를 상속받습니다.
@HiltAndroidApp
class MyApplication : Application()
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject lateinit var apiService: ApiService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// apiService가 onCreate 호출 전에 이미 주입됨
}
}
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
@Provides
@Singleton
fun provideDatabase(@ApplicationContext ctx: Context): AppDatabase {
return Room.databaseBuilder(ctx, AppDatabase::class.java, "app.db").build()
}
}
@AndroidEntryPoint는 Activity, Fragment, Service, BroadcastReceiver 및 View를 위한 애노테이션입니다. 각 유형에 대해 Hilt 컴포넌트를 생성합니다: Activity의 @AndroidEntryPoint는 SingletonComponent를 상속하는 ActivityComponent를 생성합니다. 하위 컴포넌트는 자동으로 부모의 모든 의존성을 받습니다. @Inject lateinit var를 사용한 필드 주입은 @AndroidEntryPoint로 애노테이션된 클래스에서만 사용 가능합니다. 일반 클래스에서는 생성자 주입이 사용됩니다.
@InstallIn은 모듈이 설치될 Hilt 컴포넌트를 지정합니다. @InstallIn(SingletonComponent::class)가 있는 NetworkModule은 애플리케이션 전체에서 사용 가능합니다. @InstallIn(ActivityComponent::class)가 있는 Module은 Activity에서만 사용 가능합니다. 이는 의존성 그래프를 격리합니다: Activity별 모듈은 Fragment와 ViewModel에서 보이지 않아 유효하지 않은 의존성의 우발적 사용을 방지합니다. @ApplicationContext는 애플리케이션 Context를 얻기 위한 Hilt의 내장 한정자입니다.
동일한 인터페이스의 두 가지 다른 구현을 주입해야 하는 경우 한정자가 사용됩니다. Hilt는 문자열 식별자를 위한 @Named와 @Qualifier가 있는 사용자 정의 애노테이션을 지원합니다. 예를 들어, 다른 문자열 구성의 경우 @Named("baseUrl") 및 @Named("imageBaseUrl")입니다. 사용자 정의 한정자는 컴파일 타임 검사가 가능하므로 @Named보다 선호됩니다. 잘못된 문자열 이름은 런타임까지 감지되지 않습니다.
@HiltViewModel은 수동 ViewModelProvider.Factory를 대체하는 애노테이션입니다. @HiltViewModel로 애노테이션되고 @Inject constructor가 있는 클래스는 Dagger를 통해 자동으로 모든 의존성을 받습니다. Hilt는 Jetpack ViewModelProvider가 사용하는 ViewModelFactory를 생성합니다. Hilt가 없으면 개발자는 Activity 또는 프래그먼트에서 각 매개변수를 전달하여 수동으로 팩토리를 작성해야 합니다.
@HiltViewModel
class MainViewModel
@Inject constructor(
private val apiService: ApiService,
private val database: AppDatabase
) : ViewModel() {
private val _users = MutableStateFlow<List<User>>(emptyList())
val users: StateFlow<List<User>> = _users.asStateFlow()
fun loadUsers() {
viewModelScope.launch {
_users.value = apiService.getUsers()
}
}
}
// Activity에서 — Hilt가 자동으로 ViewModel 생성
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
}
ViewModelScoped는 ViewModel이 살아있는 동안 의존성이 유지되는 Hilt 스코프입니다. 동일한 유형의 두 ViewModel이 동일한 @ViewModelScoped 의존성을 주입하면 각각 고유한 인스턴스를 받습니다. 이는 @ViewModelScoped를 @ActivityScoped와 구별합니다. 후자에서는 하나의 Activity가 모든 프래그먼트에 대해 하나의 인스턴스를 받습니다. ViewModel별 의존성(예: SavedStateHandle)의 경우 @HiltViewModel과 @Inject constructor(savedStateHandle: SavedStateHandle)이 사용됩니다.
Hilt는 Hilt Extensions 라이브러리를 통해 assisted injection을 지원합니다. Assisted Injection을 사용하면 일부 의존성이 런타임에만 알려진 경우(예: 인텐트의 사용자 ID) 주입 시 생성자에 매개변수를 전달할 수 있습니다. Assisted Injection에는 @AssistedInject와 @Assisted 매개변수의 조합이 사용됩니다. Hilt는 표준 방식으로 주입할 수 있는 AssistedFactory를 생성합니다.
순수 Dagger는 Component의 수동 생성, 스코프 정의 및 각 Android 컴포넌트에 대한 주입 구성이 필요합니다. 개발자는 AppComponent, ActivityComponent, FragmentComponent를 만들고 @Subcomponent를 통해 관계를 관리합니다. 이 접근 방식은 최대 제어를 제공하지만 상당한 보일러플레이트 코드가 필요합니다. Dagger는 비표준 DI 아키텍처가 필요한 대규모 프로젝트나 비Android Java/Kotlin 프로젝트에서 사용됩니다.
Hilt는 보일러플레이트를 자동화합니다: 하나의 @HiltAndroidApp, 각 컴포넌트에 하나의 @AndroidEntryPoint, 미리 정의된 스코프. Google은 모든 새로운 Android 프로젝트에 Hilt를 권장합니다. Dagger에서 Hilt로의 마이그레이션에는 Component를 @InstallIn으로 대체, @Subcomponent를 미리 정의된 Hilt 컴포넌트로 대체, 수동 ViewModelProvider.Factory를 @HiltViewModel로 대체하는 것이 포함됩니다. 대부분의 @Module 클래스는 @Provides 메서드를 변경하지 않고 @InstallIn을 추가하여 마이그레이션됩니다.
| 특성 | Dagger | Hilt |
|---|---|---|
| 설정 | 수동: Component, Subcomponent, Builder | 자동: @HiltAndroidApp, @AndroidEntryPoint |
| Android 컴포넌트 | 미리 정의 없음 | 12개 이상의 내장 컴포넌트 |
| ViewModel | 수동 팩토리 | @HiltViewModel + @Inject constructor |
| 멀티모듈 | @Component(dependencies)를 통해 | @InstallIn + 집계를 통해 |
| 복잡성 | 높음 — 경험 필요 | 낮음 — 직관적 이해 가능 |
| 유연성 | 최대 | 표준(95% 시나리오 커버) |
Hilt의 제한 사항: 라이브러리는 Android만 지원(순수 서버사이드 Java 프로젝트에는 부적합), 특정 컴포넌트 구조를 강제(재정의 어려움), Jetpack Compose 프로젝트의 경우 android.hilt:hilt-navigation-compose에 대한 의존성 추가. Compose 애플리케이션의 경우 Hilt는 Composable에서 hiltViewModel()을 통해 액세스 가능한 @HiltViewModel을 제공합니다. Activity에서 수동 ViewModel 제공이 필요 없습니다.
자주 묻는 질문
Dagger는 Component와 Module을 수동으로 구성하는 기본 컴파일 타임 DI 프레임워크입니다. Hilt는 컴포넌트 생성과 Activity, Fragment, ViewModel, Service 및 BroadcastReceiver의 라이프사이클 통합을 자동화하는 Android 래퍼입니다.
@HiltAndroidApp은 Application의 Hilt 컴포넌트 생성을 활성화합니다. 이 애노테이션이 없으면 Hilt는 모든 ActivityComponent, FragmentComponent 및 ViewModelComponent가 상속하는 기본 SingletonComponent를 만들 수 없습니다. 이 애노테이션은 모든 Hilt 프로젝트에 필수입니다.
Hilt Navigation은 NavBackStackEntry의 ViewModel에 @HiltViewModel을 제공하고 네비게이션 그래프 내에서 ViewModel을 스코프하는 hiltNavGraphViewModels()을 제공합니다. android.hilt:hilt-navigation-fragment 라이브러리는 각 NavBackStackEntry에 대해 자동으로 ViewModel을 생성합니다.
애플리케이션 컨텍스트에는 @ApplicationContext를, Activity 컨텍스트에는 @ActivityContext를 사용하세요. Hilt는 android.hilt:hilt-android 라이브러리에 이러한 한정자를 내장하여 제공합니다. @ActivityContext는 ActivityComponent에 설치된 모듈에서만 사용 가능합니다.
@Binds는 메서드가 정확히 하나의 매개변수를 받고 해당 타입을 인터페이스로 반환하는 경우 @Provides의 효율적인 대안입니다. @Binds는 메서드를 호출하지 않고 직접 캐스트를 생성하여 생성되는 코드 양을 줄이고 주입 성능을 향상시킵니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.