본문
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)
'Mobile > DI' 카테고리의 다른 글
180911(화) - Koin (0) | 2018.09.11 |
---|---|
180423(월) - Dagger (User's Guide) (0) | 2018.04.23 |
170713(목) - Kotlin & Dagger2 crash (0) | 2017.07.13 |
170712(수) - Dependency Injection with Dagger2 (0) | 2017.07.12 |
170707(금) - android-architecture-todo-mvp-dagger (0) | 2017.07.07 |
댓글