만들어보고 싶은 프로젝트가 생겨 진행을 해볼까 했는데 맥북까지 있는 겸 KMP를 사용해 보기로 했다.
KMP를 사용하며 의존성 주입을 하려니 Hilt는 지원하지 않는단다. 이 때문에 Koin을 공부하게 된다...
예전 대학 동아리 프로젝트를 진행하며 잠깐 사용해본 Koin이었으나 정말 스쳐갔기 때문에 하나도 기억에 안 남아있기도 하고... 언젠가 KMP도 여러 기업들에서 쓰이는 날이 오면 좋을 테니... 일단 해보자
Koin을 알기 전에 당연하게 알고 들어가야 하는 내용이 있다.
바로 의존성 주입에 대해 알고 가야 한다.
DI(Dependency Injection, 의존성 주입)이란?
우선 의존에 대해 간단한 예시를 보자.
class UserRepository
class UserViewModel(
private val userRepository: UserRepository
)
UserViewModel은 UserRepository를 인자로 받고 이를 사용하게 된다.
즉, UserViewModel이 UserRepository에 의존하고 있다는 것이다.
직접 객체를 생성하는 방식의 문제점을 살펴보면
class UserViewModel {
private val repository = UserRepository()
}
- 객체 생성 책임이 ViewModel에 있다.
- 테스트하기 어렵다.
- 구현체 변경이 어렵다.
- 객체 간 결합도가 높아진다.
이를 해결하기 위해 의존성 주입이란 것을 하게 된다.
class UserViewModel(
private val repository: UserRepository
)
val repository = UserRepository()
val viewModel = UserViewModel(repository)
이제 DI Framework인 Koin에 대해 알아보자.
Koin이란?
Koin은 Di Framework로 코드 생성 방식이 아닌 Runtime 기반 DI이다.
핵심 개념
Module
↓
Definition
↓
Container
↓
Dependency Resolution
핵심 함수
- module
- single
- factory
- scoped
- get()
- inject()
- viewModel()
기본 사용법
single: 애플리케이션 하나의 인스턴스를 공유한다.
val appModule = module {
single {
UserRepository()
}
}
single {
Retrofit.Builder()
.baseUrl(BASE_URL)
.build()
}
factory: 요청할 때마다 새로운 객체를 생성한다.
factory {
UserRepository()
}
single은 같은 객체를 재사용 하는 것이라면, factory는 요청할 때마다 새로운 객체를 생성한다.
get(): Koin container에서 등록된 UserRepository를 찾아서 주입한다.
val appModule = module {
single { UserRepository() }
single {
UserViewModel(get())
}
}
get()
inject(): 필요한 시점에 Lazy하게 가져오는 주입 방식이다.
private val repository: UserRepository by inject()
Interface와 구현제 주입
interface UserRepository {
fun getUser()
}
class UserRepositoryImpl : UserRepository {
override fun getUser() {
}
}
// koin
single<UserRepository> {
UserRepositoryImpl()
}
class GetUserUseCase(
private val repository: UserRepository
)
UseCase
↓
UserRepository Interface
↓
UserRepositoryImpl
위와 같이 코드를 작성 시 UserRepositoryImpl이 주입된다.
Android에서 Koin 사용
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@MyApplication)
modules(appModule)
}
}
}
<application
android:name=".MyApplication">
Koin Module 구성
di/
├── NetworkModule.kt
├── DatabaseModule.kt
├── RepositoryModule.kt
└── ViewModelModule.kt
val networkModule = module {
single {
Retrofit.Builder()
.baseUrl(BASE_URL)
.build()
}
single<UserApi> {
get<Retrofit>().create(UserApi::class.java)
}
}
val repositoryModule = module {
single<UserRepository> {
UserRepositoryImpl(get())
}
}
val viewModelModule = module {
viewModel {
UserViewModel(get())
}
}
startKoin {
androidContext(this@MyApplication)
modules(
networkModule,
repositoryModule,
viewModelModule
)
}
Koin과 Hilt 비교
Koin의 장단점
장점
- Kotlin DSL이라 직관적
- 설정이 간단
- 작은 프로젝트에 빠르게 적용 가능
- 테스트 및 프로토타이핑에 편리
단점
- Runtime에 의존성 해결
- 잘못된 정의가 실행 시점에 발견될 수 있음
- 규모가 커질수록 Module 관리 중요
Hilt 장단점
장점
- Compile-time 검증
- Android Architecture Components와 높은 통합성
- 대규모 프로젝트에 적합
- Dagger 기반의 강력한 DI 기능
단점
- 러닝커브 높음
- 빌드 시간 증가
- 디버깅 및 에러 메시지 복합성
- 초기 설정 및 보일러플레이트
Koin 을 사용하며 주의할 점
1. 모든 것을 single로 만들지 않기
- single만 무작정 사용하면 객체 생명주기가 부적절해질 수 있음.
2. get() 남발하지 않기
- 의존성 관계가 명확하게 드러나는 Constructor Injection을 우선
3. Module을 적절히 분리하기
- 기능/레이어별로 분리
4. Interface를 적극적으로 사용하기
5. DI Framework가 Architecture를 대신해주지는 않음
- Koin은 의존성을 관리하는 도구일 뿐 의존성 방향과 계층 구조는 직접 설계해야 함
'Android' 카테고리의 다른 글
| Android-Unity 연동 (0) | 2026.06.16 |
|---|---|
| [Android] Notification 사용법 (0) | 2022.09.11 |
| [Android] PendingIntent란? (0) | 2022.08.26 |
| [Android] TabLayout(탭 레이아웃) 구현 (0) | 2022.08.24 |
| [Android] ViewPager2 사용법 (1) | 2022.08.23 |
댓글