$30 off During Our Annual Pro Sale. View Details »

DIY Dependency Injection with Kotlin @ DevFest Florida 2019

DIY Dependency Injection with Kotlin @ DevFest Florida 2019

Talk Description: Dependency Injection seems like this SUPER complex topic that only the most senior developers understand. At the end of the day, it’s just “inversion of control” where instances of objects/classes are provided to you, instead of created by you. Kotlin’s language features allow us to do this concisely, without a library, and in this talk I’ll show you how to DO IT YOURSELF. This talk will demystify dependency injection and help you understand the huge benefits it provides for testing, maintainability and modularity.

NOTE: There is a lot of context you may be missing if you don't watch the recording of the talk. It is not available yet though as of 01/23/2019.

Sam Edwards

January 19, 2019
Tweet

More Decks by Sam Edwards

Other Decks in Programming

Transcript

  1. @HandstandSam #DevFestFL
    DIY Dependency
    Injection with Kotlin
    Sam Edwards -

    View Slide

  2. @HandstandSam #DevFestFL
    Why am I talking about DIY Dependency Injection with Kotlin?

    View Slide

  3. @HandstandSam #DevFestFL
    Why am I talking about DIY Dependency Injection with Kotlin?
    ! Share the story of how we implemented DIY

    View Slide

  4. @HandstandSam #DevFestFL
    Why am I talking about DIY Dependency Injection with Kotlin?
    ! Share the story of how we implemented DIY
    ! Demystify the topic of Dependency Injection

    View Slide

  5. @HandstandSam #DevFestFL
    Why am I talking about DIY Dependency Injection with Kotlin?
    ! Share the story of how we implemented DIY
    ! Demystify the topic of Dependency Injection
    ! Show how you can implement DIY, if you choose

    View Slide

  6. @HandstandSam #DevFestFL
    Our Story

    View Slide

  7. @HandstandSam #DevFestFL
    Our Story
    ! Our team previously built Capital One Wallet

    View Slide

  8. @HandstandSam #DevFestFL
    Our Story
    ! Our team previously built Capital One Wallet
    ! We got tasked to build a reusable SDK for Authentication

    View Slide

  9. @HandstandSam #DevFestFL
    Our Story
    ! Our team previously built Capital One Wallet
    ! We got tasked to build a reusable SDK for Authentication
    ! This was the first time we were building a complex SDK

    View Slide

  10. @HandstandSam #DevFestFL
    Product Requirements

    View Slide

  11. @HandstandSam #DevFestFL
    Product Requirements
    ! SDK will be used by different apps, in different codebases

    View Slide

  12. @HandstandSam #DevFestFL
    Product Requirements
    ! SDK will be used by different apps, in different codebases
    ! Should not assume clients will be using a 3rd party library

    View Slide

  13. @HandstandSam #DevFestFL
    Product Requirements
    ! SDK will be used by different apps, in different codebases
    ! Should not assume clients will be using a 3rd party library
    ! Stay lean and lightweight

    View Slide

  14. @HandstandSam #DevFestFL
    Product Requirements
    ! SDK will be used by different apps, in different codebases
    ! Should not assume clients will be using a 3rd party library
    ! Stay lean and lightweight
    ! Make the SDK configurable

    View Slide

  15. @HandstandSam #DevFestFL
    Technical Requirements

    View Slide

  16. @HandstandSam #DevFestFL
    Technical Requirements
    ! Reactive Programming

    View Slide

  17. @HandstandSam #DevFestFL
    Technical Requirements
    ! Reactive Programming
    ! Immutable Data

    View Slide

  18. @HandstandSam #DevFestFL
    Technical Requirements
    ! Reactive Programming
    ! Immutable Data
    ! Modular Code

    View Slide

  19. @HandstandSam #DevFestFL
    Technical Requirements
    ! Reactive Programming
    ! Immutable Data
    ! Modular Code
    ! Single Responsibility Code

    View Slide

  20. @HandstandSam #DevFestFL
    Technical Requirements
    ! Reactive Programming
    ! Immutable Data
    ! Modular Code
    ! Single Responsibility Code
    ! Well Tested Code

    View Slide

  21. @HandstandSam #DevFestFL
    Technical Requirements
    ! Reactive Programming
    ! Immutable Data
    ! Modular Code
    ! Single Responsibility Code
    ! Well Tested Code
    ! Avoid 3rd Party Libraries

    View Slide

  22. @HandstandSam #DevFestFL
    Technical Requirements
    ! Reactive Programming
    ! Immutable Data
    ! Modular Code
    ! Single Responsibility Code
    ! Well Tested Code
    ! Avoid 3rd Party Libraries
    ! 100% Kotlin

    View Slide

  23. @HandstandSam #DevFestFL
    Technical Requirements
    ! Reactive Programming
    ! Immutable Data
    ! Modular Code
    ! Single Responsibility Code
    ! Well Tested Code
    ! Avoid 3rd Party Libraries
    ! 100% Kotlin
    ! Dependency Injection

    View Slide

  24. @HandstandSam #DevFestFL
    Why 100% Kotlin?

    View Slide

  25. @HandstandSam #DevFestFL
    Why 100% Kotlin?
    makes coding easy and concise.

    View Slide

  26. @HandstandSam #DevFestFL
    Why 100% Kotlin?
    makes dependency injection easy and concise.

    View Slide

  27. @HandstandSam #DevFestFL
    Why Dependency Injection?

    View Slide

  28. @HandstandSam #DevFestFL
    Why Dependency Injection?
    ! Testing

    View Slide

  29. @HandstandSam #DevFestFL
    Why Dependency Injection?
    ! Testing
    ! Mock Flavors

    View Slide

  30. @HandstandSam #DevFestFL
    Why Dependency Injection?
    ! Testing
    ! Mock Flavors
    ! Custom Debug Features

    View Slide

  31. @HandstandSam #DevFestFL
    Why Dependency Injection?
    ! Testing
    ! Mock Flavors
    ! Custom Debug Features
    ! Environment Switching (Test vs Production Servers)

    View Slide

  32. @HandstandSam #DevFestFL
    Why Dependency Injection?
    ! Testing
    ! Mock Flavors
    ! Custom Debug Features
    ! Environment Switching (Test vs Production Servers)
    ! Promotes building more reusable code

    View Slide

  33. @HandstandSam #DevFestFL
    Why Dependency Injection?
    ! Testing
    ! Mock Flavors
    ! Custom Debug Features
    ! Environment Switching (Test vs Production Servers)
    ! Promotes building more reusable code
    ! Discourages use of static singletons

    View Slide

  34. @HandstandSam #DevFestFL
    Why Dependency Injection?
    ! Testing
    ! Mock Flavors
    ! Custom Debug Features
    ! Environment Switching (Test vs Production Servers)
    ! Promotes building more reusable code
    ! Discourages use of static singletons
    ! It enables all the really cool things I like to talk about

    View Slide

  35. @HandstandSam #DevFestFL
    It Enables Testing
    DevFest Florida

    View Slide

  36. @HandstandSam #DevFestFL
    It Enables Custom Debug Features
    Droidcon Boston

    View Slide

  37. @HandstandSam #DevFestFL
    It Enable Mock Flavors
    Droidcon NYC

    View Slide

  38. @HandstandSam #DevFestFL
    Dependency Injection is Not Magical

    View Slide

  39. @HandstandSam #DevFestFL
    Dependency Injection is Not Magical
    DI !=

    View Slide

  40. @HandstandSam #DevFestFL
    Dependency Injection is

    View Slide

  41. @HandstandSam #DevFestFL
    Dependency Injection is
    Inversion of Control

    View Slide

  42. @HandstandSam #DevFestFL
    Dependency Injection is
    Passing in dependencies

    instead of pre-defining them.
    Inversion of Control

    View Slide

  43. @HandstandSam #DevFestFL
    Dependency Injection is Inversion of Control
    Code
    execute
    Dependencies
    ( )

    View Slide

  44. @HandstandSam #DevFestFL
    Dependency Injection is Inversion of Control
    Code
    execute
    ( )

    Dependencies

    View Slide

  45. @HandstandSam #DevFestFL
    Without Dependency Injection
    class NetworkConfig(
    val fullUrl = "http://localhost:8080/"
    val isLocalhostServer = true
    }

    View Slide

  46. @HandstandSam #DevFestFL
    Setter Injection
    class NetworkConfig(
    var fullUrl: String,
    var isLocalhostServer: Boolean
    )
    val config = NetworkConfig()
    config.fullUrl = “http://localhost:8080/"
    config.isLocalhostServer = true

    View Slide

  47. @HandstandSam #DevFestFL
    Constructor Injection
    class NetworkConfig(
    val baseUrl: String,
    val port: Int
    ) {
    val fullUrl = "$baseUrl:$port/"
    val isLocalhostServer = baseUrl.contains("//localhost")
    }

    View Slide

  48. @HandstandSam #DevFestFL
    Commonalities: Inversion of Control
    ! Inversion of Control is passing in dependencies instead of pre-
    defining them.

    View Slide

  49. @HandstandSam #DevFestFL
    Aren’t Their Libraries That Do DI for You?
    ! Yes, let’s take a look.

    View Slide

  50. @HandstandSam #DevFestFL
    Open Source DI Libraries
    ! Dagger
    ! Toothpick
    ! Koin
    ! Kodein

    View Slide

  51. @HandstandSam #DevFestFL
    ! Dagger
    ! Toothpick
    ! Koin
    ! Kodein
    Code
    Generation
    YES
    YES
    NO
    NO

    View Slide

  52. @HandstandSam #DevFestFL
    ! Dagger
    ! Toothpick
    ! Koin
    ! Kodein
    Code
    Generation
    YES
    YES
    NO
    NO


    View Slide

  53. @HandstandSam #DevFestFL
    ! Dagger
    ! Toothpick
    ! Koin
    ! Kodein
    Code
    Generation
    YES
    YES
    NO
    NO
    Single

    Instance
    NO
    NO
    YES
    NO


    View Slide

  54. @HandstandSam #DevFestFL
    ! Dagger
    ! Toothpick
    ! Koin
    ! Kodein
    Code
    Generation
    YES
    YES
    NO
    NO
    Single

    Instance
    NO
    NO
    YES
    NO



    View Slide

  55. @HandstandSam #DevFestFL
    ! Dagger
    ! Toothpick
    ! Koin
    ! Kodein
    Code
    Generation
    YES
    YES
    NO
    NO
    Single

    Instance
    NO
    NO
    YES
    NO
    Available

    at the time
    YES
    YES
    YES
    NO



    View Slide

  56. @HandstandSam #DevFestFL
    ! Dagger
    ! Toothpick
    ! Koin
    ! Kodein
    Code
    Generation
    YES
    YES
    NO
    NO
    Single

    Instance
    NO
    NO
    YES
    NO
    Available

    at the time
    YES
    YES
    YES
    NO




    View Slide

  57. @HandstandSam #DevFestFL
    ! Dagger
    ! Toothpick
    ! Koin
    ! Kodein
    Code
    Generation
    YES
    YES
    NO
    NO
    Single

    Instance
    NO
    NO
    YES
    NO
    Available

    at the time
    YES
    YES
    YES
    NO
    Adds dependency
    on another library
    YES
    YES
    YES
    YES




    View Slide

  58. @HandstandSam #DevFestFL
    So, How Much Work is DIY?

    View Slide

  59. @HandstandSam #DevFestFL
    So, How Much Work is DIY?
    ! Not that much

    View Slide

  60. @HandstandSam #DevFestFL
    So, How Much Work is DIY?
    ! Not that much
    ! Slightly more verbose

    View Slide

  61. @HandstandSam #DevFestFL
    So, How Much Work is DIY?
    ! Not that much
    ! Slightly more verbose
    ! Have to manually build advanced DI features

    View Slide

  62. @HandstandSam #DevFestFL
    So, How Much Work is DIY?
    ! Not that much
    ! Slightly more verbose
    ! Have to manually build advanced DI features
    ! Great for simple use cases when you don’t already know a DI library
    really well

    View Slide

  63. @HandstandSam #DevFestFL
    ShoppingApp - Open Source on GitHub
    https:/
    /github.com/handstandsam/ShoppingApp

    View Slide

  64. @HandstandSam #DevFestFL
    Swapping Dependencies on a Graph

    View Slide

  65. @HandstandSam #DevFestFL
    Swapping Dependencies on a Graph
    ! Mock Server vs. Testing Server vs. Live (Production Server)

    View Slide

  66. @HandstandSam #DevFestFL
    Swapping Dependencies on a Graph
    ! Mock Server vs. Testing Server vs. Live (Production Server)
    ! Graph is where all your dependencies are configured.

    View Slide

  67. @HandstandSam #DevFestFL
    Swapping Dependencies on a Graph
    ! Mock Server vs. Testing Server vs. Live (Production Server)
    ! Graph is where all your dependencies are configured.
    ! Just swap out pieces of your graph you need to change.

    View Slide

  68. @HandstandSam #DevFestFL
    Swapping Dependencies on a Graph
    ! Mock Server vs. Testing Server vs. Live (Production Server)
    ! Graph is where all your dependencies are configured.
    ! Just swap out pieces of your graph you need to change.
    ! Or provide a brand new graph with the required fields.

    View Slide

  69. @HandstandSam #DevFestFL
    Network Graph
    interface NetworkGraph {
    val categoryRepo: CategoryRepo
    val itemRepo: ItemRepo
    val userRepo: UserRepo
    }

    View Slide

  70. @HandstandSam #DevFestFL
    Network Graph
    interface NetworkGraph {
    val categoryRepo: CategoryRepo
    val itemRepo: ItemRepo
    val userRepo: UserRepo
    }
    class NetworkGraphImpl(
    networkConfig: NetworkConfig
    ) : NetworkGraph {
    private val retrofit: Retrofit=
    Retrofit.Builder()
    .baseUrl(networkConfig.fullUrl)
    .addConverterFactory(moshiConverterFactory)
    .addCallAdapterFactory(rxJava2CallAdapterFactory)
    .client(okHttpClientBuilder.build())
    .build()
    private val shoppingService: ShoppingService =
    retrofit.create(ShoppingService::class.java)
    override val categoryRepo: CategoryRepo = NetworkCategoryRepo(shoppingService)
    override val itemRepo: ItemRepo = NetworkItemRepo(shoppingService)
    override val userRepo: UserRepo = NetworkUserRepo(shoppingService)
    }

    View Slide

  71. @HandstandSam #DevFestFL
    Network Graph
    interface NetworkGraph {
    val categoryRepo: CategoryRepo
    val itemRepo: ItemRepo
    val userRepo: UserRepo
    }
    class NetworkGraphImpl(
    networkConfig: NetworkConfig
    ) : NetworkGraph {
    private val retrofit: Retrofit=
    Retrofit.Builder()
    .baseUrl(networkConfig.fullUrl)
    .addConverterFactory(moshiConverterFactory)
    .addCallAdapterFactory(rxJava2CallAdapterFactory)
    .client(okHttpClientBuilder.build())
    .build()
    private val shoppingService: ShoppingService =
    retrofit.create(ShoppingService::class.java)
    override val categoryRepo: CategoryRepo = NetworkCategoryRepo(shoppingService)
    override val itemRepo: ItemRepo = NetworkItemRepo(shoppingService)
    override val userRepo: UserRepo = NetworkUserRepo(shoppingService)
    }

    View Slide

  72. @HandstandSam #DevFestFL
    Create Graphs of Dependencies
    • Similar to Dagger @Component
    OkHttpClient Services
    Retrofit
    NetworkGraph
    Url
    Repos

    View Slide

  73. @HandstandSam #DevFestFL
    Session Graph
    interface SessionGraph {
    val sessionManager: SessionManager
    val checkoutCart: CheckoutCart
    val userPreferences: UserPreferences
    }

    View Slide

  74. @HandstandSam #DevFestFL
    Session Graph interface SessionGraph {
    val sessionManager: SessionManager
    val checkoutCart: CheckoutCart
    val userPreferences: UserPreferences
    }
    class SessionGraphImpl(
    appContext: Context
    ) : SessionGraph {
    override val checkoutCart = CheckoutCart()
    override val userPreferences = UserPreferences(appContext)
    override val sessionManager = SessionManager(checkoutCart, userPreferences)
    }

    View Slide

  75. @HandstandSam #DevFestFL
    Multiple or Nested Graphs are
    AppGraph
    SessionGraph
    NetworkGraph

    View Slide

  76. @HandstandSam #DevFestFL
    Multiple or Nested Graphs are
    AppGraph
    SessionGraph
    NetworkGraph

    View Slide

  77. @HandstandSam #DevFestFL
    Multiple or Nested Graphs are
    AppGraph
    SessionGraph
    NetworkGraph
    ! Each module project needs their own graph

    View Slide

  78. @HandstandSam #DevFestFL
    Multiple or Nested Graphs are
    AppGraph
    SessionGraph
    NetworkGraph
    ! Each module project needs their own graph
    ! Each graph is similar to a Dagger @Component

    View Slide

  79. @HandstandSam #DevFestFL
    ! Use the `by lazy` language feature of Kotlin to delay initialization, but
    cache result.
    Leveraging `by lazy`

    View Slide

  80. @HandstandSam #DevFestFL
    ! Use the `by lazy` language feature of Kotlin to delay initialization, but
    cache result.
    Leveraging `by lazy`
    class AppGraph {
    val networkGraph by lazy NetworkGraph(config = config)
    }

    View Slide

  81. @HandstandSam #DevFestFL
    ! Create a factory with a lambda
    Factories

    View Slide

  82. @HandstandSam #DevFestFL
    ! Create a factory with a lambda
    Factories
    class AppGraph {
    val dbFactory: () -> Database =
    {Database(logger = logger, utils = utils)}
    }
    val db = dbFactory()

    View Slide

  83. @HandstandSam #DevFestFL
    Scoping

    View Slide

  84. @HandstandSam #DevFestFL
    ! Use a factory, but this is DIY, so you will have to manage scoping
    yourself.
    Scoping

    View Slide

  85. @HandstandSam #DevFestFL
    Setting up Your Graphs
    class MyApplication : Application {
    val graph by lazy { AppGraph(this) }
    override fun onCreate() {
    //Access Graph to Instantiate
    graph
    }
    }
    ! Create Graph during Application::onCreate() and attach to Application

    View Slide

  86. @HandstandSam #DevFestFL
    Setting up Your Graphs
    class MyApplication : Application {
    val graph by lazy { AppGraph(this) }
    override fun onCreate() {
    //Access Graph to Instantiate
    graph
    }
    }
    class MyApplication : Application {
    lateinit var graph: AppGraph
    override fun onCreate() {
    graph = AppGraph(this)
    }
    }
    or
    ! Create Graph during Application::onCreate() and attach to Application

    View Slide

  87. @HandstandSam #DevFestFL
    Supporting Graphs in Modules
    class AppGraph(context: Context) {
    init { instance = this }
    companion object {
    lateinit var instance: AppGraph
    }
    }

    View Slide

  88. @HandstandSam #DevFestFL
    ! Create a singleton since the module is unaware of the Application
    Supporting Graphs in Modules
    class AppGraph(context: Context) {
    init { instance = this }
    companion object {
    lateinit var instance: AppGraph
    }
    }

    View Slide

  89. @HandstandSam #DevFestFL
    Dependency Injection on Activities & Fragments

    View Slide

  90. @HandstandSam #DevFestFL
    ! Constructors of Application, Fragments, Activities & Services are
    called by the platform
    Dependency Injection on Activities & Fragments

    View Slide

  91. @HandstandSam #DevFestFL
    ! Constructors of Application, Fragments, Activities & Services are
    called by the platform
    ! You have to wait do do any injection until after the `onCreate`
    lifecycle method
    Dependency Injection on Activities & Fragments

    View Slide

  92. @HandstandSam #DevFestFL
    ! lateinit var, grabbing from Application
    Accessing Graph in Activities & Fragments
    class MyActivity : Activity() {
    lateinit private var graph: AppGraph
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    graph = (application as MyApplication).graph
    //Access your fields now
    graph.database
    }
    }

    View Slide

  93. @HandstandSam #DevFestFL
    ! Simplify using an extension function.
    Accessing Graph in Activities & Fragments
    class MyActivity : Activity() {
    lateinit private var graph: AppGraph
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    graph = application.graph
    }
    }
    fun Application.graph(): AppGraph{
    return (this as MyApplication).graph
    }

    View Slide

  94. @HandstandSam #DevFestFL
    ! OR use `by lazy` to defer access until the Activity has been created
    Accessing Graph in Activities & Fragments
    class MyActivity : Activity() {
    private val graph: AppGraph get() = application.graph()
    private val db: UserRepo by lazy {graph.networkGraph.userRepo}
    }
    fun Application.graph(): AppGraph{
    return (this as MyApplication).graph
    }

    View Slide

  95. @HandstandSam #DevFestFL
    Accessing Graph in Activities & Fragments
    class MyActivity : Activity() {
    private val graph: AppGraph get() = application.graph()
    private val db: UserRepo get() = graph.networkGraph.userRepo
    }
    fun Application.graph(): AppGraph{
    return (this as MyApplication).graph
    }

    View Slide

  96. @HandstandSam #DevFestFL
    ! OR use `get() =` to defer access until the Activity has been created
    Accessing Graph in Activities & Fragments
    class MyActivity : Activity() {
    private val graph: AppGraph get() = application.graph()
    private val db: UserRepo get() = graph.networkGraph.userRepo
    }
    fun Application.graph(): AppGraph{
    return (this as MyApplication).graph
    }

    View Slide

  97. @HandstandSam #DevFestFL
    Pro Tips

    View Slide

  98. @HandstandSam #DevFestFL
    Pro Tips
    ! Try and avoid `lateinit var` for injected values

    View Slide

  99. @HandstandSam #DevFestFL
    Pro Tips
    ! Try and avoid `lateinit var` for injected values
    ! `lateinit` can create runtime Exceptions

    View Slide

  100. @HandstandSam #DevFestFL
    Pro Tips
    ! Try and avoid `lateinit var` for injected values
    ! `lateinit` can create runtime Exceptions
    ! `var` creates mutable state

    View Slide

  101. @HandstandSam #DevFestFL
    Pro Tips
    ! Try and avoid `lateinit var` for injected values
    ! `lateinit` can create runtime Exceptions
    ! `var` creates mutable state
    ! `by lazy` create an anonymous class

    View Slide

  102. @HandstandSam #DevFestFL
    Pro Tips
    ! Try and avoid `lateinit var` for injected values
    ! `lateinit` can create runtime Exceptions
    ! `var` creates mutable state
    ! `by lazy` create an anonymous class
    ! You can use `get() =` if your data is an immutable singleton

    View Slide

  103. @HandstandSam #DevFestFL
    Accessing Dependencies from the Graph
    LoginPresenter(
    view = loginView,
    sessionManager = appGraph.sessionGraph.sessionManager,
    userRepo = appGraph.networkGraph.userRepo
    )

    View Slide

  104. @HandstandSam #DevFestFL
    Accessing Dependencies from the Graph
    private val sessionManager: SessionManager
    get() = graph.sessionGraph.sessionManager
    private val userRepo: UserRepo
    get() = graph.networkGraph.userRepo
    LoginPresenter(
    view = loginView,
    sessionManager = sessionManager,
    userRepo = userRepo
    )

    View Slide

  105. @HandstandSam #DevFestFL
    Key Takeaways

    View Slide

  106. @HandstandSam #DevFestFL
    Key Takeaways
    ! You don’t HAVE to use a Dependency Injection Library, Dagger

    View Slide

  107. @HandstandSam #DevFestFL
    Key Takeaways
    ! You don’t HAVE to use a Dependency Injection Library, Dagger
    ! There are no magic annotations. @Inject

    View Slide

  108. @HandstandSam #DevFestFL
    Key Takeaways
    ! You don’t HAVE to use a Dependency Injection Library, Dagger
    ! There are no magic annotations. @Inject
    ! Kotlin language features make this concise and straightforward

    View Slide

  109. @HandstandSam #DevFestFL
    Key Takeaways
    ! You don’t HAVE to use a Dependency Injection Library, Dagger
    ! There are no magic annotations. @Inject
    ! Kotlin language features make this concise and straightforward
    ! Dependency Injection is critical for any app

    View Slide

  110. @HandstandSam #DevFestFL
    Thank you.
    My team is hiring in DC, Richmond & San Francisco.

    View Slide