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

C716975ef0b4ed35553572ff44ea1bed?s=128

Daniel Molinero Reguera

November 25, 2019
Tweet

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