의존성 주입.. "그거 어떻게 하는건데?"
DI란 의존성 주입(Dependency Injection)의 약자로 클래스 내부에서 객체를 생성하지 않고 외부에서 객체를 생성해 주입 받는 방식입니다.
그렇다면 DI를 왜 사용할까 라는 의문이 생기는데
DI(Dependency Injection)를 사용하면 결합도가 낮아지고 코드 수정이 쉬워지며, 테스트 코드 작성도 쉬워진다는 장점이 있습니다.
안드로이드에서 사용하는 DI는 Dagger, Koin, Hilt등 여러 라이브러리가 있는데
이중 Koin은 Kotlin에 특화된 라이브러리입니다.
먼저 Koin을 사용하기전 라이브러리를 주입 합니다.
Project단에 Koin의 버전과 플러그인을 적고
buildscript {
ext.kotlin_version = "1.5.31"
ext.koin_version = "3.0.2"
dependencies {
classpath "com.android.tools.build:gradle:4.2.2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "io.insert-koin:koin-gradle-plugin:$koin_version"
Module단에도 적어줍니다.
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
// koin
implementation "io.insert-koin:koin-android:$koin_version"
이후 저는 di라는 폴더를 생성 후 module.kt라고 모듈 파일을 따로 생성해줬습니다.
val appModule = module { // 데이터베이스 모듈
single { Room.databaseBuilder(
androidApplication(),
AppDatabase::class.java,
"psg-app.db")
.build() }
single { get<AppDatabase>().sleepTimeDao() }
}
val viewModelModule = module { // 뷰모델 모듈
viewModel { MainViewModel(get()) }
viewModel { FullMapViewModel() }
viewModel { DetailMapViewModel() }
}
val repositoryModule = module { // 레포지토리 모듈
single { AppRepository(get()) }
}
세 가지 모듈을 적는데 이때
module
키워드를 사용해 모듈로 선언하고 변수에 추가해줍니다.
- single : 앱이 실행되는 동안 계속 유지되는 싱글톤 객체를 생성합니다.
- factory : 요청할 때마다 매번 새로운 객체를 생성합니다.
- get() : 컴포넌트 내에서 알맞은 의존성을 주입 받습니다.
-> 위의 예제에서AppRepository(get())
의 get()은
single { get<AppDatabase>().sleepTimeDao() }
의 Dao를 주입받습니다.- viewModel : 뷰모델은 따로 viewModel 키워드로 선언해줍니다.
이제 모듈을 만들었다면 Appication을 상속 받는 클래스를 생성합니다.
class MyApp: Application() {
init {
INSTANCE = this
}
companion object{
lateinit var INSTANCE: MyApp
fun applicationContext(): Context = INSTANCE.applicationContext
}
override fun onCreate() {
super.onCreate()
startKoin {
androidLogger(Level.NONE)
androidContext(this@MyApp)
modules(listOf(appModule, viewModelModule, repositoryModule))
}
}
}
저는 MyApp이라는 이름의 Application 클래스를 생성했습니다.
이 클래스에서 onCreate부분에 startKoin
이라는 함수로 모듈을 선언해줍니다.
- androidLogger(...) : AndroidLogger를 Koin logger로 사용합니다.
- androidContext(...) : 해당 안드로이드 context를 사용합니다.
- modules(...) : 사용할 모듈을 등록합니다.
modules(listOf(appModule, viewModelModule, repositoryModule))
위의 방법으로 아까 적은 세 가지 모듈을 등록해줍니다.
이후에 manifest에 등록 해주어야 하는데
<application
android:name=".MyApp"
이렇게 application의 name에 .클래스명을 적어주면 끝입니다.
적용한 모듈을 Activity에서 사용해보면
private val viewModel: MainViewModel by inject()
이렇게 by inject()
키워드만 사용하면 손쉽게 뷰모델을 사용할 수 있습니다.