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

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.

Danny Preussler

October 05, 2018
Tweet

More Decks by Danny Preussler

Other Decks in Programming

Transcript

  1. .droidconKe
    To Inject or not to Inject
    Dependency Injection with Kotlin
    Danny Preussler
    sporttotal.tv
    @PreusslerBerlin

    View Slide

  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

    View Slide

  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)

    View Slide

  4. @PreusslerBerlin

    View Slide

  5. @PreusslerBerlin

    View Slide

  6. Dependency Inversion
    Inversion of Control
    Dependency Injection
    ?
    ?
    ?
    Service Locator

    View Slide

  7. The Dependency Inversion Principle
    High level entities
    should not depend on
    low level details.

    View Slide

  8. Inversion of Control
    Who initiates a message
    Hollywood's Law:
    don't call me, I'll call you.

    View Slide

  9. Inversion of Control
    Stop using new

    View Slide

  10. What? Why?

    View Slide

  11. Inversion of Control
    Nothing says “implementation” more than
    the new keyword

    View Slide

  12. @PreusslerBerlin
    class MainActivity : Activity() {
    private val tracker = GoogleAnalyticsTracker()
    override fun onCreate(state: Bundle?) {
    super.onCreate(state)
    setContentView(R.layout.activity_main)
    tracker.trackStarted()
    }
    }

    View Slide

  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!

    View Slide

  14. @PreusslerBerlin
    A voice out of the dark:
    Inversion of Control!

    View Slide

  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

    View Slide

  16. Inversion of Control
    Common implementations:
    ● Factory
    ● Service Locator
    ● Dependency Injection

    View Slide

  17. Inversion of Control
    Common implementations:
    ● Factory
    tracker = Factory.createTracker()
    ● Service Locator
    ● Dependency Injection

    View Slide

  18. Inversion of Control
    Common implementations:
    ● Factory
    ● Service Locator
    tracker = Locator.get(Tracker::class)
    ● Dependency Injection

    View Slide

  19. Inversion of Control
    Common implementations:
    ● Factory
    ● Service Locator
    ● Dependency Injection
    @Inject
    val tracker: Tracker

    View Slide

  20. @PreusslerBerlin
    DI on Android

    View Slide

  21. @PreusslerBerlin
    Dagger2
    • Dagger1 by Square, Dagger2 by Google
    • Most popular dependency injection library in Android
    • Very fast at runtime
    • Complete runtime validation

    View Slide

  22. Why changing it then?

    View Slide

  23. View Slide

  24. View Slide

  25. @PreusslerBerlin
    Annotation processing

    View Slide

  26. @PreusslerBerlin
    @PreusslerBerlin

    View Slide

  27. @PreusslerBerlin
    Annotation processing with code generation
    •Build time impact
    @PreusslerBerlin

    View Slide

  28. @PreusslerBerlin
    Annotation processing
    •Came up to get around limitations of Java as language
    @PreusslerBerlin

    View Slide

  29. @PreusslerBerlin
    Problems of Dagger:
    • Complex
    • Hard learning curve
    • Lot of boilerplate
    • Not very good for testing
    @PreusslerBerlin

    View Slide

  30. @PreusslerBerlin
    How can Kotlin help?
    • Lazy
    • Delegates
    • Type inference
    • Inlining (generic T)
    • Lambdas
    • Functional programming
    @PreusslerBerlin

    View Slide

  31. @PreusslerBerlin
    Problems with Java DI and Kotlin

    View Slide

  32. @PreusslerBerlin
    Constructors
    class PlayerViewModel(
    playlistUsecase: PlaylistUsecase
    )

    View Slide

  33. @PreusslerBerlin
    Constructors
    class PlayerViewModel
    @Inject constructor(
    playlistUsecase: PlaylistUsecase
    )

    View Slide

  34. @PreusslerBerlin
    Constructors
    class PlayerViewModel(playlistUsecase: PlaylistUsecase) {}
    vs
    class PlayerViewModel
    @Inject constructor(playlistUsecase: PlaylistUsecase) {}

    View Slide

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

    View Slide

  36. @PreusslerBerlin
    Default arguments
    class PlayerViewModel(
    val schedulers: RxSchedulers
    )
    @Inject constructor():
    this(DefaultSchedulers())

    View Slide

  37. @PreusslerBerlin
    Default arguments
    class PlayerViewModel(
    val schedulers: RxSchedulers = DefaultSchedulers())
    vs
    class PlayerViewModel(
    val schedulers: RxSchedulers)
    @Inject constructor(): this(DefaultSchedulers())

    View Slide

  38. @PreusslerBerlin
    Fields
    private val viewModel:
    ViewModel by lazy {… }
    vs
    @Inject
    lateinit var viewModel: ViewModel

    View Slide

  39. @PreusslerBerlin
    Fields
    private val viewModel:
    ViewModel by lazy {… }
    vs
    @Inject
    lateinit var viewModel: ViewModel

    View Slide

  40. @PreusslerBerlin
    Fields
    private val viewModel:
    ViewModel by lazy {… }
    vs
    @Inject
    lateinit var viewModel: ViewModel

    View Slide

  41. @PreusslerBerlin
    &
    vs Java DI

    View Slide

  42. @PreusslerBerlin
    Injection (any Java DI)
    @Inject lateinit
    var binder : BinderFactory

    View Slide

  43. @PreusslerBerlin
    Injection (Kodein)
    val binder : BinderFactory
    by kodein.instance()

    View Slide

  44. @PreusslerBerlin
    Injection (Koin)
    val binder : BinderFactory
    by inject()

    View Slide

  45. @PreusslerBerlin
    Dependency Injection
    or
    Service Locator?

    View Slide

  46. @PreusslerBerlin
    Injection

    View Slide

  47. @PreusslerBerlin
    Service Locator

    View Slide

  48. @PreusslerBerlin
    Martin says:
    • both provide fundamental decoupling
    • In both: application code is independent of the concrete
    implementation

    View Slide

  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

    View Slide

  50. @PreusslerBerlin
    Martin says:
    So the decision between locator and injector depends
    on whether that dependency is a problem.

    View Slide

  51. @PreusslerBerlin
    Injection (Koin)
    val binder : BinderFactory
    by inject()

    View Slide

  52. @PreusslerBerlin
    inline fun get(…): T =

    resolveByClass(T::class, …)
    Behind by inject:

    View Slide

  53. @PreusslerBerlin
    inline fun get(…): T =

    resolveByClass(T::class, …)
    Behind by inject:

    View Slide

  54. @PreusslerBerlin
    inline fun get(…): T =

    resolveByClass(T::class, …)
    Behind by inject:

    View Slide

  55. @PreusslerBerlin
    The android problem
    • Normally constructor injection
    preferred
    • On Android: framework classes need
    field/setter injection:
    Activity, Service,
    Fragment, Views
    Android central

    View Slide

  56. @PreusslerBerlin
    Bindings

    View Slide

  57. @PreusslerBerlin
    Bindings (Dagger, Toothpick)
    bind(API::class.java)
    .to(DummyApi::class.java)
    bind(BinderFactory::class.java)
    .toInstance(ViewBindingFactory())

    View Slide

  58. @PreusslerBerlin
    Bindings (Toothpick Ktx)
    bindClass(DummyApi::class)
    bindInstance
    { ViewBindingFactory>() }

    View Slide

  59. @PreusslerBerlin
    Bindings (Kodein)
    bind()
    with provider { DummyApi() }
    bind()
    with singleton {
    ViewBinderFactory() }

    View Slide

  60. @PreusslerBerlin
    Bindings (Koin)
    factory{ DummyApi() }
    single{
    ViewBindingFactory()}

    View Slide

  61. @PreusslerBerlin
    Let’s talk about constructors

    View Slide

  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)

    View Slide

  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)

    View Slide

  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)

    View Slide

  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)

    View Slide

  66. @PreusslerBerlin
    Examples (Toothpick Ktx)
    bindProvider(HomeViewModelProvider::class)
    class HomeViewModelProvider
    @Inject constructor(val resources Resources)
    : Provider {
    override fun get() = HomeViewModel(resources)
    }

    View Slide

  67. @PreusslerBerlin
    Examples (Toothpick Ktx)
    bindProvider(HomeViewModelProvider::class)
    class HomeViewModelProvider
    @Inject constructor(val resources Resources)
    : Provider {
    override fun get() = HomeViewModel(resources)
    }

    View Slide

  68. @PreusslerBerlin
    Examples (Kodein)
    bind() with provider
    { HomeViewModel(instance()) }

    View Slide

  69. @PreusslerBerlin
    Examples (Koin)
    factory { HomeViewModel(get())}

    View Slide

  70. @PreusslerBerlin
    Should we change
    to Koin/Kodein?

    View Slide

  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/

    View Slide

  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/

    View Slide

  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/

    View Slide

  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

    View Slide

  75. @PreusslerBerlin
    Where are we?
    • Choice depends on scale
    • Hard dependency to SL framework
    • Performance comparable
    • Compile-time validation

    View Slide

  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

    View Slide

  77. @PreusslerBerlin
    Tests

    View Slide

  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

    View Slide

  79. @PreusslerBerlin
    Access the real thing in test (Koin)
    dependencies {
    testImplementation 'org.koin:koin-test:1.0.0‘
    }
    val preferences: Persistence by inject()

    View Slide

  80. @PreusslerBerlin
    Override for tests (Koin)
    val resources = mock()
    @BeforeEach
    fun setup() {
    StandAloneContext.loadKoinModules(
    module {
    factory(override = true) { resources }
    })
    }

    View Slide

  81. @PreusslerBerlin
    Override for tests (Koin)
    val resources by inject()
    @BeforeEach
    fun setup()
    declareMock()
    }

    View Slide

  82. @PreusslerBerlin
    Features you probably want to have

    View Slide

  83. @PreusslerBerlin
    Tags/Names
    • Dagger/Toothpick:
    @Named(„api1“)
    • Koin:
    factory (" api1 ") { …. }
    by inject (name = „api“)
    • Kodein
    bind(tag = “api1") with …
    by kodein.instance(tag = “api1")

    View Slide

  84. @PreusslerBerlin
    Scoping
    • Toothpick Ktx:
    scope(SCOPE, {
    module {
    // bindings
    • Koin:
    module {
    scope(SCOPE) {
    // bindings
    }}
    • Kodein
    Kodein {
    bind() with scoped(SCOPE)…

    View Slide

  85. @PreusslerBerlin
    Scoping
    • Toothpick Ktx:
    Toothpick.openScopes(APP_SCOPE, SCOPE).apply {
    installModules(createPlayerModule())
    }
    • Koin:
    by inject()
    • Kodein:
    by kodein.factory()

    View Slide

  86. @PreusslerBerlin
    Other Features
    • Android module
    • Koin has direct support for architecture components
    • Multi modules support
    • Non JVM Kotlin projects
    Koin ✓
    Kodein ✓

    View Slide

  87. @PreusslerBerlin
    Transition
    flickr.com/photos/anca_bejinaru/5234037866/

    View Slide

  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/

    View Slide

  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/

    View Slide

  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/

    View Slide

  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/

    View Slide

  92. @PreusslerBerlin
    Transition
    • Might end up in awkward readable syntax
    factory { HomeViewModel(get(), get(), get(), get(), get(), get()) }
    flickr.com/photos/anca_bejinaru/5234037866/

    View Slide

  93. @PreusslerBerlin
    Transition
    • Might end up in awkward readable syntax
    • Actually a code smell
    • Reflective approach available now
    factory { build }
    flickr.com/photos/anca_bejinaru/5234037866/

    View Slide

  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!

    View Slide

  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

    View Slide

  96. @PreusslerBerlin
    More
    • https://www.martinfowler.com/articles/injection.html
    • https://android.jlelse.eu/moving-from-dagger-to-koin-simplify-your-
    android-development-e8c61d80cddb
    • http://fragmentedpodcast.com/episodes/133/
    flickr.com/photos/[email protected]/40102368982

    View Slide

  97. To inject
    or not inject
    @PreusslerBerlin
    We are hiring
    iOS developer /Backend lead / Designer

    View Slide