이번에는 DI 라이브러리
중 하나인 Koin
에 대해서 정리를 해보고자 합니다. 기존에 DI 라이브러리
로 유명한건 Dagger
인데, Dagger
가 학습 곡선이
높아서 우선 상대적으로 학습 곡선이 낮은 Koin
을 학습하고 그 이후에 Dagger
도 같이 볼까 합니다.
DI(Dependency Injection)이란?
DI 라이브러리
인 Koin
을 설명하기 전에 우선 DI(Dependency Injection)
가 무엇인지부터 설명을 하려고 합니다. 일단 Dependency
는 의존성
이고
Injection
은 주입
이란 의미라 단순 번역은 의존성 주입
입니다.
의존성
이란 예를 들어서 A
라는 클래스가 내부에서 B
라는 클래스를 참조
하는 경우 A클래스->B클래스 의존성을 갖는다
고 말할 수 있습니다.
class B {
...
}
class A {
val b = B() // <- A클래스 내부에서 클래스 B를 생성하여 사용
...
}
근데 이렇게 의존성
이 생기게 되면 B클래스
의 변화가 생기면 의존성
을 갖는 A클래스
도 같이 변경을 해야하는데, 만약 B클래스
가
바뀌면 B클래스
의 의존성
을 갖는 모든 클래스
가 변경
이 되어야 합니다.
예를 들어서 B클래스
에 C클래스
의 의존성
이 생겨서 이를 생성자
에서 넣어야 한다면 아래와 같이 바뀌어야 합니다.
// 새로 추가된 클래스
class C {
...
}
class B(private val c: C) {
...
}
class A {
val b = B(C()) // <- B클래스가 변경됨으로 이 부분에 변화가 생깁니다.
...
}
사실 위의 소스에서도 B클래스
에 DI
가 발생하였는데 B클래스
에서는 C클래스
에 대한 의존성
을 생성자
를 통해서 주입
했습니다.
이렇게 DI가 발생하면 아래와 같은 장점을 가질 수 있습니다.
재사용성
을 높여줍니다.테스트
에 용이합니다.- 코드를
단순화
시켜줍니다. 종속된 코드
를 줄여줍니다.결합도
를 낮추면서유연성
과확장성
이 향상됩니다.
IoC (Inversion of Control)
또 하나 더 알아야 할 개념으로 IoC
가 있습니다. 제어 역전
이란 말로 해석이 되며, 제어 역전
은 개발자가 직접 프로그램의 흐름을 제어하는 코드를
작성하지 않고 외부 프레임워크의 흐름 제어를 받도록 하는 개발 원칙으로 IoC
를 따라 소프트웨어를 개발하면 인스턴스의 생성
이나 이벤트 처리
등의
호출을 프레임워크
가 알아서 해주게 됩니다.
간단히 말하면 모든 제어 권한을 자신이 아닌 다른 대상에게 위임하는 것입니다.
Koin 이란?
Koin은 코틀린을 위한 DI 라이브러리로 학습곡선이 높은 Dagger
에 비해 상대적으로 낮은 학습곡선을 가지고 있으며
순수 코틀린만으로 작성이 되었으며 어노테이션 프로세싱을 및 리플렉션을 사용하지 않기 때문에 상대적으로 더 가볍습니다.
더 자세한 내용을 알고 싶으시면 Koin
의 공식 사이트에서 확인하실 수 있습니다.
실습
설치하기위해서 gradle
에 아래와 같이 설정을 합니다. 버전이나 새로운 업데이트 내용은 Koin Github에서
확인하실 수 있습니다.
아래 내용은 Koin 공식 튜토리얼을 따라하면서 정리한 내용입니다.
우선 project
에는 아래와 같이 설정을 합니다.
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.3.41'
ext.koin_version = '2.0.1' // <- 여기서 Koin 버전 설정
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter() // <- Koin 설치를 위해서 필요
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
[gradle.Project]
그 다음 app쪽에 dependencies에 아래와 같이 설정합니다.
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.byjw.kointest"
minSdkVersion 23
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
// Koin for Kotlin
implementation "org.koin:koin-core:$koin_version"
// Koin extended & experimental features
implementation "org.koin:koin-core-ext:$koin_version"
// Koin for Unit tests
testImplementation "org.koin:koin-test:$koin_version"
// Koin for Java developers
implementation "org.koin:koin-java:$koin_version"
// Koin for Android
implementation "org.koin:koin-android:$koin_version"
// Koin Android Scope features
implementation "org.koin:koin-android-scope:$koin_version"
// Koin Android ViewModel features
implementation "org.koin:koin-android-viewmodel:$koin_version"
// Koin Android Experimental features
implementation "org.koin:koin-android-ext:$koin_version"
// Koin AndroidX Scope features
implementation "org.koin:koin-androidx-scope:$koin_version"
// Koin AndroidX ViewModel features
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
// Koin AndroidX Experimental features
implementation "org.koin:koin-androidx-ext:$koin_version"
}
[gradle.app]
이제 설치가 되었고 구현을 해보고자 합니다. Koin은 크게 아래 단계를 통해서 구현을 하게 됩니다.
모듈
생성하기 (Koin DSL)Android Application Class
에서startKoin()
으로 실행하기- 의존성 주입
우선 Koin DSL
을 이용하여 모듈을 설치하여야 하는데 DSL
은 무엇인까요? DSL(Domain Specific Language)
으로 번역을 하자면 도메인 특화 언어
로
위키피디아에는 "특정한 도메인을 적용하는데 특화된 언어"
라고 정의되어 있습니다.
Koin DSL은 아래와 같이 있습니다.
applicationContext
: Koin 모듈 생성factory
: 매번 inject 할때마다 인스턴스 생성single
: 싱글톤 생성bind
: 종속시킬 class나 interface를 주입get
: 컴포넌트내에서 알맞은 의존성을 주입함
모듈은 아래와 같이 만들 수 있습니다.
package com.byjw.kointest
import org.koin.dsl.module
// 여기서 Koin DSL을 이용하여 모듈을 만들어줍니다.
val appModule = module {
single<Repository> { RepositoryImpl() } // 싱글톤으로 생성
factory { MyPresenter(get()) } // 의존성 주입때마다 인스턴스 생성, get()을 이용하면 위에서 선언된 적절한 클래스가 주입됩니다.
}
[MyModule.kt]
모듈에서 사용하는 인터페이스입니다.
package com.byjw.kointest
interface Repository {
fun getMyData(): String
}
[Repository.class]
아래는 인터페이스의 구현 클래스입니다.
package com.byjw.kointest
class RepositoryImpl : Repository {
override fun getMyData(): String {
return "Hello Koin"
}
}
[RepositoryImpl.class]
아래는 위에서 만든 Repository
를 사용하는 Presenter
입니다. 생성자에서 repository
의 인스턴스가 의존성 주입이 되는데, 모듈에서의 get()
을 통해서
자동으로 주입이 됩니다.
package com.byjw.kointest
class MyPresenter(private val repository: Repository) {
fun sayHello() = "${repository.getMyData()} from $this"
}
[MyPresenter.class]
이제 startKoin()
을 실행시키기 위해 Application Class
를 만듭니다.
package com.byjw.kointest
import android.app.Application
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@MyApplication)
modules(appModule)
}
}
}
[MyApplication.class]
여기서 주의해야할 점은 Application Class
를 만들었으면 AndroidManifest.xml
에 android:name
에 적용 해주어야 합니다.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
package="com.byjw.kointest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:name=".MyApplication"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
[AndroidManifest.xml]
실제 사용은 아래와 같이 by inject()
를 통해서 바로 의존성을 주입할 수 있습니다.
package com.byjw.kointest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import org.koin.android.ext.android.inject
class MainActivity : AppCompatActivity() {
private val myPresenter: MyPresenter by inject() // 이 부분을 통해서 의존성 주입이 일어납니다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
myTextView.text = myPresenter.sayHello()
}
}
[MainActivity.class]