본문

180918(화) - Koin (Koin docs)

Koin


Koin docs


What is Koin?

- pragmatic lightweight DI framework for Kotlin developers

- written pure Kotlin

- using functional resolution only

- no proxy, no code generation, no reflection

- DSL : lightweight container and pragmatic API


Koin DSL

- module

create a Koin Module

- factory

provide a factory bean definition (new instance each time)

- single

provide a singleton bean definition (aliased as bean???)

- get

resolve component dependency

- bind

add type to bind for given bean definition


Resolving & injecting dependencies

- injection시, have to write constructor injection style.

- get() function 사용으로 resolve an instance

// Presenter <- Service
class Service()
class Controller(val view : View)

val myModule = module {

    // declare Service as single intance
    single { Service() }
    // declare Controller as single instance, resolving View instance with get()
    single { Controller(get()) }
}



Binding additional type

- in some cases, want to match several types for just one definition

// Service interface
interface Service{

    fun doSomething()
}

// Service Implementation
class ServiceImp() : Service{

    fun doSomething() { ... }
}


val myModule = module {

    // Will match types ServiceImp & Service
    single { ServiceImp() } bind Service::class
}



Naming

val myModule = module {
    single<Service>("default") { ServiceImpl() }
    single<Service>("test") { ServiceImpl() }
}

val service : Service by inject(name = "default")



Injecting parameter

- resolved dependencies(get())와는 다르게, parameters passed through the resolution API (parametersOf())

class Presenter(val view : View)

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


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



- multiple parameters

class Presenter(val view : View, id : String)

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


class MyComponent : View, KoinComponent {

    val id : String ...

    // inject with view & id
    val presenter : Presenter by inject { parametersOf(this,id) }
}



Flags

- createOnStart()

・ startKoin(...)은 automatically create flagged with createOnStart이므로 원하지 않는다면 false로 바꿔줄 것.

・ special time에 load some definition하고 싶다면, (background thread instead UI) desired components에서 just get/inject

// Start Koin modules
startKoin(listOf(myModuleA,myModuleB), createOnStart = false)


・ definition level

val myModuleA = module {

    single<Service> { ServiceImp() }
}

val myModuleB = module {

    // eager creation for this definition
    single<Service>(createOnStart=true) { TestServiceImp() }
}


・ module level

val myModuleA = module {

    single<Service> { ServiceImp() }
}

val myModuleB = module(createOnStart=true) {

    single<Service>{ TestServiceImp() }
}



Overriding a definition on module

- Koin은 overriding redefinition을 용납하지 않는다. 바로 throw exception할 것임

val myModuleA = module {

    single<Service> { ServiceImp() }
}

val myModuleB = module {

    single<Service> { TestServiceImp() }
}

// Will throw an BeanOverrideException
startKoin(listOf(myModuleA,myModuleB))


- 다만 override parameter를 사용하면 허용 가능

- order가 중요한데, must have overriding definitions in last module list

val myModuleA = module {

    single<Service> { ServiceImp() }
}

val myModuleB = module {

    // override for this definition
    single<Service>(override=true) { TestServiceImp() }
}


val myModuleA = module {

    single<Service> { ServiceImp() }
}

// Allow override for all definitions from module
val myModuleB = module(override=true) {

    single<Service> { TestServiceImp() }
}



Generic

- Koin은 generics type argument를 지원하지 않는다.

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


- 위와같은 definitions를 Koin은 이해하지 못하므로 name or location(module)로 구분해주어야 한다.

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



Create() function

- create<T>() function은 inject first constructor, type T instance.

- get(...) function없이 definition 가능

single<T>() == single { create<T>() }
factory<T>() == factory { create<T>() }


single<T> { R() } == single<T> { create<R>() }
factory<T>() { R() } == factory<T> { create<R>() }


// Presenter <- Service
class Service()
class Controller(val view : View)

val myModule = module {

    // declare Service as single intance
    single { Service() }
    // declare Controller as single instance, resolving View instance with get()
    single<Controller> { ControllerImpl(get()) }
}


// Presenter <- Service
class Service()
class Controller(val view : View)

val myModule = module {

    // declare Service as single intance
    single<Service>()
    // declare Controller as single instance, resolving View instance with get()
    single<Controller> { create<ControllerImpl>() }
}


- implementation type이 있고 want to resolve with a target type이면 singleBy 사용

module {
    singleBy<Target,Implementation>()
}



Namespaces

- path는 optional parameter이며 default value is root namespace

- class name을 module name으로 사용 가능

// definitions in / (root) namespace
val aRootModule = module { ... }

// definitions in /org/sample namespace
val sampleModule = module("org.sample") { ... }


// definitions in /org/sample/UserSession
val sampleModule = module(UserSession::class.moduleName) { ... }



Inner modules

- must specify a path

// definitions in / (root) namespace
val aModule = module {

    // definitions in /org/sample namespace
    module("org.sample") {

    }
}


val sampleModule = module("org.sample") { ... }

// is equivalent to
val sampleModule = module {
    module("org") {
        module("sample") {
            // ...
        }
    }
}



Implicit definitions naming

- Koin gives a default name to each definition, prefixing with module path

module {
    module("B") {
        single { ComponentA() }
        single { ComponentB(get()) }
    }

    module("C") {
        single { ComponentA() }
        single { ComponentC(get()) }
    }
}


[module] declare Single [name='B.ComponentA',class='org.koin.test.module.ImplicitNamingTest.ComponentA', path:'B']
[module] declare Single [name='B.ComponentB',class='org.koin.test.module.ImplicitNamingTest.ComponentB', path:'B']
[module] declare Single [name='C.ComponentA',class='org.koin.test.module.ImplicitNamingTest.ComponentA', path:'C']
[module] declare Single [name='C.ComponentC',class='org.koin.test.module.ImplicitNamingTest.ComponentC', path:'C']


get<ComponentA>(name = "B.ComponentA")



Linking definitions between modules

- can depend on definitions from other module

// 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()) }
}



Linking modules strategies

- definitions & modules 사이는 lazy

class Repository(val datasource : Datasource)
interface Datasource
class LocalDatasource() : Datasource
class RemoteDatasource() : Datasource


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

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

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


// Load Repository + Local Datasource definitions
startKoin(listOf(repositoryModule,localDatasourceModule))

// Load Repository + Remote Datasource definitions
startKoin(listOf(repositoryModule,remoteDatasourceModule))



Visibility rules

- simple : child modules can see parents(include own), but not inverse

- module에 path를 넣으면 다른 module에서 not visible

// definitions in /
val rootModule = module {
    single { ComponentA() }
}
// definitions in /org
val orgModule = module("org") {
    single { ComponentB(...) }
}
// definitions in /org/sample
val sampleModule = module("org.sample") {
    single { ComponentC(...) }
}
// definitions in /org/demo
val demoModule = module("org.demo") {
    single { ComponentD(...) }
}



Using Scopes

- limit lifetime instance 제공

- scope context ends이면, 해당 scope under bound의 모든 obejct는 cannot again injected and all dropped from container


- 3 kind of scopes

・ single

entire container lifetime (can't be dropped)

・ factory

create new object each time

・ scope

persistent to associated scope lifetime

module {
    scope("scope_id") { Presenter() }
}


module {
    scope("scope_id") { Presenter() }
}

// create a scope
val session = getKoin().createScope("scope_id")

// or get scope if already created before
val session = getKoin().getScope("scope_id")

// will return the same instance of Presenter until Scope 'scope_id' is closed
val presenter = get<Presenter>()


class Presenter(val userSession : UserSession)
module {
    // Shared user session data
    scope { UserSession() }
    // Inject UserSession instance from "session" Scope
    factory { Presenter(get())}
}



Closing a scope

// from a KoinComponent
val session = getKoin().createScope("session")
// will return the same instance of Presenter until 'session' is closed
val presenter = get<Presenter>()

// close it
session.close()
// instance of presenter has been dropped


registerScopeCallback(object : ScopeCallback{
        override fun onClose(id: String) {
            // scope id has been closed
        }
    })



Start the container

- startKoin 이 한번 called되면, Koin 은 모든 modules & definitions read

- options

・ logging

・ properties

- startKoin()은 can't more than once. instead use loadKoinModules(...)



Behind the start

- start koin을 하게되면 agnostic을 allows하는 StandAloneContext holder instance에서 KoinContext instance created.

- KoinContext는 all components(forms the Koin container) 를 수집

- stored in StandAloneContext holder



Stop Koin

- anywhere call function stopKoin()


Logging

interface Logger {
    /**
     * Normal log
     */
    fun log(msg : String)

    /**
     * Debug log
     */
    fun debug(msg : String)

    /**
     * Error log
     */
    fun err(msg : String)
}


startKoin(listof(...), logger = EmptyLogger())


- PrintLogger

directly log into console

- EmptyLogger

log nothing

- SLF4JLogger

used by ktor and spark

- AndroidLogger

log into Android logger



Properties

- koin.perperties file /src/main/resources/koin.properties

- extra - startKoin function

// Key - value
server_url=http://service_url


val myModule = module {

    // use the "server_url" key to retrieve its value
    single { MyService(getProperty("server_url")) }
}


class MyComponent : KoinComponent {

    fun doSomething(){

        // Read a Koin property
        val serviceUrl = getProperty("server_url")

        // Set a Koin property
        setProperty("isDebug",false)
    }
}



Components

- use Koin features를 하려면 need to tag == KoinComponent interface

class MyService

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


fun main(vararg args : String){
    // Start Koin
    startKoin(listOf(myModule))

    // Create MyComponent instance and inject from Koin container
    MyComponent()
}


class MyComponent : KoinComponent {

    // lazy inject Koin instance
    val myService : MyService by inject()

    // or
    // eager inject Koin instance
    val myService : MyService get()
}


- by inject()

lazy evaluated instance from Koin container

- get()

eager fetch instance from Koin container

- release

deprecated -> recommanded use scope

- getProperty() / setProperty()


// retrieve from given module
val a_b = get<ComponentA>(module = ComponentB::class.moduleName)
val a_c = get<ComponentA>(module = ComponentC::class.moduleName)


공유

댓글