Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dependency Injection with KODEIN

Dependency Injection with KODEIN

Presentation about Dependency Injection fundamentals and how to achieve DI in Kotlin-powered applications using the KODEIN framework.

Presentation given at

- Kotlin Meetup SP #02 (January/2018)

Ubiratan Soares

January 11, 2018
Tweet

More Decks by Ubiratan Soares

Other Decks in Programming

Transcript

  1. class Controller(private val source: DataSource) { fun doSomeWork() { val

    service = Service(ds) val formatter = OutputFormatter() render(formatter.format(service.getResults())) } }
  2. class Controller(private val source: DataSource) { fun doSomeWork() { val

    service = Service(ds) val formatter = OutputFormatter() render(formatter.format(service.getResults())) } }
  3. 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 { }
  4. 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 { }
  5. Enforcing DI means that any class with an available public

    constructor gets all its collaborators at instantiation time
  6. • 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
  7. 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"
  8. interface Logger { fun log(severity: Level, toLog : String) }

    class SL4JLogger : Logger { override fun log(severity: Level, toLog: String) { // Print to console } } class LogEntriesLogger : Logger { override fun log(severity: Level, toLog: String) { // Send to external logging service } }
  9. 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
  10. object Injector { val graph = Kodein.lazy { bind<Gson>() with

    singleton { Gson() } bind<OkHttpClient>() with singleton { OkHttpClient.Builder().build() } bind<RestCaller>() with provider { RestCaller(httpClient = instance()) } // More bindings ...
  11. object Injector { val graph = Kodein.lazy { bind<Gson>() with

    singleton { Gson() } bind<OkHttpClient>() with singleton { OkHttpClient.Builder().build() } bind<RestCaller>() with provider { RestCaller(httpClient = instance()) } // More bindings ... The inferred LazyKodein subtype represents the object graph
  12. object Injector { val graph = Kodein.lazy { bind<Gson>() with

    singleton { Gson() } bind<OkHttpClient>() with singleton { OkHttpClient.Builder().build() } bind<RestCaller>() with provider { RestCaller(httpClient = instance()) } // More bindings ... Instances will be created in a lazy way
  13. object Injector { val graph = Kodein.lazy { bind<Gson>() with

    singleton { Gson() } bind<OkHttpClient>() with singleton { OkHttpClient.Builder().build() } bind<RestCaller>() with provider { RestCaller(httpClient = instance()) } // More bindings ... Defines a binding, eg, a way to provide an object instance
  14. object Injector { val graph = Kodein.lazy { bind<Gson>() with

    singleton { Gson() } bind<OkHttpClient>() with singleton { OkHttpClient.Builder().build() } bind<RestCaller>() with provider { RestCaller(httpClient = instance()) } // More bindings ... Defines the lifecycle time - or scope - of this object
  15. object Injector { val graph = Kodein.lazy { bind<Gson>() with

    singleton { Gson() } bind<OkHttpClient>() with singleton { OkHttpClient.Builder().build() } bind<RestCaller>() with provider { RestCaller(httpClient = instance()) } // More bindings ... Pulls a dependency transitively from the graph, with a proper scope
  16. bind<APIGateway>() with provider { APIGateway( homeService = instance(), brokerService =

    instance(), walletService = instance() ) } You should use named parameters to clarify the dependencies that are being pulled transitively
  17. bind<Scheduler>(UITHREAD) with singleton { AndroidSchedulers.mainThread() } bind<Scheduler>(WORKER) with singleton {

    Schedulers.io() } bind<SomeBussinessRule>() with provider { SomeInfrastructure( storage = instance(), webService = instance(), worker = instance(WORKER) ) } companion object { val WORKER = "worker" val UITHREAD = "main" }
  18. You can bind multiple instances of same type using tags

    bind<Scheduler>(UITHREAD) with singleton { AndroidSchedulers.mainThread() } bind<Scheduler>(WORKER) with singleton { Schedulers.io() } bind<SomeBussinessRule>() with provider { SomeInfrastructure( storage = instance(), webService = instance(), worker = instance(WORKER) ) } companion object { val WORKER = "worker" val UITHREAD = "main" }
  19. bind<OkHttpClient>() with singleton { OkHttpClient.Builder().build() } bind<RestCaller>() with provider {

    RestCaller(httpClient = instance()) } This dependency will live as long the Kodein instance lives
  20. bind<OkHttpClient>() with singleton { OkHttpClient.Builder().build() } bind<RestCaller>() with provider {

    RestCaller(httpClient = instance()) } Each time this dependency will be required, a new instance will be created
  21. You may have a dependency bounded with a custom scope,

    eg, this dependency will live as long the provided object lives bind<Presenter>() with scopedSingleton(androidActivityScope) { Presenter( scheduler = instance(UITHREAD), view = it ) } The object itself is accessible at binding time
  22. fun main(args: Array<String>) { 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
  23. 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<Presenter>() // Do your work … }
  24. 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<Presenter>() // Do your work … } This interface defines an instance that can hold a dependency graph
  25. 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<Presenter>() // Do your work … } Provided by the Android support
  26. 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<Presenter>() // Do your work … } Provides the custom dependency scope
  27. 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
  28. 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
  29. UBIRATAN SOARES Computer Scientist by ICMC/USP Software Engineer, curious guy

    Google Developer Expert for Android Teacher, speaker, etc, etc