class Controller(private val source: DataSource) { fun doSomeWork() { val service = Service(ds) val formatter = OutputFormatter() render(formatter.format(service.getResults())) } }
class Controller(private val source: DataSource) { fun doSomeWork() { val service = Service(ds) val formatter = OutputFormatter() render(formatter.format(service.getResults())) } }
class Controller( private val service: Service, private val formatter: OutputFormatter) { fun doSomeWork() { val results = service.getResults() render(formatter.format(results)) } } class Service(private val source: DataSource) { } class DataSource( private val logger: Logger, private val tracker: ErrorTracker, // other dependencies ) class OutputFormatter { }
class Controller( private val service: Service, private val formatter: OutputFormatter) { fun doSomeWork() { val results = service.getResults() render(formatter.format(results)) } } class Service(private val source: DataSource) { } class DataSource( private val logger: Logger, private val tracker: ErrorTracker, // other dependencies ) class OutputFormatter { }
• Manual code setup of all objects creation and DAG relationships • Extra code needed for scoped- based dependencies • Reflection-free • No DI errors at runtime • Boilterplate • Semi automated code setup, driven by JSR330 annotations standard • Reflection-based (Guice, etc) or reflection-free (Dagger2) • Reflection-based solutions may crash at runtime due to dependency resolution errors • Less code than static factories Static Factories JSR330
The Dependency Inversion Principle (from SOLID) "High level policies should not depend directly on low-level details; instead, low-level details should depend on high level policies. To accomplish this, runtime dependencies can be decoupled from source code dependencies via a shared polymorphic abstraction"
OVERVIEW • 100% Kotlin dependency injection framework • Heavily based on Kotlin features : type system and lazy properties • Reflection-based • Mirrors some API style from Guice, but has its own style as well • Supports Kotlin-JVM, Android, JS and Kotlin-native
object Injector { val graph = Kodein.lazy { bind() with singleton { Gson() } bind() with singleton { OkHttpClient.Builder().build() } bind() with provider { RestCaller(httpClient = instance()) } // More bindings ... Instances will be created in a lazy way
object Injector { val graph = Kodein.lazy { bind() with singleton { Gson() } bind() with singleton { OkHttpClient.Builder().build() } bind() with provider { RestCaller(httpClient = instance()) } // More bindings ... Defines a binding, eg, a way to provide an object instance
object Injector { val graph = Kodein.lazy { bind() with singleton { Gson() } bind() with singleton { OkHttpClient.Builder().build() } bind() with provider { RestCaller(httpClient = instance()) } // More bindings ... Defines the lifecycle time - or scope - of this object
object Injector { val graph = Kodein.lazy { bind() with singleton { Gson() } bind() with singleton { OkHttpClient.Builder().build() } bind() with provider { RestCaller(httpClient = instance()) } // More bindings ... Pulls a dependency transitively from the graph, with a proper scope
bind() with provider { APIGateway( homeService = instance(), brokerService = instance(), walletService = instance() ) } You should use named parameters to clarify the dependencies that are being pulled transitively
You can bind multiple instances of same type using tags bind(UITHREAD) with singleton { AndroidSchedulers.mainThread() } bind(WORKER) with singleton { Schedulers.io() } bind() with provider { SomeInfrastructure( storage = instance(), webService = instance(), worker = instance(WORKER) ) } companion object { val WORKER = "worker" val UITHREAD = "main" }
bind() with singleton { OkHttpClient.Builder().build() } bind() with provider { RestCaller(httpClient = instance()) } This dependency will live as long the Kodein instance lives
bind() with singleton { OkHttpClient.Builder().build() } bind() with provider { RestCaller(httpClient = instance()) } Each time this dependency will be required, a new instance will be created
You may have a dependency bounded with a custom scope, eg, this dependency will live as long the provided object lives bind() with scopedSingleton(androidActivityScope) { Presenter( scheduler = instance(UITHREAD), view = it ) } The object itself is accessible at binding time
fun main(args: Array) { val gateway: APIGateway by Injector.graph.instance() gateway.start() } Our class holds the LazyKodein property No class reference needed to retrieve an instance from the graph
class App : Application(), KodeinAware { override val kodein by Injection(context = this).graph } class SomeActivity : AppCompatActivity(), SomeView { private val kodein by lazy { LazyKodein(appKodein) } private val presenter by kodein.with(this).instance() // Do your work … }
class App : Application(), KodeinAware { override val kodein by Injection(context = this).graph } class SomeActivity : AppCompatActivity(), SomeView { private val kodein by lazy { LazyKodein(appKodein) } private val presenter by kodein.with(this).instance() // Do your work … } This interface defines an instance that can hold a dependency graph
class App : Application(), KodeinAware { override val kodein by Injection(context = this).graph } class SomeActivity : AppCompatActivity(), SomeView { private val kodein by lazy { LazyKodein(appKodein) } private val presenter by kodein.with(this).instance() // Do your work … } Provided by the Android support
class App : Application(), KodeinAware { override val kodein by Injection(context = this).graph } class SomeActivity : AppCompatActivity(), SomeView { private val kodein by lazy { LazyKodein(appKodein) } private val presenter by kodein.with(this).instance() // Do your work … } Provides the custom dependency scope
MUCH MORE • More ways to provide and retrieve dependencies • More ways to define dependencies scopes • Modules support, including dependency overriding • Multibindings support • JSR330 inter-operability for injections • Great documentation
CONCLUSIONS • Kodein is a great Kotlin first-class DI framework • You dont have to follow any JSR330 conventions to define the object graph • Kodein has same pain-points that reflection-based DI framework have : not so fast when compared with reflection-free solutions, errors may occur at runtime due to dependencies resolution failures • Great alternative for DI on Android applications