Koin is a pragmatic and lightweight dependency injection framework for Kotlin developers.
코인은 코틀린 개발자를 위해 개발된 실용적이고 가벼운 의존성 주입 프레임워크입니다. - Koin
안드로이드 진영에서 사용되는 의존성 주입 프레임워크는 대표적으로 Dagger
, Hilt
, Koin
등이 있는데, Koin
은 이중에서도 가장 낮은 러닝 커브를 자랑하여 의존성 주입에 입문하기 굉장히 좋은 프레임워크이다.
필요한 객체를 직접 생성하지 않고 외부로부터 객체를 넘겨 받아서 사용하는 것을 의미한다.
// 의존성 주입 X
class Computer {
Monitor monitor = new Monitor(); // 컴퓨터는 모니터에 의존성을 가지고 있다.
// Monitor 가 변경되면...?
}
// 의존성 주입
class Computer {
Monitor monitor;
public Computer(Monitor monitor) { // 의존성 주입
this.monitor = monitor;
}
}
의존성 주입에 관련된 글은 추후에 자세히 작성할 예정인데, 의존성 주입을 함으로서 얻는 이점은 아래와 같다.
실습을 하면서 키워드를 익혀보도록 하자.
dependencies {
// Koin
implementation("io.insert-koin:koin-core:3.1.2")
implementation("io.insert-koin:koin-android:3.1.2")
}
class SingleInstance {
companion object {
var count : Int = 0
}
init { // 객체 생성시마다 1씩 증가하며 생성된 새 객체의 수를 추적하는데 사용한다.
++count
}
fun hello() = "i am single instance number $count"
}
class FactoryInstance {
companion object {
var count : Int = 0
}
init { // 객체 생성시마다 1씩 증가하며 생성된 새 객체의 수를 추적하는데 사용한다.
++count
}
fun hello() = "i am factory instance number $count"
}
class SampleRepository() {
fun hello() = "Hello Sample Repository"
}
class SampleViewModel(private val repository: SampleRepository) : ViewModel() {
fun hello() = repository.hello()
}
샘플로 사용할 클래스를 생성한다.
val singleModule = module {
single { SingleInstance() }
}
val factoryModule = module {
factory { FactoryInstance() }
}
val repositoryModule = module {
single { SampleRepository()}
}
// Repository 모듈이 선언되어 있지 않으면 정상 작동하지 않는다.
val viewModelModule = module {
viewModel { SampleViewModel(get()) }
}
single
: 컨테이너 내에서 단 한번만 생성된다.factory
: 컨테이너 내에서 요청 시점마다 새로운 객체를 생성한다.get()
: 컨테이너 내에서 알맞은 의존성을 주입한다. (위 코드에서는 SampleRepository가 주입된다.)
viewModel
: 컨테이너 내에 해당 viewModel
을 등록한다. (ViewModelFactory 안에 Koin이 알아서 등록해준다.)
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// 컨테이너 등록
// Application 이 파괴되면 컨테이너도 함께 파괴
startKoin {
androidContext(this@MyApplication)
modules(singleModule, factoryModule, repositoryModule, viewModelModule) // 모듈 등록
}
}
}
의존성 주입을 위한 컨테이너를 생성한다. 이 컨테이너는 Application
의 생명주기에 맞게 생성되고 삭제된다. (액티비티 내부에서 컨테이너를 생성했다면 액티비티의 생명주기를 따를 것이다.)
주의점으로, Application
클래스는 생성 후 AndroidManifest.xml
에서 새롭게 등록해주어야 한다.
<application
android:name=".MyApplication"
. . . >
</application>
inject()
키워드를 이용하여 지연 초기화로 의존성을 주입할 수 있다. 뷰모델의 경우 viewModel()
키워드를 사용하여 주입하며, 마찬가지로 지연 초기화의 방법이다. (불변 객체, 즉 val 에만 사용 가능)
만약 지연 초기화 하고 싶지 않다면 각각 get()
, getViewModel()
을 사용하면 된다. (var 에도 사용 가능)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// by inject() 를 사용하여 의존성 주입, 사용할 때 초기화된다. (lazy init)
val singleInstance : SingleInstance by inject()
val singleInstance2 : SingleInstance by inject()
val singleInstance3 : SingleInstance by inject()
val factoryInstance : FactoryInstance by inject()
val factoryInstance2 : FactoryInstance by inject()
val factoryInstance3 : FactoryInstance by inject()
// val repository : SampleRepository by inject()
val viewModel : SampleViewModel by viewModel()
// 지연 초기화하지 않는 방법
var nonLazySingleInstance : SingleInstance = get()
var nonLazyViewModel : SampleViewModel = getViewModel()
// 등록된 객체의 인스턴스 요청
// 싱글톤 객체의 경우 카운트가 늘어나지 않는 모습 확인
log(singleInstance.hello())
log(singleInstance2.hello())
log(singleInstance3.hello())
// 팩토리 객체는 생성할 때마다 카운트가 늘어나는 모습 확인
// 의존성을 요청할 때마다 새로운 객체 생성
log(factoryInstance.hello())
log(factoryInstance2.hello())
log(factoryInstance3.hello())
// log(repository.hello())
log(viewModel.hello())
}
}
위에서 이야기했듯이 single
모듈은 컨테이너 내에서 단 한번만 생성되는 반면, factory
모듈은 여러번 생성되어 카운트가 증가된 모습을 확인할 수 있다. 또한, get()
키워드를 이용하여 컨테이너 내에서 알맞은 의존성이 주입된 것을 확인할 수 있다.
참고 및 출처