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

TP3 & KTP: Simple, fast, and boilerplate-free DI for Kotlin

TP3 & KTP: Simple, fast, and boilerplate-free DI for Kotlin

Presented at Droidcon San Francisco 2019

Daniel Molinero Reguera

November 25, 2019
Tweet

More Decks by Daniel Molinero Reguera

Other Decks in Programming

Transcript

  1. SIMPLE, FAST, AND BOILERPLATE-FREE DI FOR KOTLIN TP3 & KTP

    https://github.com/stephanenicolas/toothpick/ K d_lemures stephanenicolas # ktp-droidconsf
  2. THE TALK ▸ KTP basics • Modules & Bindings •

    Singletons ▸ KTP vs Dagger vs Koin • Performance • Ease of use ▸ Advanced features • Scopes • Lifecycle integration • ViewModel integration # ktp-droidconsf
  3. KTP BASICS

  4. INJECTING class MyActivity : AppCompatActivity() { val notificationHelper: NotificationHelper by

    inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .inject(this) } } KTP BASICS # ktp-droidconsf
  5. INJECTING class MyActivity : AppCompatActivity() { val notificationHelper: NotificationHelper by

    inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .inject(this) } } KTP BASICS # ktp-droidconsf
  6. INJECTING class MyActivity : AppCompatActivity() { val notificationHelper: NotificationHelper by

    inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .inject(this) } } KTP BASICS # ktp-droidconsf
  7. MAKING DEPENDENCIES INJECTABLE KTP BASICS # ktp-droidconsf @InjectContructor class NotificationHelper()

    { fun showImportantNotification(message: String) { Snackbar.make(message, Snackbar.LENGHT_LONG) .show() } }
  8. MAKING DEPENDENCIES INJECTABLE KTP BASICS # ktp-droidconsf @InjectContructor class NotificationHelper()

    { fun showImportantNotification(message: String) { Snackbar.make(message, Snackbar.LENGHT_LONG) .show() } }
  9. LAZY INJECTING class MyActivity : AppCompatActivity() { val notificationHelper: NotificationHelper

    by lazy() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .inject(this) } } KTP BASICS # ktp-droidconsf
  10. MODULES & BINDINGS KTP BASICS # ktp-droidconsf class MyActivity :

    AppCompatActivity() { val notificationHelper: INotificationHelper by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .installModules(module { bind<INotificationHelper>().toClass<SnackbarNotificationHelper>() }) .inject(this) } }
  11. MODULES & BINDINGS KTP BASICS # ktp-droidconsf class MyActivity :

    AppCompatActivity() { val notificationHelper: INotificationHelper by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .installModules(module { bind<INotificationHelper>().toClass<SnackbarNotificationHelper>() }) .inject(this) } }
  12. MODULES & BINDINGS KTP BASICS # ktp-droidconsf class MyActivity :

    AppCompatActivity() { val notificationHelper: INotificationHelper by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .installModules(module { bind<INotificationHelper>().toClass<SnackbarNotificationHelper>() }) .inject(this) } }
  13. INJECTING SINGLETONS KTP BASICS # ktp-droidconsf class MyActivity : AppCompatActivity()

    { val notificationHelper: NotificationHelper by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .inject(this) } }
  14. KTP BASICS # ktp-droidconsf @Singleton class NotificationHelper() { fun showImportantNotification(message:

    String) { Snackbar.make(message, Snackbar.LENGHT_LONG) .show() } } SINGLETONS BY ANNOTATIONS
  15. SINGLETONS BY ANNOTATIONS KTP BASICS # ktp-droidconsf @Singleton class NotificationHelper()

    { fun showImportantNotification(message: String) { Snackbar.make(message, Snackbar.LENGHT_LONG) .show() } }
  16. SINGLETONS BY BINDINGS KTP BASICS # ktp-droidconsf class MyActivity :

    AppCompatActivity() { val notificationHelper: NotificationHelper by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .installModules(module { bind<NotificationHelper>().singleton() }) .inject(this) } }
  17. KTP BASICS # ktp-droidconsf class MyActivity : AppCompatActivity() { val

    notificationHelper: NotificationHelper by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .installModules(module { bind<NotificationHelper>().singleton() }) .inject(this) } } SINGLETONS BY BINDINGS
  18. KTP VS DAGGER VS KOIN

  19. DI LIBS COMPARISON Runtime
 Performance Daggers KTP K Koin Speed

    wise, all libs have the same performance level. DAGGER VS KOIN VS KTP # ktp-droidconsf
  20. DI LIBS COMPARISON DAGGER VS KOIN VS KTP Boiler-plate Daggers

    KTP K KTP requires less boiler plate than
 Koin (providers) and 
 Dagger (components, modules & tests). # ktp-droidconsf Koin
  21. DI LIBS COMPARISON Kotlin API Friendliness & Ease of use

    Daggers KTP K Koin KTP is easier to learn.
 Dagger has no Kotlin API & it’s hard! DAGGER VS KOIN VS KTP # ktp-droidconsf
  22. DI LIBS COMPARISON With KTP, Annotation Processing is incremental
 and

    has a limited impact on build times. Build
 Performance Daggers KTP K DAGGER VS KOIN VS KTP # ktp-droidconsf Koin
  23. DI LIBS COMPARISON DI GRAPH VALIDATION With KTP, it is

    possible to validate the DI Graph at test-time,
 when Dagger does it at compile-time. # ktp-droidconsf @Test fun testInjections() { // GIVEN val activity = SimpleBackpackItemsActivity() // WHEN activity.injectDependencies() // GIVEN // if injection did NOT crash, the graph is valid }
  24. DI LIBS COMPARISON ERRORS AND STACK TRACES KTP stack traces

    are easy to read 
 and understand ! # ktp-droidconsf at com.example.toothpick.model.Backpack.<init>(Backpack.kt:11) at com.example.toothpick.model.Backpack__Factory.createInstance(Backpack__Factory.java:10) at com.example.toothpick.model.Backpack__Factory.createInstance(Backpack__Factory.java:7) at toothpick.InternalProvider.get(InternalProvider.java:134) at toothpick.InternalScopedProvider.get(InternalScopedProvider.java:80) at toothpick.ScopeImpl.getInstance(ScopeImpl.java:93) at toothpick.ktp.delegate.EagerDelegate.onEntryPointInjected(InjectDelegate.kt:50) at toothpick.ktp.delegate.DelegateNotifier.notifyDelegates(DelegateNotifier.kt:49) at toothpick.ktp.KTP$1.inject(KTP.kt:37) at toothpick.Toothpick.inject(Toothpick.java:243) at toothpick.ScopeImpl.inject(ScopeImpl.java:140) at com.toothpick.SimpleBackpackItemsActivity.injectDependencies(SimpleBackpackItemsActivity.kt:73) at com.toothpick.SimpleBackpackItemsActivityTest.testInjections(SimpleBackpackItemsActivityTest.kt:18)
  25. ADVANCED FEATURES

  26. # ktp-droidconsf ADVANCED FEATURES SCOPES ROOT SCOPE
 is living Injection

    is performed inside scopes.
 Scopes contain bindings and singletons. KTP.openRootScope() .installModules(module { bind<INotificationHelper>().toClass<…>()
 bind<NetworkService>().singleton() })
 .inject(this)
  27. MULTIPLE SCOPES APPLICATION # ktp-droidconsf ACTIVITY 1
 is living Each

    scope will have its own bindings & singletons. ACTIVITY 1 ADVANCED FEATURES
  28. # ktp-droidconsf APPLICATION FLOW .installModules(module { bind<INotificationHelper>() .toClass<ToastNotificationHelper>() }) ACTIVITY

    1 ACTIVITY 2 ACTIVITY 3 @FlowScope @Singleton data class ShoppingCart SCOPE TREE ADVANCED FEATURES
  29. OPENING A SUBSCOPE # ktp-droidconsf class MyActivity : AppCompatActivity() {

    val notificationHelper: INotificationHelper by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .openSubScope(this) .inject(this) } } ROOT MY ACTIVITY ADVANCED FEATURES
  30. # ktp-droidconsf class MyActivity : AppCompatActivity() { val notificationHelper: INotificationHelper

    by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .openSubScope(this) .inject(this) } } ROOT MY ACTIVITY OPENING A SUBSCOPE ADVANCED FEATURES
  31. MODULES & BINDINGS IN SCOPES # ktp-droidconsf class MyActivity :

    AppCompatActivity() { val notificationHelper: INotificationHelper by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .openSubScope(this) .installModules(module { bind<INotificationHelper>() .toClass<SnackbarNotificationHelper>() }) .inject(this) } } ROOT MY ACTIVITY ADVANCED FEATURES
  32. # ktp-droidconsf SCOPE LIFECYCLE APPLICATION
 is living ACTIVITY 1
 is

    living ACTIVITY 2
 is living A scope is a space in memory 
 that is associated to a duration, it has a lifecycle. ADVANCED FEATURES
  33. CLOSING A SCOPE # ktp-droidconsf class MyActivity : AppCompatActivity() {

    val notificationHelper: NotificationHelper by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .openSubScope(this) .inject(this) } override fun onDestroy() { super.onDestroy() KTP.closeScope(this) } } ADVANCED FEATURES
  34. CLOSING A SCOPE # ktp-droidconsf class MyActivity : AppCompatActivity() {

    val notificationHelper: NotificationHelper by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .openSubScope(this) .inject(this) } override fun onDestroy() { super.onDestroy() KTP.closeScope(this) } } ADVANCED FEATURES
  35. LIFECYCLE INTEGRATION # ktp-droidconsf class MyActivity : AppCompatActivity() { val

    notificationHelper: NotificationHelper by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .openSubScope(this) .closeOnDestroy(this) .inject(this) } } ADVANCED FEATURES
  36. LIFECYCLE INTEGRATION # ktp-droidconsf class MyActivity : AppCompatActivity() { val

    notificationHelper: NotificationHelper by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .openSubScope(this) .closeOnDestroy(this) .inject(this) } } dependencies { implementation ‘com.github.stephanenicolas.toothpick:smoothie-lifecycle-ktp:X.Y.Z’ } ADVANCED FEATURES
  37. VIEWMODEL INTEGRATION # ktp-droidconsf ADVANCED FEATURES class MyActivity : AppCompatActivity()

    { val myViewModel: MyViewModel by viewModels() }
  38. VIEWMODEL INTEGRATION # ktp-droidconsf ADVANCED FEATURES class MyActivity : AppCompatActivity()

    { val myViewModel: MyViewModel by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .openSubScope(MyViewModel::class) { it.installViewModelBinding<MyViewModel>(this) } .inject(this) } }
  39. VIEWMODEL INTEGRATION # ktp-droidconsf ADVANCED FEATURES class MyActivity : AppCompatActivity()

    { val myViewModel: MyViewModel by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .openSubScope(MyViewModel::class) { it.installViewModelBinding<MyViewModel>(this) } .inject(this) } }
  40. VIEWMODEL INTEGRATION # ktp-droidconsf ADVANCED FEATURES class MyViewModel : ViewModel()

    { val notificationHelper: NotificationHelper by inject() } ViewModel will get its dependencies injected.
  41. # ktp-droidconsf ADVANCED FEATURES VIEWMODEL INTEGRATION class MyActivity : AppCompatActivity()

    { val myViewModel: MyViewModel by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .openSubScope(MyViewModel::class) { it.closeOnViewModelCleared(this) } .inject(this) } }
  42. # ktp-droidconsf ADVANCED FEATURES VIEWMODEL INTEGRATION dependencies { implementation ‘com.github.stephanenicolas.toothpick:smoothie-lifecycle—viewmodel—ktp:X.Y.Z’

    } class MyActivity : AppCompatActivity() { val myViewModel: MyViewModel by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) KTP.openRootScope() .openSubScope(MyViewModel::class) { it.closeOnViewModelCleared(this) } .inject(this) } }
  43. SIMPLE, FAST, AND BOILERPLATE-FREE DI FOR KOTLIN TP3 & KTP

    https://github.com/stephanenicolas/toothpick/ K d_lemures stephanenicolas # ktp-droidconsf