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

The Imposter's Guide To Dependency Injection - DCSF22

Bc87ea9c7a0f85b8761b716a677c6694?s=47 Adam McNeilly
June 02, 2022
180

The Imposter's Guide To Dependency Injection - DCSF22

Bc87ea9c7a0f85b8761b716a677c6694?s=128

Adam McNeilly

June 02, 2022
Tweet

Transcript

  1. The Imposter's Guide To Dependency Injec6on Adam McNeilly - @AdamMc331

    @AdamMc331 #DCSF22 1
  2. Dependency Injec+on @AdamMc331 #DCSF22 2

  3. Dependency Injec+on • Two big words @AdamMc331 #DCSF22 2

  4. Dependency Injec+on • Two big words • Half a dozen

    libraries @AdamMc331 #DCSF22 2
  5. Dependency Injec+on • Two big words • Half a dozen

    libraries • Countless Twi6er wars @AdamMc331 #DCSF22 2
  6. Dependency Injec+on • Two big words • Half a dozen

    libraries • Countless Twi6er wars • Endless Confusion @AdamMc331 #DCSF22 2
  7. A Note About Imposter Syndrome1 1 h$ps:/ /hbr.org/2021/02/stop-telling-women-they-have-imposter-syndrome

  8. Let's Start At The Beginning @AdamMc331 #DCSF22 4

  9. Code Without DI In the following snippet, our class relies

    directly on FirebaseAnalytics. class ProfileViewModel() { fun onProfileLoaded() { Firebase.analytics.logEvent("viewed_profile") } } @AdamMc331 #DCSF22 5
  10. What Problems Can This Cause? @AdamMc331 #DCSF22 6

  11. Try Tes'ng It class ProfileViewModelTest { @Test fun verifyEventTracked() {

    val viewModel = ProfileViewModel() viewModel.onProfileLoaded() // No Ability To Verify Event Tracked } } @AdamMc331 #DCSF22 7
  12. It Also Crashes Caused by: java.lang.Run2meExcep2on: Method getMainLooper in android.os.Looper

    not mocked. @AdamMc331 #DCSF22 8
  13. We Have A Dependency On FirebaseAnaly1cs, Let's Inject It @AdamMc331

    #DCSF22 9
  14. Constructor Injec-on class ProfileViewModel( private val analytics: FirebaseAnalytics = Firebase.analytics,

    ) { fun onProfileLoaded() { analytics.logEvent("viewed_profile") } } @AdamMc331 #DCSF22 10
  15. Constructor Injec-on2 2 h$ps:/ /twi$er.com/KellyShuster/status/1020363593060093952 @AdamMc331 #DCSF22 11

  16. Updated Test class ProfileViewModelTest { @Test fun verifyEventTracked() { val

    mockAnalytics = FakeFirebaseAnalytics() val viewModel = ProfileViewModel(mockAnalytics) viewModel.onProfileLoaded() mockAnalytics.verifyEventLogged("viewed_profile") } } @AdamMc331 #DCSF22 12
  17. Wait, Should We Mock Firebase? @AdamMc331 #DCSF22 13

  18. Wait, Should We Mock Firebase? • We might change vendors

    in the future @AdamMc331 #DCSF22 13
  19. Wait, Should We Mock Firebase? • We might change vendors

    in the future • Is it Firebase we're tes9ng, or just that some event was tracked? @AdamMc331 #DCSF22 13
  20. We Should Create Our Own Interface interface AnalyticsTracker { fun

    trackEvent(eventName: String) } @AdamMc331 #DCSF22 14
  21. With Our Own Implementa1on class FirebaseAnalyticsTracker : AnalyticsTracker { override

    fun trackEvent(eventName: String) { Firebase.analytics.logEvent(eventName) } } @AdamMc331 #DCSF22 15
  22. Inject That Instead class ProfileViewModel( private val analytics: AnalyticsTracker =

    FirebaseAnalyticsTracker(), ) @AdamMc331 #DCSF22 16
  23. That's The Core Concept Of Dependency Injec6on! @AdamMc331 #DCSF22 17

  24. Check Out More3 3 h$ps:/ /speakerdeck.com/stkent/framework-free-dependency-injec:on-360-andev-2019 @AdamMc331 #DCSF22 18

  25. What Makes It More Complicated Than "Passing Stuff In"? @AdamMc331

    #DCSF22 19
  26. Sharing Dependencies @AdamMc331 #DCSF22 20

  27. Analy&cs Are Everywhere @AdamMc331 #DCSF22 21

  28. Analy&cs Are Everywhere • Let's create one AnalyticsTracker to use

    in each screen @AdamMc331 #DCSF22 21
  29. Analy&cs Are Everywhere • Let's create one AnalyticsTracker to use

    in each screen • Let's ensure that we can swap out that tracker once and have each screen updated @AdamMc331 #DCSF22 21
  30. Create A Dependency Container4 class AppDependencies { val analyticsTracker: AnalyticsTracker

    get() = FirebaseAnalyticsTracker() } 4 h$ps:/ /developer.android.com/training/dependency-injec:on/manual @AdamMc331 #DCSF22 22
  31. Instan&ate In Applica&on Class class MyApp : Application() { val

    appDependencies = AppDependencies() } @AdamMc331 #DCSF22 23
  32. Fetch Dependencies From Screens class ProfileActivity : Activity() { override

    fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val appDependencies = (this.application as MyApp).appDependencies val analyticsTracker = appDependencies.analyticsTracker val profileViewModel = ProfileViewModel(analyticsTracker) } } @AdamMc331 #DCSF22 24
  33. This Is The Manual Approach @AdamMc331 #DCSF22 25

  34. DI Libraries Help Reduce Boilerplate From The Manual Approach @AdamMc331

    #DCSF22 26
  35. They All Follow The Same Recipe @AdamMc331 #DCSF22 27

  36. They All Follow The Same Recipe • Create a collec+on

    of dependencies @AdamMc331 #DCSF22 27
  37. They All Follow The Same Recipe • Create a collec+on

    of dependencies • Configure your Applica+on class as the container for those dependencies @AdamMc331 #DCSF22 27
  38. They All Follow The Same Recipe • Create a collec+on

    of dependencies • Configure your Applica+on class as the container for those dependencies • Request dependencies as needed @AdamMc331 #DCSF22 27
  39. Hilt Example5 5 h$ps:/ /developer.android.com/training/dependency-injec:on/hilt-android @AdamMc331 #DCSF22 28

  40. Dependency Container @Module @InstallIn(ActivityComponent::class) abstract class AnalyticsModule { @Binds abstract

    fun bindAnalyticsTracker( analyticsTracker: FirebaseAnalyticsTracker, ): AnalyticsTracker } @AdamMc331 #DCSF22 29
  41. Dependency Container @Module @InstallIn(ActivityComponent::class) abstract class AnalyticsModule { @Binds abstract

    fun bindAnalyticsTracker( analyticsTracker: FirebaseAnalyticsTracker, ): AnalyticsTracker } @AdamMc331 #DCSF22 29
  42. Applica'on Class @HiltAndroidApp class MyApp : Application() @AdamMc331 #DCSF22 30

  43. Request Dependencies @AndroidEntryPoint class ProfileActivity : Activity() { @Inject lateinit

    var analyticsTracker: AnalyticsTracker } @AdamMc331 #DCSF22 31
  44. Koin Example6 6 h$ps:/ /insert-koin.io/docs/quickstart/android/ @AdamMc331 #DCSF22 32

  45. Dependency Container val analyticsModule = module { single<AnalyticsTracker> { FirebaseAnalyticsTracker()

    } } @AdamMc331 #DCSF22 33
  46. Dependency Container val analyticsModule = module { single<AnalyticsTracker> { FirebaseAnalyticsTracker()

    } } @AdamMc331 #DCSF22 33
  47. Applica'on Class class MyApp : Application() { override fun onCreate()

    { super.onCreate() startKoin { modules(analyticsModule) } } } @AdamMc331 #DCSF22 34
  48. Request Dependencies class ProfileActivity : Activity() { val analyticsTracker: AnalyticsTracker

    by inject() } @AdamMc331 #DCSF22 35
  49. Your DI Library Of Choice Ma4ers Less Than Why You

    Need It @AdamMc331 #DCSF22 36
  50. Dependency Management Enables Several Benefits @AdamMc331 #DCSF22 37

  51. Dependency Management Enables Several Benefits • Unit test support @AdamMc331

    #DCSF22 37
  52. Dependency Management Enables Several Benefits • Unit test support •

    Ability to change dependencies @AdamMc331 #DCSF22 37
  53. Dependency Management Enables Several Benefits • Unit test support •

    Ability to change dependencies • Dependencies can be shared @AdamMc331 #DCSF22 37
  54. Dependency Management Enables Several Benefits • Unit test support •

    Ability to change dependencies • Dependencies can be shared • One centralized configura:on of all dependencies @AdamMc331 #DCSF22 37
  55. A Li%le Bit More @AdamMc331 #DCSF22 38

  56. Scoping @AdamMc331 #DCSF22 39

  57. Applica'on Scoped Some dependencies should be maintained as a single

    instance for our en5re applica5on lifecycle. This can be true for database connec5ons, error reporters, analy5cs trackers, and similar far reaching func5onality. @AdamMc331 #DCSF22 40
  58. Component Scoped Some dependencies should only be maintained as long

    as the components using them require it. For example, a presenter only needs to stay in memory if the screen using it is visible or in the backstack, or a cache layer only for a specific screen. @AdamMc331 #DCSF22 41
  59. Library Differences How we manage scoping is different depending on

    the DI framework we choose, but in advanced situa;ons we should be aware of memory management and ensuring we either share or create new dependencies as necessary. @AdamMc331 #DCSF22 42
  60. What To Consider @AdamMc331 #DCSF22 43

  61. Manual Approach @AdamMc331 #DCSF22 44

  62. Manual Approach • Pro: No "magic" @AdamMc331 #DCSF22 44

  63. Manual Approach • Pro: No "magic" • Pro: Ability to

    use "find usages" @AdamMc331 #DCSF22 44
  64. Manual Approach • Pro: No "magic" • Pro: Ability to

    use "find usages" • Pro: Easy to refactor with IDE tooling @AdamMc331 #DCSF22 44
  65. Manual Approach • Pro: No "magic" • Pro: Ability to

    use "find usages" • Pro: Easy to refactor with IDE tooling • Con: A lot of code to write and maintain ourselves @AdamMc331 #DCSF22 44
  66. Manual Approach • Pro: No "magic" • Pro: Ability to

    use "find usages" • Pro: Easy to refactor with IDE tooling • Con: A lot of code to write and maintain ourselves • Con: ReinvenBng the wheel @AdamMc331 #DCSF22 44
  67. DI Library @AdamMc331 #DCSF22 45

  68. DI Library • Pro: Reduced boilerplate code @AdamMc331 #DCSF22 45

  69. DI Library • Pro: Reduced boilerplate code • Pro: Quick

    to get started @AdamMc331 #DCSF22 45
  70. DI Library • Pro: Reduced boilerplate code • Pro: Quick

    to get started • Pro: More complex situa9ons like scoping are solved for us @AdamMc331 #DCSF22 45
  71. DI Library • Pro: Reduced boilerplate code • Pro: Quick

    to get started • Pro: More complex situa9ons like scoping are solved for us • Con: Can feel like "magic" @AdamMc331 #DCSF22 45
  72. DI Library • Pro: Reduced boilerplate code • Pro: Quick

    to get started • Pro: More complex situa9ons like scoping are solved for us • Con: Can feel like "magic" • Con: Harder to trace usages of dependencies @AdamMc331 #DCSF22 45
  73. DI Library • Pro: Reduced boilerplate code • Pro: Quick

    to get started • Pro: More complex situa9ons like scoping are solved for us • Con: Can feel like "magic" • Con: Harder to trace usages of dependencies • Con: Requires knowledge of DI as a concept and the tool used @AdamMc331 #DCSF22 45
  74. Library Considera.ons @AdamMc331 #DCSF22 46

  75. Library Considera.ons • Some DI libraries can verify and manage

    dependencies at compile 8me @AdamMc331 #DCSF22 46
  76. Library Considera.ons • Some DI libraries can verify and manage

    dependencies at compile 8me • This can lead to increased build 8mes as complexity grows @AdamMc331 #DCSF22 46
  77. Library Considera.ons • Some DI libraries can verify and manage

    dependencies at compile 8me • This can lead to increased build 8mes as complexity grows • Some DI libraries use a service locator concept to fetch dependencies at run8me @AdamMc331 #DCSF22 46
  78. Library Considera.ons • Some DI libraries can verify and manage

    dependencies at compile 8me • This can lead to increased build 8mes as complexity grows • Some DI libraries use a service locator concept to fetch dependencies at run8me • This keeps builds consistent, but can lead to unexpected errors at run8me if our dependency graph is broken @AdamMc331 #DCSF22 46
  79. Trust Yourself Whether you choose a manual DI approach or

    one of the libraries out there will be up to you and your team. Chose what you are comfortable with, or what you think provides the most benefit to the size and complexity of your project. @AdamMc331 #DCSF22 47
  80. Remember The Fundamentals @AdamMc331 #DCSF22 48

  81. Remember The Fundamentals • The fundamental concepts of dependency injec5on

    provide support for be9er tes5ng, increasing our confidence in our products. @AdamMc331 #DCSF22 48
  82. Remember The Fundamentals • The fundamental concepts of dependency injec5on

    provide support for be9er tes5ng, increasing our confidence in our products. • They also provide flexibility, and enable us to make broad changes to our product quickly. @AdamMc331 #DCSF22 48
  83. Remember The Fundamentals • The fundamental concepts of dependency injec5on

    provide support for be9er tes5ng, increasing our confidence in our products. • They also provide flexibility, and enable us to make broad changes to our product quickly. • Having a dependency management system is a huge step in ensuring las5ng success of our applica5ons that can withstand the test of 5me. @AdamMc331 #DCSF22 48
  84. Thanks! @AdamMc331 #DCSF22 49