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

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 Why am I talking about DIY Dependency Injection

    with Kotlin? ! Share the story of how we implemented DIY
  2. @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
  3. @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
  4. @HandstandSam #DevFestFL Our Story ! Our team previously built Capital

    One Wallet ! We got tasked to build a reusable SDK for Authentication
  5. @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
  6. @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
  7. @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
  8. @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
  9. @HandstandSam #DevFestFL Technical Requirements ! Reactive Programming ! Immutable Data

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

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

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

    ! Modular Code ! Single Responsibility Code ! Well Tested Code ! Avoid 3rd Party Libraries ! 100% Kotlin ! Dependency Injection
  13. @HandstandSam #DevFestFL Why Dependency Injection? ! Testing ! Mock Flavors

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

    ! Custom Debug Features ! Environment Switching (Test vs Production Servers) ! Promotes building more reusable code
  15. @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
  16. @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
  17. @HandstandSam #DevFestFL Setter Injection class NetworkConfig( var fullUrl: String, var

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

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

    is passing in dependencies instead of pre- defining them.
  20. @HandstandSam #DevFestFL ! Dagger ! Toothpick ! Koin ! Kodein

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

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

    that much ! Slightly more verbose ! Have to manually build advanced DI features
  26. @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
  27. @HandstandSam #DevFestFL Swapping Dependencies on a Graph ! Mock Server

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

    vs. Testing Server vs. Live (Production Server) ! Graph is where all your dependencies are configured.
  29. @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.
  30. @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.
  31. @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) }
  32. @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) }
  33. @HandstandSam #DevFestFL Create Graphs of Dependencies • Similar to Dagger

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

    val checkoutCart: CheckoutCart val userPreferences: UserPreferences }
  35. @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) }
  36. @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
  37. @HandstandSam #DevFestFL ! Use the `by lazy` language feature of

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

    class AppGraph { val dbFactory: () -> Database = {Database(logger = logger, utils = utils)} } val db = dbFactory()
  40. @HandstandSam #DevFestFL ! Use a factory, but this is DIY,

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

    init { instance = this } companion object { lateinit var instance: AppGraph } }
  44. @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 } }
  45. @HandstandSam #DevFestFL ! Constructors of Application, Fragments, Activities & Services

    are called by the platform Dependency Injection on Activities & Fragments
  46. @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
  47. @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 } }
  48. @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 }
  49. @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 }
  50. @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 }
  51. @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 }
  52. @HandstandSam #DevFestFL Pro Tips ! Try and avoid `lateinit var`

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

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

    loginView, sessionManager = appGraph.sessionGraph.sessionManager, userRepo = appGraph.networkGraph.userRepo )
  57. @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 )
  58. @HandstandSam #DevFestFL Key Takeaways ! You don’t HAVE to use

    a Dependency Injection Library, Dagger ! There are no magic annotations. @Inject
  59. @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
  60. @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