To inject or not to inject: Dependency Injection with Kotlin (Droidcon Nairobi)

To inject or not to inject: Dependency Injection with Kotlin (Droidcon Nairobi)

Dependency Injection via frameworks like Dagger was present in most modern Android projects.
But then Kotlin happened. And since then a bunch of new libraries like Koin or Kodein appeared. Developers are even writing articles about how to do DI without any framework.
Some argue that those don’t even offer real dependency injection. Let’s look at that argument and compare the approaches.
So, is there something wrong with Dagger & co. in Kotlin? Are they obsolete? What are the alternatives? Let’s dive in.

A8b79d304b5184e5a5b0a109590f6683?s=128

Danny Preussler

October 05, 2018
Tweet

Transcript

  1. 1.

    .droidconKe To Inject or not to Inject Dependency Injection with

    Kotlin Danny Preussler sporttotal.tv @PreusslerBerlin
  2. 2.

    about:me • Java developer since 2003 • Talks about testing

    and architecture on mobile since 2008 • Kotlin since 2016 • Google Developer Expert for Android and Kotlin • Worked for eBay, Groupon, Viacom
  3. 3.

    sporttotal.tv • We are streaming under-medialised sports • Looking for

    Backend/Frontend Lead (Berlin) • Looking for iOS Developer (Berlin) • Looking for Designer (Köln, Berlin)
  4. 12.

    @PreusslerBerlin class MainActivity : Activity() { private val tracker =

    GoogleAnalyticsTracker() override fun onCreate(state: Bundle?) { super.onCreate(state) setContentView(R.layout.activity_main) tracker.trackStarted() } }
  5. 13.

    @PreusslerBerlin class MainActivity : Activity() { private val tracker =

    GoogleAnalyticsTracker() override fun onCreate(state: Bundle?) { super.onCreate(state) setContentView(R.layout.activity_main) tracker.trackStarted() } } Not testable!
  6. 15.

    @PreusslerBerlin class MainActivity : Activity() { private val tracker =

    GoogleAnalyticsTracker() override fun onCreate(state: Bundle?) { super.onCreate(state) setContentView(R.layout.activity_main) tracker.trackStarted() } } Google Analytics is implementation detail Single Resposibility Not testable
  7. 18.

    Inversion of Control Common implementations: • Factory • Service Locator

    tracker = Locator.get(Tracker::class) • Dependency Injection
  8. 19.

    Inversion of Control Common implementations: • Factory • Service Locator

    • Dependency Injection @Inject val tracker: Tracker
  9. 21.

    @PreusslerBerlin Dagger2 • Dagger1 by Square, Dagger2 by Google •

    Most popular dependency injection library in Android • Very fast at runtime • Complete runtime validation
  10. 23.
  11. 24.
  12. 29.

    @PreusslerBerlin Problems of Dagger: • Complex • Hard learning curve

    • Lot of boilerplate • Not very good for testing @PreusslerBerlin
  13. 30.

    @PreusslerBerlin How can Kotlin help? • Lazy • Delegates •

    Type inference • Inlining (generic T) • Lambdas • Functional programming @PreusslerBerlin
  14. 37.

    @PreusslerBerlin Default arguments class PlayerViewModel( val schedulers: RxSchedulers = DefaultSchedulers())

    vs class PlayerViewModel( val schedulers: RxSchedulers) @Inject constructor(): this(DefaultSchedulers())
  15. 38.
  16. 39.
  17. 40.
  18. 48.

    @PreusslerBerlin Martin says: • both provide fundamental decoupling • In

    both: application code is independent of the concrete implementation
  19. 49.

    @PreusslerBerlin Martin says: important difference is about how implementation is

    provided • With service locator: the class asks for it • With injection there is no explicit request • With a Service Locator every user has a dependency to the locator
  20. 50.

    @PreusslerBerlin Martin says: So the decision between locator and injector

    depends on whether that dependency is a problem.
  21. 52.

    @PreusslerBerlin inline fun <reified T : Any> get(…): T =

    … resolveByClass(T::class, …) Behind by inject:
  22. 53.

    @PreusslerBerlin inline fun <reified T : Any> get(…): T =

    … resolveByClass(T::class, …) Behind by inject:
  23. 54.

    @PreusslerBerlin inline fun <reified T : Any> get(…): T =

    … resolveByClass(T::class, …) Behind by inject:
  24. 55.

    @PreusslerBerlin The android problem • Normally constructor injection preferred •

    On Android: framework classes need field/setter injection: Activity, Service, Fragment, Views Android central
  25. 62.

    @PreusslerBerlin The annotated contructor issue • @Inject to mark constructor

    is handy: class HomeViewModel @Inject constructor(val resources: Resources) • Violates separations of concerns • Violates clean architecture : DI should stay in outermost layer • Providers are the cleaner solution anyway (part of JSR330)
  26. 63.

    @PreusslerBerlin The annotated constructor issue • @Inject to mark constructor

    is handy: class HomeViewModel @Inject constructor(val resources: Resources) • Violates separations of concerns • Violates clean architecture : DI should stay in outermost layer • Providers are the cleaner solution anyway (part of JSR330)
  27. 64.

    @PreusslerBerlin The annotated contructor issue • @Inject to mark constructor

    is handy: class HomeViewModel @Inject constructor(val resources: Resources) • Violates separations of concerns • Violates clean architecture : DI should stay in outermost layer • Providers are the cleaner solution anyway (part of JSR330)
  28. 65.

    @PreusslerBerlin The annotated contructor issue • @Inject to mark constructor

    is handy: class HomeViewModel @Inject constructor(val resources: Resources) • Violates separations of concerns • Violates clean architecture : DI should stay in outermost layer • Providers are the cleaner solution anyway (part of JSR330)
  29. 71.

    @PreusslerBerlin Boilerplate • Manual dependency injection has zero fixed cost

    …scales poorly. • A service locator has some overhead in creating it, inserting the bindings, and then doing the lookups. • A dependency injector has a lot of fixed overhead in setting up an injector hierarchy (components in Dagger 2) and all the modules to provide bindings. https://www.reddit.com/r/androiddev/comments/8ch4cg/dagger2_vs_koin_for_dependency_injection/
  30. 72.

    @PreusslerBerlin Boilerplate • Manual dependency injection has zero fixed cost

    …scales poorly. • A service locator has some overhead in creating it, inserting the bindings, and then doing the lookups. • A dependency injector has a lot of fixed overhead in setting up an injector hierarchy (components in Dagger 2) and all the modules to provide bindings. https://www.reddit.com/r/androiddev/comments/8ch4cg/dagger2_vs_koin_for_dependency_injection/
  31. 73.

    @PreusslerBerlin Boilerplate • Manual dependency injection has zero fixed cost

    …scales poorly. • A service locator has some overhead in creating it, inserting the bindings, and then doing the lookups. • A dependency injector has a lot of fixed overhead in setting up an injector hierarchy (components in Dagger 2) and all the modules to provide bindings. https://www.reddit.com/r/androiddev/comments/8ch4cg/dagger2_vs_koin_for_dependency_injection/
  32. 74.

    @PreusslerBerlin Boilerplate • if you're going to write a serious

    app with hundreds of bindings and hundreds of injected types with a deep graph of types then you're better off with a proper injector that generates the code that you otherwise manually write worth Koin. https://www.reddit.com/r/androiddev/comments/8ch4cg/dagger2_vs_koin_for_dependency_injection/ only true when using @ constructor
  33. 75.

    @PreusslerBerlin Where are we? • Choice depends on scale •

    Hard dependency to SL framework • Performance comparable • Compile-time validation
  34. 76.

    @PreusslerBerlin Compile time validation? • Whose job is it to

    manage the nulls. The language? Or the programmer? • Defects are the fault of programmers. It is programmers who create defects – not languages. • And what is it that programmers are supposed to do to prevent defects? • TEST! https://blog.cleancoder.com/uncle-bob/2017/01/11/TheDarkPath.html
  35. 78.

    @PreusslerBerlin Tests • Koin has dryRun()to verify the module setup

    • Have a test spawning up all fragments/activities to see if breaking change was added
  36. 79.

    @PreusslerBerlin Access the real thing in test (Koin) dependencies {

    testImplementation 'org.koin:koin-test:1.0.0‘ } val preferences: Persistence by inject()
  37. 80.

    @PreusslerBerlin Override for tests (Koin) val resources = mock<Resources>() @BeforeEach

    fun setup() { StandAloneContext.loadKoinModules( module { factory(override = true) { resources } }) }
  38. 83.

    @PreusslerBerlin Tags/Names • Dagger/Toothpick: @Named(„api1“) • Koin: factory <Api>(" api1

    ") { …. } by inject (name = „api“) • Kodein bind<Api>(tag = “api1") with … by kodein.instance(tag = “api1")
  39. 84.

    @PreusslerBerlin Scoping • Toothpick Ktx: scope(SCOPE, { module { //

    bindings • Koin: module { scope(SCOPE) { // bindings }} • Kodein Kodein { bind<API>() with scoped(SCOPE)…
  40. 86.

    @PreusslerBerlin Other Features • Android module • Koin has direct

    support for architecture components • Multi modules support • Non JVM Kotlin projects Koin ✓ Kodein ✓
  41. 88.

    @PreusslerBerlin Transition • Depends on how well you hide the

    DI in your project • Replace all @inject with manual binding calls will be painful flickr.com/photos/anca_bejinaru/5234037866/
  42. 89.

    @PreusslerBerlin Transition • Depends on how well you hide the

    DI in your project • Replace all @inject with manual binding calls will be painful (could be a first step) flickr.com/photos/anca_bejinaru/5234037866/
  43. 90.

    @PreusslerBerlin Transition • Depends on how well you hide the

    DI in your project • Replace all @inject with manual binding calls will be painful • Kodein helps with reflection based javax.inject implementation flickr.com/photos/anca_bejinaru/5234037866/
  44. 91.

    @PreusslerBerlin Transition • Depends on how well you hide the

    DI in your project • Replace all @inject with manual binding calls will be painful • Kodein helps with reflection based javax.inject implementation (keeps your project running) flickr.com/photos/anca_bejinaru/5234037866/
  45. 92.

    @PreusslerBerlin Transition • Might end up in awkward readable syntax

    factory { HomeViewModel(get(), get(), get(), get(), get(), get()) } flickr.com/photos/anca_bejinaru/5234037866/
  46. 93.

    @PreusslerBerlin Transition • Might end up in awkward readable syntax

    • Actually a code smell • Reflective approach available now factory { build<HomeViewModel> } flickr.com/photos/anca_bejinaru/5234037866/
  47. 94.

    @PreusslerBerlin Sum up • Koin: • Pro: lightweight, easy •

    Contra: Limited • Kodein • Pro: Feature rich • Contra: Too many features • DI is simple concept, keep it simple!
  48. 95.

    @PreusslerBerlin Give it a try • Kodein https://github.com/Kodein-Framework/Kodein-DI • Koin

    https://github.com/Ekito/koin • Toothpick KTX https://github.com/sporttotal-tv/toothpick-kotlin-extensions
  49. 97.