의존성 주입 — Hilt·Koin
ViewModel은 Repository가 필요하고, Repository는 Api와 Dao가 필요하다. 이 의존성 그래프를 각자 new로 직접 만들면 — 강하게 결합되고, 테스트에서 가짜로 바꿀 수 없고, 객체 생성 코드가 사방에 흩어진다. **의존성 주입(DI)**은 필요한 객체를 밖에서 만들어 넣어줘 이 문제를 푼다. 안드로이드 공식 권장이 Hilt(Dagger 기반)이고, 경량 대안이 Koin이다. (출처: Android — Dependency injection / Hilt.)
DI가 푸는 문제 — 결합도와 테스트
// ❌ 직접 생성 — 강결합, 테스트에서 api를 가짜로 못 바꿈
class MemberViewModel {
private val repo = MemberRepositoryImpl(RetrofitApi(), RoomDao()) // 박혀버림
}
// ✅ 주입 — 의존성을 생성자로 받음 → 테스트에서 가짜 주입 가능
class MemberViewModel(private val repo: MemberRepository)
*"의존성을 밖에서 받는다"*는 한 가지 전환이 — 결합도를 낮추고(인터페이스로 받음), 테스트에서 가짜(fake/mock)로 교체하게 하며, 객체 수명·재사용을 중앙에서 관리하게 한다.
Hilt — Dagger 기반 컴파일 타임 DI
Hilt는 Dagger를 안드로이드용으로 감싼 공식 DI다. 컴파일 시점에 의존성 그래프를 생성·검증해 런타임 오버헤드가 없고 그래프 오류를 빌드 때 잡는다.
@HiltAndroidApp // Application에 — 그래프 진입점
class MyApp : Application()
@Module @InstallIn(SingletonComponent::class)
object DataModule {
@Provides @Singleton // 어떻게 만드는지 알려줌
fun provideApi(): MemberApi = Retrofit.Builder()...create()
@Provides @Singleton
fun provideRepo(api: MemberApi, dao: MemberDao): MemberRepository =
MemberRepositoryImpl(api, dao) // 인자(api·dao)도 그래프에서 주입됨
}
@HiltViewModel // ViewModel 주입
class MemberViewModel @Inject constructor(
private val repo: MemberRepository // 자동 주입
) : ViewModel()
@AndroidEntryPoint // Activity/Fragment 주입 진입점
class MainActivity : ComponentActivity()
핵심 어노테이션 — @HiltAndroidApp(진입점)·@AndroidEntryPoint(Activity/Fragment)·@HiltViewModel·@Inject constructor(생성자 주입)·@Module+@Provides(만드는 법)·@Binds(인터페이스↔구현 연결).
스코프 — 객체의 수명
Hilt는 컴포넌트별 스코프로 객체 수명을 정한다.
| 스코프 | 수명 |
|---|---|
@Singleton | 앱 전체 (하나) |
@ActivityRetainedScoped | 회전에도 유지(ViewModel 수명) |
@ViewModelScoped | ViewModel당 |
@ActivityScoped | Activity당 |
스코프를 안 붙이면 주입할 때마다 새 인스턴스, 붙이면 그 컴포넌트 안에선 같은 인스턴스가 공유된다.
Koin — 경량 런타임 DI
Koin은 코틀린 DSL로 의존성을 선언하는 경량 서비스 로케이터다. 런타임에 해석하므로 어노테이션 처리·빌드 부담이 없고 설정이 간단하지만, 그래프 오류를 런타임에야 발견하고 해석 비용이 약간 있다.
val appModule = module {
single<MemberRepository> { MemberRepositoryImpl(get(), get()) } // get()=의존성 해석
viewModel { MemberViewModel(get()) }
}
// ViewModel에서: by inject() / by viewModel()
Hilt vs Koin — 트레이드오프
| Hilt | Koin | |
|---|---|---|
| 검증 시점 | 컴파일 타임(안전) | 런타임 |
| 빌드 | KAPT/KSP 처리(느림) | 가벼움 |
| 러닝 커브 | 가파름(Dagger) | 완만 |
| 공식성 | 구글 공식 권장 | 커뮤니티 인기 |
큰 팀·안전 우선이면 Hilt(컴파일 검증), 작은 앱·빠른 시작이면 Koin이 흔한 선택이다.
정리 — 만들지 말고 받아라
DI의 핵심 — 의존성을 직접 new 하지 말고 밖에서 주입받아 결합도를 낮추고 테스트에서 교체하게 한다. Hilt는 컴파일 타임에 그래프를 생성·검증(@Module/@Provides/@Inject·스코프), Koin은 런타임 DSL로 가볍게. 둘 다 생성자 주입을 기본으로 — 의존성을 인터페이스로 받는 습관이 테스트 가능한 코드를 만든다.
한 줄 요약 — DI=의존성을 밖에서 주입(결합도↓·테스트 교체). Hilt(공식·Dagger·컴파일 검증):
@HiltAndroidApp·@AndroidEntryPoint·@HiltViewModel·@Inject constructor·@Module/@Provides/@Binds·스코프(@Singleton/@ViewModelScoped). Koin(경량·런타임 DSL·single/viewModel/get()). 컴파일 안전(Hilt) vs 간편(Koin).