→ Koin = 런타임(Dynamic) DI 컨테이너
→ Hilt/Dagger = 컴파일 타임(Code-Generated) DI 컨테이너
→ 즉, Koin은 코드를 생성하지 않고, 앱 실행 중에 Service Locator 방식 + DSL 모듈 등록으로 DI를 수행함
Koin 시작
startKoin{
androidContext(this@BeaverPayApp)
modules(
appModule,
presentersModule,
domainModule,
dataModule,
localModule,
remoteModule,
payonModule
)
}
→ 이렇게 하면 KoinApplication 객체가 생성되며 내부에 Instance Registry가 만들어짐
정의된 module 등록
val dataModule = module {
includes(localModule) // local Module 포함
single<UserRepository> {
UserRepositoryImpl(get()) // get() -> LocalUserDataSource
}
single<PayonCardReaderRepository> {
PayonCardReaderRepositoryImpl(get())
}
}
val domainModule = module {
// UseCase
factory { CheckUserUseCase(get()) }
factory { InsertUserUseCase(get()) }
factory { PayonCardReaderUseCase(get()) }
}
val presentersModule = module {
viewModel { ReaderViewModel(get()) }
viewModel { LoginViewModel(get(), get()) }
viewModel { MainViewModel() }
}
→ 모듈에 등록되면 Koin은 이를 Key-Value 형태로 저장함
| Key | Value |
|---|---|
| UserRepository | 정의된 Provider 함수 |
| ReaderViewModel | Provider 함수 |
DI 발생 (get(), Inject(), viewModel() 호출 순간)
val repo: UserRepository = get()
→ 이 순간 Koin은 다음 절차를 수행
ex)
val dataModule = module {
single<UserRepository> { UserRepositoryImpl(get()) }
}
/*
* 1. single<UserRepository> → "UserRepository 타입이 필요할 때, 이걸 써라" 라는 정의
* 2. {UserRepositoryImpl(get())} → "생성 시 LocalUserDataSource 같은 의존성을 get() 해서 넣어라"
* 3. 즉, UserRepository가 필요한 클래스의 생성자에서 요청될 때,
* Koin이 알아서 UserRepositoryImpl 인스턴스를 만들어 넣습니다.
* 4. class ReaderViewModel(private val userRepository: UserResposigory)을 호출할 때
* Koin 내부에서 get()이 자동으로 실행되는 것이다.
*/
→ Koin의 DI 동작 흐름
get<UserRepository>() 요청
**↓**
Koin Registry에 등록된 UserRepository Provider 찾기
**↓**
싱글톤? → 이미 instance 있으면 반환
↓
없으면 Provider 함수 실행
↓
UserRepositoryImpl(get()) 실행
↓
내부 get() 으로 의존성 탐색 반복
↓
UserRepositoryImpl 인스턴스 생성
↓
Registry에 캐싱(싱글톤이면)
↓
결과 반환
: 런타임에 필요한 객체를 모듈에서 찾아 직접 생성 → 반환하는 방식
Hilt와 비교하면?
| 항목 | Koin | Hilt |
|---|---|---|
| DI 방식 | 런타임 | 컴파일 타임 |
| 성능 | 느릴 수 있음(런타임 분석) | 빠름(코드 생성) |
| 안정성 | 런타임 오류 ↑ | 컴파일 오류로 조기 발견 |
| 코드량 | 적음 | 많음(Annotation 필요) |
| KMP 지원 | 🌟 좋음 | 없음 |
| 멀티 모듈 | 쉬움 | 복잡함 |
| 학습 난이도 | 쉬움 | 어려움 |
Compose에서 ViewModel 주입
val viewModel: LoginViewModel = koinViewModel()왜 Koin이 KMP에 적합한가?
→ 결국 KMP 프로젝트는 Hilt 대신 Koin 사용이 정답.
→ Koin은 런타임 DI 컨테이너로, 등록된 module에서 객체를 찾아 직접 생성하여 의존성을 해결한다.
코드 생성 없이 DSL 기반이라 구조가 단순하고 KMP 지원이 매우 좋다.