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.

5701f31a8433a22ae736282de8d08cd6?s=128

Sam Edwards

January 19, 2019
Tweet

Transcript

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

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

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

    with Kotlin? ! Share the story of how we implemented DIY
  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
  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
  6. @HandstandSam #DevFestFL Our Story

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

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

    One Wallet ! We got tasked to build a reusable SDK for Authentication
  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
  10. @HandstandSam #DevFestFL Product Requirements

  11. @HandstandSam #DevFestFL Product Requirements ! SDK will be used by

    different apps, in different codebases
  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
  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
  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
  15. @HandstandSam #DevFestFL Technical Requirements

  16. @HandstandSam #DevFestFL Technical Requirements ! Reactive Programming

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

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

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

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

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

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

    ! Modular Code ! Single Responsibility Code ! Well Tested Code ! Avoid 3rd Party Libraries ! 100% Kotlin
  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
  24. @HandstandSam #DevFestFL Why 100% Kotlin?

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

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

    concise.
  27. @HandstandSam #DevFestFL Why Dependency Injection?

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

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

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

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

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

    ! Custom Debug Features ! Environment Switching (Test vs Production Servers) ! Promotes building more reusable code
  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
  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
  35. @HandstandSam #DevFestFL It Enables Testing DevFest Florida

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

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

  38. @HandstandSam #DevFestFL Dependency Injection is Not Magical

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

  40. @HandstandSam #DevFestFL Dependency Injection is

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

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

    pre-defining them. Inversion of Control
  43. @HandstandSam #DevFestFL Dependency Injection is Inversion of Control Code execute

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

    ( ) Dependencies
  45. @HandstandSam #DevFestFL Without Dependency Injection class NetworkConfig( val fullUrl =

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

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

    port: Int ) { val fullUrl = "$baseUrl:$port/" val isLocalhostServer = baseUrl.contains("//localhost") }
  48. @HandstandSam #DevFestFL Commonalities: Inversion of Control ! Inversion of Control

    is passing in dependencies instead of pre- defining them.
  49. @HandstandSam #DevFestFL Aren’t Their Libraries That Do DI for You?

    ! Yes, let’s take a look.
  50. @HandstandSam #DevFestFL Open Source DI Libraries ! Dagger ! Toothpick

    ! Koin ! Kodein
  51. @HandstandSam #DevFestFL ! Dagger ! Toothpick ! Koin ! Kodein

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

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

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

    Code Generation YES YES NO NO Single
 Instance NO NO YES NO ❌ ❌ ❌
  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 ❌ ❌ ❌
  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 ❌ ❌ ❌ ❌
  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 ❌ ❌ ❌ ❌
  58. @HandstandSam #DevFestFL So, How Much Work is DIY?

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

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

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

    that much ! Slightly more verbose ! Have to manually build advanced DI features
  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
  63. @HandstandSam #DevFestFL ShoppingApp - Open Source on GitHub https:/ /github.com/handstandsam/ShoppingApp

  64. @HandstandSam #DevFestFL Swapping Dependencies on a Graph

  65. @HandstandSam #DevFestFL Swapping Dependencies on a Graph ! Mock Server

    vs. Testing Server vs. Live (Production Server)
  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.
  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.
  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.
  69. @HandstandSam #DevFestFL Network Graph interface NetworkGraph { val categoryRepo: CategoryRepo

    val itemRepo: ItemRepo val userRepo: UserRepo }
  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) }
  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) }
  72. @HandstandSam #DevFestFL Create Graphs of Dependencies • Similar to Dagger

    @Component OkHttpClient Services Retrofit NetworkGraph Url Repos
  73. @HandstandSam #DevFestFL Session Graph interface SessionGraph { val sessionManager: SessionManager

    val checkoutCart: CheckoutCart val userPreferences: UserPreferences }
  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) }
  75. @HandstandSam #DevFestFL Multiple or Nested Graphs are AppGraph SessionGraph NetworkGraph

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

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

    ! Each module project needs their own graph
  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
  79. @HandstandSam #DevFestFL ! Use the `by lazy` language feature of

    Kotlin to delay initialization, but cache result. Leveraging `by lazy`
  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) }
  81. @HandstandSam #DevFestFL ! Create a factory with a lambda Factories

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

    class AppGraph { val dbFactory: () -> Database = {Database(logger = logger, utils = utils)} } val db = dbFactory()
  83. @HandstandSam #DevFestFL Scoping

  84. @HandstandSam #DevFestFL ! Use a factory, but this is DIY,

    so you will have to manage scoping yourself. Scoping
  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
  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
  87. @HandstandSam #DevFestFL Supporting Graphs in Modules class AppGraph(context: Context) {

    init { instance = this } companion object { lateinit var instance: AppGraph } }
  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 } }
  89. @HandstandSam #DevFestFL Dependency Injection on Activities & Fragments

  90. @HandstandSam #DevFestFL ! Constructors of Application, Fragments, Activities & Services

    are called by the platform Dependency Injection on Activities & Fragments
  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
  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 } }
  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 }
  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 }
  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 }
  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 }
  97. @HandstandSam #DevFestFL Pro Tips

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

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

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

    for injected values ! `lateinit` can create runtime Exceptions ! `var` creates mutable state
  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
  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
  103. @HandstandSam #DevFestFL Accessing Dependencies from the Graph LoginPresenter( view =

    loginView, sessionManager = appGraph.sessionGraph.sessionManager, userRepo = appGraph.networkGraph.userRepo )
  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 )
  105. @HandstandSam #DevFestFL Key Takeaways

  106. @HandstandSam #DevFestFL Key Takeaways ! You don’t HAVE to use

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

    a Dependency Injection Library, Dagger ! There are no magic annotations. @Inject
  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
  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
  110. @HandstandSam #DevFestFL Thank you. My team is hiring in DC,

    Richmond & San Francisco.