봄날은 갔다. 이제 그 정신으로 공부하자

의존성 주입 라이브러리 - 1편 (koin) 본문

Android jetpack

의존성 주입 라이브러리 - 1편 (koin)

길재의 그 정신으로 공부하자 2020. 11. 23. 18:18

 

android 앱 개발에 다양한 패턴(MVP, MVVM)이 적용되면서 DI에 필요가 증가하였고 현재 많은 android 앱들이 개발에 Koin이나 Dagger와 같은 의존성 주입 라이브러리를 사용하고 있습니다.

해당 글에서는 android 의존성 주입 라이브러리(Data Injection lib) 중 하나인 Koin에 대해 설명합니다.

Dagger는 별도의 글에서 설명합니다.

 

Koin이란?

Koin은 Java로 구현된 Dagger과 달리 Kotlin언어로 개발된 DI 라이브러리이며, 순수 Kotlin으로만 작성되어있어

Proxy, Annotation 프로세싱을 통한 코드 생성, 리플렉션을 사용하지 않기 때문에 가볍습니다.

Koin은 Kotlin에서 제공하는 DSL(Domain-Specific Language)을 활용하여, 의존성을 주입을 하기위한 똑똑하고

실용적인 API를 만들어낼 수 있습니다.

 

DI(Data Injection)란?

DI는 Data Injection(의존성 주입)의 줄임말로 구성요소간의 의존 관계가 소스코드 내부가 아닌 외부 설정 파일등을 통해 정의되게하는 디자인 패턴중 하나입니다. 예를들면, 카페에서 커피를 만드는데 커피 머신이 어떤 부품으로 구성되어있는지 바리스타는 알필요가 없다는 개념과 비슷하며 이렇게 분리시켜 놓으면 객체의 생성과 사용을 분리시킬 수 있고, 재사용이 유연해지는 장점이 있습니다.

Application & Module DSL

Koin은 Koin Application의 요소를 설명할 수 있는 몇개의 키워드를 제공합니다.

  • Application DSL: Koin container 구성을 설명
  • Module DSL: 주입해야 할 구성 요소를 설명

Application DSL

KoinApplication 인스턴스는 Koin 컨테이너 인스턴스 구성입니다. 이를 통해 Logging, properties loading 및 modules을 구성 할 수 있습니다.

새로운 Koin Application을 만들기 위해서는 아래 함수들을 사용해야 합니다.

  • KoinApplication { } -> KoinApplication을 생성하는 컨테이너 구성
  • startKoin { } -> KoinApplication 컨테이너를 구성하고 GlobalContext를 사용하여 등록합니다.

 

KoinApplication 인스턴스 속성으로는 아래와 같은 함수들이 있다.

  • logger ( ) -> 사용할 레벨 및 로거 구현를 설명합니다.
  • modules ( ) -> 컨테이너에 로드할 Koin 모듈의 리스트 설정 (list or varargs list)
  • properties ( ) -> HashMap 속성을 Koin 컨테이너에 로드
  • fileProperties ( ) -> 주어진 파일에서 Koin 컨테이너로 속성 로드
  • environmentProperties ( ) -> OS 환경에서 Koin 컨테이너로 속성 로드

KoinApplication instance: Global vs Local

위에서 설명하였듯이 koin 컨테이너 구성은 koinApplication 또는 startKoin 함수의 두 가지 방법으로 설명 할 수 있습니다.

  • koinApplication: Koin 컨테이너 인스턴스를 설명합니다.
  • startKoin: Koin 컨테이너 인스턴스를 설명하고 Koin GlobalContext에 등록합니다.

컨테이너 구성을 GlobalContext에 등록하면 전역 API가 이를 직접 사용할 수 있습니다. 

모든 KoinComponent는 Koin 인스턴스를 나타낸다. 기본적으로 GlobalContext를 사용합니다.

자세한 내용은 아래 "Custom Koin 인스턴스" 항목을 통해 확인 가능 합니다.

Koin 시작하기

Koin을 시작한다는 것은 KoinApplication 인스턴스를 GlobalContext로 실행한다는 의미이며 모듈로 Koin 컨테이너를 시작하려면 아래와 같이 startKoin 함수를 사용합니다.

// start a KoinApplication in Global context
startKoin {
    // declare used logger
    logger()
    // declare used modules
    modules(coffeeAppModule)
}

Module DSL

 

Koin을 사용하는 Application을 만들기 위해서는 아래 함수들을 사용합니다.

  • module{ // module content } -> join 모듈 생성

모듈의 콘텐트에는 아래 함수들을 사용합니다.

  • factory { // definition } -> Injection하는 시점에 인스턴트 생성 (매번 생성)
  • single { // definition } -> 앱이 살아있는 동안 전역적으로 사용가능한 객체를 생성
  • get( ) -> 주입할 각 컴포넌트끼리의 의존성을 해결하기 위해 사용
  • bind( ) -> 생성할 객체를 다른 타입으로 바인딩하고 싶을때 사용
  • binds( ) -> 생성할 객체를 다른 타입들로 바인딩하고 싶을때 사용
  • scope{ // scope group }  -> 주어진 scope들 내에서만 유효한 인스턴스 생성
  • scoped{ // definition } -> 주어진 scope에서만 유효한 인스턴스 생성

 

Definitions

Writing a module

Koin module은 모든 구성 요소를 선언하는 공간으로 아래와 같이 module 함수를 사용하여 Koin 모듈을 선언이 가능합니다.

val myModule = module {
   // your dependencies here
}

Defining a singleton

singleton 컴포넌트를 선언한다는 것은 Koin 컨테이너가 선언 된 컴포넌트의 unique instance를 유지한다는 것을 의미하며, 선언 방법은 아래와 같습니다.

class MyService()
val myModule = module {
    // declare single instance for MyService class
    single { MyService() }
}

Defining your component within a lambda

single, factory & scope keywords를 사용하면 lambda 식을 통해 구성 요소를 선언 할 수 있으며 이 lambda는 구성 요소를 작성하는 방법을 설명합니다. 일반적으로 생성자를 통해 구성 요소를 인스턴스화하지만 어떠한 식도 사용 가능합니다.

lambda의 결과 타입은 컴포넌트의 기본타입니다.

single { Class constructor // Kotlin expression }

 

Defining a factory

factory 컴포넌트는 요청할 때마다 새 인스턴스를 제공하는 방식으로 아래와 같이 사용합니다.

class Controller()
val myModule = module {
    // declare factory instance for Controller class
    factory { Controller() }
}

Resolving & injecting dependencies

Koin은 get()을 호출하는 것만으로 적당한 객체를 찾아 주입하게 됩니다.

class ComponentA()
class ComponentB(val componentA : ComponentA)
val myModule = applicationContext {
   bean { ComponentA() }
   bean { ComponentB(get()) }
}

위의 예제를 보면 ComponentB의 생성자에 대한 매개변수로 ComponentA를 받고 있습니다.

이때 미리 정의해둔 ComponentA객체가 있다면 생성자에 get()을 호출하여 간단히 객체를 주입할 수 있습니다.

Definition: binding an interface

single or factory는 lambda식내에서 사용한 타입에 의해 타입이 결정됩니다.

일치하는 타입이 있을 경우에 주어진 객체를 주입하는데, 인터페이스를 타입으로 하는 객체를 주입 하려는 경우 이를 상속한 구현체를 주입할수도 있습니다.

// Service interface
interface Service{
    fun doSomething()
}
// Service Implementation
class ServiceImp() : Service {
    fun doSomething() { ... }
}

Koin 모듈내에서 as 키워드를 통해 아래와 같이 인터페이스 구현체를 바인딩을 할 수 있고,

val myModule = module {
    // Will match type ServiceImp only
    single { ServiceImp() }
    // Will match type Service only
    single { ServiceImp() as Service }
}

또 다른 방식으로 타입 추론 방식을 사용할 수도 있습니다

val myModule = module {
    // Will match type ServiceImp only
    single { ServiceImp() }
    // Will match type Service only
    single<Service> { ServiceImp() }
}

추가 타입 바인딩(Additional type binding)

경우에 따라 한개의 정의에 여러개의 타입을 매칭시켜 할 때가 있는데, 클래스와 인터페이스를 예를 들어 설명하면 아래와 같습니다.

// Service interface
interface Service{
    fun doSomething()
}


// Service Implementation
class ServiceImp() : Service{
    fun doSomething() { ... }
}

추가 유형의 bind를 정의하기 위해서는 아래와 같이 class와 함께 “bind” 명령어가 필요합니다.

val myModule = module {
    // Will match types ServiceImp & Service
    single { ServiceImp() } bind Service::class
}

여기서 get ()을 사용하여 서비스 유형을 직접 확인할 수 있으나 여러 정의 바인딩 서비스가 있는 경우에는 

bind<>() 함수를 사용해야합니다.

Definition: naming & default bindings

동일한 유형에 대해 두 가지 정의를 구별 할 수 있도록 정의에 이름을 지정할 수 있습니다.

val myModule = module {
 single<Service>(named("default")) { ServiceImpl() }
 single<Service>(named("test")) { ServiceImpl() }
}
val service : Service by inject(name = named("default"))

get () 및 inject () 함수를 통해 필요한 경우 정의 이름을 지정할 수 있으며 이름은 named () 함수에 의해 생성 된 한정자 입니다.

val myModule = module {
    single<Service> { ServiceImpl1() }
    single<Service>(named("test")) { ServiceImpl2() }
}
val service : Service by inject() // ServiceImpl1 definition
val service : Service by inject(named("test")) // ServiceImpl2 definition

 

Declaring Injection parameters

single, factory or scoped 정의 시 인젝션 파라미터를 사용할 수 있습니다.

 

class Presenter(val view : View)
val myModule = module {
    single{ (view : View) -> Presenter(view) }
}

get()과 달리 Injection parameters는 resolution API 통해 전달되는 매개변수이며, 이는 해당 매개 변수가 parametersOf 함수와 함께 get() 및 inject()로 전달 된 값임을 의미합니다.

val presenter : Presenter by inject { parametersOf(view) }

Using definition flags

실행 시 인스턴스 생성

실행 시(또는 원할 경우) 인스턴스를 생성하기 위해서는 createAtStart 플래그를 사용해야 합니다.

// definition에서의 createAtStart 플래그
val myModuleA = module {
    single<Service> { ServiceImp() }
}
val myModuleB = module {
    // eager creation for this definition
    single<Service>(createAtStart=true) { TestServiceImp() }
}


// module에서의 createAtStart 플래그
val myModuleA = module {
    single<Service> { ServiceImp() }
}
val myModuleB = module(createAtStart=true) {
    single<Service>{ TestServiceImp() }
}

 

startKoin 함수는 createAtStart로 플래그 지정된 정의 인스턴스를 자동으로 생성합니다.

// Start Koin modules
startKoin {
    modules(myModuleA,myModuleB)
}

 

Dealing with generics

Koin definition은 제너릭 타입 매개변수를 고려하지 않습니다.

예를 들면, 아래 모듈은 List의 두 가지 정의를 정의하려고 시도합니다.

module {
    single { ArrayList<Int>() }
    single { ArrayList<String>() }
}

Koin은 이러한 제너릭 타입을 지원하지 않으므로 이를 사용하려면 아래와 같이 named or location(module)을 통해 구별해야 합니다.

module {
    single(named("Ints")) { ArrayList<Int>() }
    single(named("Strings")) { ArrayList<String>() }
}

 

Modules

What is a module?

Koin 모듈은 Koin 정의를 수집하기위한 “공간”으로 module 함수로 선언합니다.

val myModule = module {
    // Your definitions ...
}

Using several modules

Component가 반드시 동일한 module에 있을 필요는 없습니다.  

module은 정의를 구성하는 데 도움이 되는 논리적 공간이며 다른 모듈의 정의에 따라 달라질 수 있습니다.

Koin에서 모든 module에 대한 정의는 lazy init이 기본 값입니다.

별도의 module에 링크 된 component가 있는 예는 아래와 같습니다.

// ComponentB <- ComponentA
class ComponentA()
class ComponentB(val componentA : ComponentA)
val moduleA = module {
    // Singleton ComponentA
    single { ComponentA() }
}
val moduleB = module {
    // Singleton ComponentB with linked instance ComponentA
    single { ComponentB(get()) }
}

위와 같이 moduleA module과  moduleB module을 분리하여 정의하여도 상호간에 조합 or 참조가 가능합니다.

// Start Koin with moduleA & moduleB
startKoin{
    modules(moduleA,moduleB)
}

module을 전략적으로 연결하기

기본적으로 module간의 정의가 lazy init이기 때문에 모듈을 사용하여 다른 전략 구현을 구현할 수 있습니다.

리포지토리 및 데이터 소스를 예로 들어 설명하면, 저장소에는 데이터 소스가 필요하며 데이터 소스는 로컬 또는 원격의 두 가지 방법으로 구현할 수 있습니다.

class Repository(val datasource : Datasource)




interface Datasource


class LocalDatasource() : Datasource
class RemoteDatasource() : Datasource

3개의 module에 각각의 component를 선언합니다.

val repositoryModule = module {
    single { Repository(get()) }
}


val localDatasourceModule = module {
    single<Datasource> { LocalDatasource() }
}


val remoteDatasourceModule = module {
    single<Datasource> { RemoteDatasource() }
}

그런 다음 상황에 맞게 module을 조합하여 런칭하면 됩니다.

// Load Repository + Local Datasource definitions
startKoin {
    modules(repositoryModule,localDatasourceModule)
}


// Load Repository + Remote Datasource definitions
startKoin {
    modules(repositoryModule,remoteDatasourceModule)
}

module 재정의

Koin에서는 기존 정의(type, name, path ...)를 재정의 할 수 없습니다. 

만약 시도하면 오류가 발생합니다. (아래 예시는 오류 발생함.)

val myModuleA = module {
    single<Service> { ServiceImp() }
}
val myModuleB = module {
    single<Service> { TestServiceImp() }
}
// Will throw an BeanOverrideException
startKoin {
    modules(myModuleA,myModuleB)
}

module을 재정의하려면 override 파라미터를 사용해야 합니다.

// Override Type1
val myModuleA = module {
    single<Service> { ServiceImp() }
}
val myModuleB = module {
    // override for this definition
    single<Service>(override#true) { TestServiceImp() }
}


// Override Type2
val myModuleA = module {
    single<Service> { ServiceImp() }
}
// Allow override for all definitions from module
val myModuleB = module(override#true) {
    single<Service> { TestServiceImp() }
}

module을 나열하고 정의를 재정의 할 때 순서가 중요하며, module 목록의 마지막에 재정의 정의가 있어야합니다.

 

Start Koin

The startKoin Function

startKoin 함수는 Koin 컨테이너를 시작하는 주요 진입 점입니다. 

실행하려면 Koin module 목록이 필요하며, 모듈이 로드 되고 Koin 컨테이너에서 정의를 해결할 준비가되어 있어야합니다.

startKoin {
    // declare used modules
    modules(…)
}

startKoin 함수가 호출되면 Koin은 모든 모듈 및 정의를 읽어 Koin은 get() 또는 inject() 호출을 통해 필요한 인스턴스를 검색 할 준비를 합니다.

Koin 컨테이너는 아래와 같은 몇개의 옵션을 갖습니다.

  • logger -> 로깅을 활성화하려면 << logging.adoc # _logging, logging >> 섹션을 참조
  • properties (), fileProperties () 또는 environmentProperties ()를 사용하여 환경, koin.properties 파일, 추가 특성에서 특성을 로드할 수 있습니다. << properties.adoc # _lproperties, properties >> 섹션을 참조

startKoin을 두 번 이상 호출 할 수 없습니다. 

module을 로드하기 위해 여러 포인트가 필요한 경우, loadKoinModules 함수를 사용하면 됩니다.

Behind the start (KoinApplication & Koin instance)

Koin을 시작하면 Koin 컨테이너 구성 인스턴스를 나타내는 KoinApplication 인스턴스를 만듭니다.

일단 시작되면 module과 옵션의 결과로 Koin 인스턴스가 생성됩니다.

그런 다음 이 Koin 인스턴스는 GlobalContext에 등록되어 모든 KoinComponent 클래스에서 사용됩니다.

startKoin 호출 후 module 로딩하기 (loadKoinModule 함수)

loadKoinModule 함수는 Koin을 사용하려는 라이브러리 제공자를 위한 것입니다. 

loadKoinModule 함수는 startKoin 함수를 사용할 필요가 없고 라이브러리 시작시 loadKoinModules 만 사용할 수 있기 때문입니다.

loadKoinModules(module1,module2 ...)

모듈 로딩 해제하기

아래 unloadKoinModules 함수를 사용하여 인스턴스를 해제 할 수 있습니다.

unloadKoinModules(module1,module2 ...)

Koin context isolation

라이브러리 개발자의 경우, 글로벌이 아닌 방식으로 Koin을 사용할 수 있는데,

라이브러리 DI로 Koin을 사용하고 Context를 격리하여 라이브러리와 Koin을 사용하는 사용자들의 충돌을 피할 수 있습니다.

격리된 Koin context 사용법은 아래와 같습니다.

// create a KoinApplication
val myApp = koinApplication {
    // declare used modules
    modules(coffeeAppModule)
}

라이브러리에서 myApp 인스턴스를 사용 가능하게 유지하고 이를 사용자 정의 KoinComponent 구현으로 전달해야합니다.

// Get a Context for your Koin instance
object MyKoinContext {
    var koinApp : KoinApplication? = null
}
// Register the Koin context
MyKoinContext.koinApp = KoinApp
abstract class CustomKoinComponent : KoinComponent {
    // Override default Koin instance, intially target on GlobalContext to yours
    override fun getKoin(): Koin = MyKoinContext?.koinApp.koin
}

마지막으로 Context를 등록하고 격리 된 Koin 컴포넌트를 실행합니다.

// Register the Koin context
MyKoinContext.koinApp = myApp
class ACustomKoinComponent : CustomKoinComponent(){
    // inject & get will target MyKoinContext
}

Stop Koin (closing all resources)

Koin은 리소스를 close하고 인스턴스 해제를 위해 stopKoin() 함수를 지원 합니다.

KoinApplication 인스턴스에는 close() 함수를 호출하면 됩니다.

 

Koin Components

Koin은 모듈의 정의 및 설명 그리고 컨테이너를 정의하는데 도움이 되는 DSL입니다.

이제 필요한 것은 컨테이너 외부에서 인스턴스를 검색하는 API이고 이것이 Koin 컴포넌트의 목표입니다.

Koin Component 생성

클래스에 Koin 기능을 사용할 수 있도록 제공하려면 KoinComponent 인터페이스로 태그를 지정해야합니다.

MyService 인스턴스를 정의하는 모듈을 예를 들면 아래와 같습니다.

class MyService
val myModule = module {
    // Define a singleton for MyService
    single { MyService() }
}

위의 정의를 사용하기 전에 아래와 같이 koin을 실행해주어야 합니다.

fun main(vararg args : String){
    // Start Koin
    startKoin {
        modules(myModule)
    }
    // Create MyComponent instance and inject from Koin container
    MyComponent()
}

아래는 Koin 컨테이너에서 인스턴스를 검색하기 위해 MyComponent를 작성하는 방법으로 get() & inject()를 사용하여 MyService 인스턴스를 inject합니다.

class MyComponent : KoinComponent {
    // lazy inject Koin instance
    val myService : MyService by inject()
    // or
    // eager inject Koin instance
    val myService : MyService = get()
}

Retrieving definitions with get & inject

Koin은 Koin 컨테이너에서 인스턴스를 검색하는 두 가지 방법을 제공합니다.

  • val t : T by inject() -> lazy evaluated delegated instance
  • val t : T = get ()- 직접 엑세스로 생성된 인스턴스
// is lazy evaluated
val myService : MyService by inject()


// retrieve directly the instance
val myService : MyService get()

name으로 인스턴스 생성하기

필요한 경우 get() or inject()를 사용하여 아래 예시와 같이 다음 매개 변수를 지정할 수 있습니다.

  * qualifier -> name of the definition (definition에 지정된 name 파라미터인 경우)

val module = module {
    single(named("A")) { ComponentA() }
    single(named("B")) { ComponentB(get()) }
}
class ComponentA
class ComponentB(val componentA: ComponentA)
// retrieve from given module
val a = get<ComponentA>(named("A"))

API에 get() or inject()가 없는 경우

API를 사용하고 있고 그 안에 Koin을 사용하려면 KoinComponent 인터페이스로 원하는 클래스에 태그를 지정해야 합니다.

 

Injecting Parameters

모든 정의에서 injection parameter들을 사용할 수 있습니다.

 

Injection parameter 정의하기

아래는 Injection parameter의 예입니다.

아래 예시를 통해 Presenter 클래스를 빌드하려면 view parameter가 필요하다는 것을 알 수 있습니다.

class Presenter(val view : View)
val myModule = module {
    single{ (view : View) -> Presenter(view) }
}

values로 injecting

get()과 달리 Injection parameters는 resolution API 통해 전달되는 매개변수입니다.

이는 해당 매개 변수가 parametersOf 함수와 함께 get() 및 inject()로 전달 된 값임을 의미합니다.

class MyComponent : View, KoinComponent {
    // inject this as View value
    val presenter : Presenter by inject { parametersOf(this) }
}

Multiple parameters

정의에 여러 개의 parameter를 갖고 싶다면 비구조화 된 선언을 사용하여 매개 변수를 나열 할 수 있습니다.

class Presenter(val view : View, id : String)
val myModule = module {
    single{ (view : View, id : String) -> Presenter(view,id) }
}

KoinComponent에서는 아래와 같은 인수와 함께 parametersOf 함수를 사용합니다.

class MyComponent : View, KoinComponent {
    val id : String ...
    // inject with view & id
    val presenter : Presenter by inject { parametersOf(this,id) }
}

 

Setters Injections

Setters Injection은 실험적인 기능으로 정식 지원 기능은 아닙니다.

inject() lazy delegate expression을 사용하여 원하는 종속성을 검색하는 대신 속성을 직접 주입 할 수 있습니다.

Injecting Property

아래와 같은 속성을 가지는 class Injection을 예를 들면,

class B
class C
class A {
    lateinit var b: B
    lateinit var c: C
}

inject() 함수를 사용하여 properties에 injection이 가능합니다.

val a : A = A()




// inject properties
a::b.inject()
a::c.inject()

모든 속성을 한번에 Injection

다른 방법으로 아래와 같이 inject() 함수를 사용하여 injection이 가능합니다.

val a : A = A()




// inject all properties
a.inject(a::b, a::c)

 

Scope API

Koin은 lifecycle이 제한되어있는 인스턴스를 정의 할 수있는 간단한 API를 제공합니다.

Scope란?

Scope는 정해진 기간 또는 method 호출 주기에 맞게 Object가 존재하는 것으로 Scope 컨텍스트가 종료되면 컨테이너에서 삭제되므로 해당 Scope 아래에 바인딩된 객체를 다시 Inject 할 수 없습니다.

Scope Definition

Koin에는 기본적으로 아래와 같이 3가지 종류의 scope가 있습니다.

  • single: 전체 컨테이너 lifetime으로 지속되는 개체를 만듭니다. (삭제할 수 없음).
  • factory: 매번 새 개체를 만든다. 짧은 생명 주기. 컨테이너에 지속성이 없으므로 공유할 수 없습니다.
  • scope: 연결된 범위 수명에 지속적으로 연결된 개체를 만듭니다.

Scope definition을 선언하려면 아래와 같은 Scope 함수를 사용해야 합니다.

module {
    scope(named("A Scope Name")){
        scoped { Presenter() }
        // ...
    }
}

Scope name을 지정하려면 한정자가 필요하며, name은 String 한정자이거나 Type 한정자 일 수 있습니다.

module {
    scope(named<MyType>()){
        scoped { Presenter() }
        // ...
    }
}


// or




module {
    scope<MyType>{
        scoped { Presenter() }
        // ...
    }
}

Scope 사용하기

아래와 같은 class들을 사용할 때

class A

class B

class C

 

A, B & C 인스턴스의 B & C 인스턴스 범위를 A 인스턴스와 연결하면 아래와 같습니다.

module {
    single { A() }
    scope<A> {
        scoped { B() }
        scoped { C() }
    }
}

 

아래는 Scope를 생성하고 종석성을 검색하는 방법입니다.

// Get A from Koin's main scope
val a : A = koin.get<A>()


// Get/Create Scope for `a`
val scopeForA = a.getOrCreateScope()




// Get scoped instances from `a`
val b = scopeForA.get<B>()
val c = scopeForA.get<C>()

getOrCreateScope 함수를 사용할 수 있는데, 이 함수는 타입에 의해 정의 된 Scope를 만듭니다.

여기서 scopeForA는 인스턴스 객체에 연결되어 있습니다.

 

Scope 속성 사용방법은 아래와 같습니다.

// Get A from Koin's main scope
val a : A = koin.get<A>()


// Get scoped instances from `a`
val b = a.scope.get<B>()
val c = a.scope.get<C>()

* scope 사용 주의 사항:  Android 수명주기와 같은 특수 구성 요소에 대해 범위를 사용하려는 경우, Android 수명주기에 연결된 범위입니다.

 

closeScope 함수를 사용하여 현재 scope 및 관련 scope가 지정된 인스턴스 삭제가 가능합니다.

// Destroy `a` scope & drop `b` & `c`
a.closeScope()

Scope API 더 알아보기

Scope 작업하기

Scope 인스턴스는 “val scope = koin.createScope(id, qualifier)”와 같이 만들 수 있습니다. 

id는 Scope Id이고 qualifier는 Scope 한정자입니다.

Scope를 사용하여 종속성을 해결하려면 다음과 같이 처리합니다.

val presenter = scope.get <Presenter>() // -> Scope 인스턴스에서 get / inject 함수를 직접 사용

 

생성 및 Scope 검색

 

  • createScope(id : ScopeID, scopeName : Qualifier) -> 주어진 id 및 scopeName으로 닫힌 Scope 인스턴스를 만든다.
  • getScope(id : ScopeID) -> 지정된 ID로 이전에 생성된 Scope를 가져온다.
  • getOrCreateScope(id : ScopeID, scopeName : Qualifier) -> 제공된 id로 이미 생성된 Scope를 가져오거나 생성되지 않은 경우, 주어진 id 및 scopeName으로 닫힌 Scope 인스턴스를 생성한다.

* 주의: 모든 Scope에서 Scope를 얻기 위한 위한 ID 인 Scope 인스턴스 ID와 연결된 Scope 그룹 이름에 대한 참조 인 Scope 이름을 구별해야 합니다.

 

Scope 종속성 해결하기

// given the classes
class ComponentA
class ComponentB(val a : ComponentA)
// module with scope
module {
    scope(named("A_SCOPE_NAME")){
        scoped { ComponentA() }
        // will resolve from current scope instance
        scoped { ComponentB(get()) }
    }
}

위와 같은 의존성은 아래와 같이 해결 할 수 있습니다.

// create an closed scope instance "myScope1" for scope "A_SCOPE_NAME"
val myScope1 = koin.createScope("myScope1",named("A_SCOPE_NAME"))


// from the same scope
val componentA = myScope1.get<ComponentA>()
val componentB = myScope1.get<ComponentB>()

* 주의: scope에 대한 정의가 없는 경우, 기본적으로 모든 Scope의 fallback은 main scope에서 해결됩니다.


Scope 닫기

Scope 인스턴스가 완료되면 close() 함수를 사용하여 닫을 수 있습니다.

* 주의: close가 호출된 scope에 대해서는 더이상 인스턴스를 생성할 수 없습니다.

// from a KoinComponent
val session = getKoin().createScope("session")


// use it ...
// close it
session.close()

Logging

Koin에는 Koin Activity(Allocation, lookup…)를 기록하는 간단한 로깅 API를 제공하며 로깅 API는 아래 클래스로 표시됩니다.

// Koin Logger
abstract class Logger(var level: Level = Level.INFO) {
    abstract fun log(level: Level, msg: MESSAGE)
    fun debug(msg: MESSAGE) {
        log(Level.DEBUG, msg)
    }
    fun info(msg: MESSAGE) {
        log(Level.INFO, msg)
    }
    fun error(msg: MESSAGE) {
        log(Level.ERROR, msg)
    }
}

Koin은 타겟 플랫폼의 기능에 따라 몇 가지 로깅 구현을 제안합니다.

  • PrintLogger - 콘솔에 직접 로그인 (koin-core에 포함)
  • EmptyLogger - 아무것도 기록하지 않음 (koin-core에 포함)
  • SLF4JLogger - SLF4J로 로그인. ktor 및 spark에서 사용 (koin-logger-slf4j project)
  • AndroidLogger - Android Logger로 로그 기록 (koin-android에 포함)

시작 시 logging 설정하기

Koin은 기본적으로 EmptyLogger를 사용하며, PrintLogger 함수를 사용해 로깅 레벨을 정할 수 있습니다.

startKoin{
    logger(LEVEL.INFO)
}

 

Properties

Koin은 환경 또는 외부 속성 파일의 속성을 처리하여 정의에 값을 inject 할 수 있습니다.

시작 시 Properties 로딩하기

시작할 때 여러 type의 속성을 로드할 수 있습니다.

  • environment properties -> 시스템 속성 로드
  • koin.properties 파일 -> /src/main/resources/koin.properties 파일에서 속성 로드
  • “extra” start properties -> startKoin 함수에서 전달 된 값의 맵

module에서 속성 읽어오기

koin module에서 Key(/src/main/resoucres/koin.properties 파일에서)로 속성을 얻을 수 있습니다.

// Key - value
server_url=http://service_url
val myModule = module {
    // use the "server_url" key to retrieve its value
    single { MyService(getProperty("server_url")) }
}

 

Comments