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

Dependency Injection with KODEIN

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

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)

Avatar for Ubiratan Soares

Ubiratan Soares PRO

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