Dependency Injection with Koin

Dependency Injection with Koin

While Dagger has been the go to library for DI in Android, in this talk we explore Koin (which recently hit 2.0) and see whether it can be a viable alternative for you and how it compares with Dagger.

82c4642dd6685601e49e7bc1a0cc3da7?s=128

Shashank Mishra

September 25, 2019
Tweet

Transcript

  1. DEPENDENCY INJECTION WITH KOIN Shashank Mishra @SHKM9 .droidconGreece19

  2. why • complexity
 • build times
 • Kotlin
 • multiplatform

    P
  3. modules @Module class AppModule { @Provides @Singleton fun providePreferences(application: Application):

    SharedPreferences { return PreferenceManager.getDefaultSharedPreferences(application) }
 
 @Provides @Singleton fun provideUserPostsUseCase(repository: UserRepository): UsersPostsUseCase { return UsersPostsUseCase(repository) }
 }
  4. modules @Module class AppModule { @Provides @Singleton fun providePreferences(application: Application):

    SharedPreferences { return PreferenceManager.getDefaultSharedPreferences(application) }
 
 @Provides @Singleton fun provideUserPostsUseCase(repository: UserRepository): UsersPostsUseCase { return UsersPostsUseCase(repository) }
 }
  5. modules val appModule = module { 
 
 
 


    
 
 
 } single { createSharedPreferences(context = get()) } 
 factory { UsersPostsUseCase(prefs = get()) } 
 viewModel { UserProfileViewModel(userPostsUseCase = get()) }
  6. component @Singleton @Component(modules = [AppModule"::class]) interface AppComponent { fun inject(activity:

    MainActivity) @Component.Builder interface Builder { @BindsInstance fun application(application: Application): Builder fun build(): AppComponent } }
  7. component @Singleton @Component(modules = [AppModule"::class]) interface AppComponent { fun inject(activity:

    MainActivity) @Component.Builder interface Builder { @BindsInstance fun application(application: Application): Builder fun build(): AppComponent } }
  8. component @Singleton @Component(modules = [AppModule"::class]) interface AppComponent { fun inject(activity:

    MainActivity) @Component.Builder interface Builder { @BindsInstance fun application(application: Application): Builder fun build(): AppComponent } }
  9. component val component: AppComponent by lazy { DaggerAppComponent.builder().application(this).build() }

  10. startKoin { androidContext(this@MyApplication) modules(listOf(appModule, networkModule, cacheModule)) } component

  11. class UserPrefs @Inject constructor(private val prefs: SharedPreferences) clients

  12. class ProfileFragment : Fragment() {
 @Inject lateinit var sharedPreferences: SharedPreferences

    . . . override fun onCreate(savedInstanceState: Bundle?) { component.inject(this) } } clients
  13. class ProfileFragment : Fragment() { private val sharedPreferences by inject<SharedPreferences>()

    private val viewModel: ProfileViewModel by viewModel() . . .
 
 } class UserPrefs(private val sharedPreferences: SharedPreferences) clients
  14. val appModule = module { single { (id: String) !->

    SomeUseCase(id) }
 } parameters
  15. val appModule = module { single { (id: String) !->

    SomeUseCase(id) }
 private val useCase: SomeUseCase by inject { parametersOf(getIntentData()) } } parameters
  16. multi-binding @Binds @IntoMap @ClassKey(Square"::class) abstract fun provideSquare(shape: Square): Shape @Binds

    @IntoMap @ClassKey(Triangle"::class) abstract fun provideTriangle(shape: Triangle): Shape
  17. multi-binding @Binds @IntoMap @ClassKey(Square"::class) abstract fun provideSquare(shape: Square): Shape @Binds

    @IntoMap @ClassKey(Triangle"::class) abstract fun provideTriangle(shape: Triangle): Shape
  18. multi-binding @Binds @IntoMap @ClassKey(Square"::class) abstract fun provideSquare(shape: Square): Shape @Binds

    @IntoMap @ClassKey(Triangle"::class) abstract fun provideTriangle(shape: Triangle): Shape
  19. multi-binding @Binds @IntoMap @ClassKey(Square"::class) abstract fun provideSquare(shape: Square): Shape @Binds

    @IntoMap @ClassKey(Triangle"::class) abstract fun provideTriangle(shape: Triangle): Shape
  20. multi-binding val shapes = component.shapesMap() interface Component { fun shapesMap():

    Map<Class!!<*>, Shape> }
  21. multi-binding val userModule = module { single { Square() }

    bind Shape"::class single { Triangle() } bind Shape"::class }
  22. multi-binding val userModule = module { single { Square() }

    bind Shape"::class single { Triangle() } bind Shape"::class } val shape: Shape = bind<Shape, Square>()
  23. scopes

  24. scopes @PerFragment @Subcomponent(modules = [ProfileModule"::class]) interface ProfileComponent { 
 fun

    inject(fragment: ProfileFragment) @Subcomponent.Builder interface Builder { fun build(): ProfileComponent } }
  25. scopes @PerFragment @Subcomponent(modules = [ProfileModule"::class]) interface ProfileComponent { 
 fun

    inject(fragment: ProfileFragment) @Subcomponent.Builder interface Builder { fun build(): ProfileComponent } }
  26. scopes @PerFragment @Subcomponent(modules = [ProfileModule"::class]) interface ProfileComponent { fun inject(fragment:

    ProfileFragment) @Subcomponent.Builder interface Builder { fun build(): ProfileComponent } }
  27. scopes @PerFragment @Subcomponent(modules = [ProfileModule"::class]) interface ProfileComponent { fun inject(fragment:

    ProfileFragment) @Subcomponent.Builder interface Builder { fun build(): ProfileComponent } }
  28. scopes @Singleton @Component(modules = [AppModule"::class]) interface AppComponent {
 
 .

    . . 
 val profileComponentBuilder: ProfileComponent.Builder }
  29. scopes class ProfileFragment : Fragment() { 
 @Inject lateinit var

    sharedPreferences: SharedPreferences override fun onCreate(savedInstanceState: Bundle?) { 
 appComponent.profileComponentBuilder.build().inject(this) 
 } }
  30. scopes module {
 scope(named<ProfileFragment>()) { scoped { UsersPostsUseCase(userRepository = get())

    } }
 }
  31. scopes class ProfileFragment : Fragment() { private val usersPostsUseCase: UsersPostsUseCase

    by inject() . . . 
 }
  32. scopes class ProfileFragment : Fragment() { private val usersPostsUseCase: UsersPostsUseCase

    by currentScope.inject() . . . 
 }
  33. scopes module {
 scope(named<ProfileFragment>()) { scoped { UsersPostsUseCase(userRepository = get())

    } }
 }
  34. scopes module {
 scope(named(DEMO_SCOPE)) { scoped { UsersPostsUseCase(userRepository = get())

    } }
 }
  35. scopes private val scope: Scope by lazy { val customScope

    = getKoin().createScope(SCOPE_ID, named(DEMO_SCOPE)) customScope }
  36. scopes private val usersPostsUseCase: UsersPostsUseCase by scope.inject()

  37. scopes private val usersPostsUseCase: UsersPostsUseCase by scope.inject() scope.close()

  38. scopes fun LifecycleOwner.bindScope(scope: Scope, event: Lifecycle.Event = Lifecycle.Event.ON_DESTROY) { lifecycle.addObserver(ScopeObserver(event,

    this, scope)) }
  39. multi-module Core Feature 1 Feature 2

  40. multi-module Core Feature Feature @Component(modules = [CoreModule"::class]) @Singleton interface CoreComponent

    { fun provideGson(): Gson }
  41. multi-module Core Feature Feature @Component(modules = [CoreModule"::class]) @Singleton interface CoreComponent

    { fun provideGson(): Gson }
  42. multi-module @Component(modules = [CoreModule"::class]) @Singleton interface CoreComponent { fun provideGson():

    Gson } @FeatureScope @Component( modules = [FeatureModule"::class], dependencies = [CoreComponent"::class] ) interface FeatureComponent { @Component.Builder interface Builder { fun build(): FeatureComponent fun coreComponent(coreComponent: CoreComponent): Builder } } Core Feature Feature
  43. multi-module @Component(modules = [CoreModule"::class]) @Singleton interface CoreComponent { fun provideGson():

    Gson } @FeatureScope @Component( modules = [FeatureModule"::class], dependencies = [CoreComponent"::class] ) interface FeatureComponent { @Component.Builder interface Builder { fun build(): FeatureComponent fun coreComponent(coreComponent: CoreComponent): Builder } } Core Feature Feature
  44. multi-module @Component(modules = [CoreModule"::class]) @Singleton interface CoreComponent { fun provideGson():

    Gson } @FeatureScope @Component( modules = [FeatureModule"::class], dependencies = [CoreComponent"::class] ) interface FeatureComponent { @Component.Builder interface Builder { fun build(): FeatureComponent fun coreComponent(coreComponent: CoreComponent): Builder } } Core Feature Feature
  45. multi-module val coreComponent = DaggerCoreComponent.builder().build() 
 . . . val

    featureComponent = DaggerFeatureComponent.builder() .coreComponent(coreComponent).build().inject(this)
  46. multi-module startKoin { androidContext(this@MyApplication) modules(listOf(appModule, networkModule, cacheModule)) } }

  47. multi-module startKoin { androidContext(this@MyApplication) modules(listOf(coreModule)) } }

  48. multi-module loadKoinModules(listOf(featureModule1, featureModule2))

  49. multi-module unloadKoinModules(listOf(featureModule1))

  50. testing

  51. testing startKoin { androidContext(application) modules(listOf(appModule)) }.checkModules()

  52. testing

  53. testing class UserService(private val repo: UserRepository)

  54. testing class UserServiceTest : KoinTest { private val service: UserService

    by inject() @Before fun before() { startKoin { modules(listOf(userModule)) } } } class UserService(private val repo: UserRepository)
  55. testing class UserServiceTest : KoinTest { private val service: UserService

    by inject() @Before fun before() { startKoin { modules(listOf(userModule)) } } } class UserService(private val repo: UserRepository)
  56. testing class UserServiceTest : KoinTest { private val service: UserService

    by inject() @Before fun before() { startKoin { modules(listOf(userModule)) } } } class UserService(private val repo: UserRepository)
  57. testing @Test fun `test getting user repo`() { declareMock<UserRepository> {

    whenever(getRepoId()) doReturn "1" } val result = service.getUserRepo()
 assert(. . .) } class UserService(private val repo: UserRepository)
  58. performance https://github.com/Sloy/android-dependency-injection-performance DAGGER KOIN Nexus 6 0.3ms 19.8ms OnePlus 5

    0.0ms 2.3ms OnePlus 1 0.0ms 11.84ms Mi A1 0.02ms 9.17ms Galaxy S8 0.01ms 5.68ms Setup Times
  59. performance https://github.com/Sloy/android-dependency-injection-performance Methods Fields Classes Percentage 17.5 35 52.5 70

    8.29 35.1 62.47 8.33 32.44 57.31 Koin Dagger
  60. class ProfileViewModel @Inject constructor( private val profileService: ProfileService, private val

    errorHandler: ErrorHandler )
  61. class ProfileViewModel @Inject constructor( private val profileService: ProfileService, private val

    authService: AuthService, private val errorHandler: ErrorHandler )
  62. module { viewModel { ProfileViewModel(get()) } }

  63. module { viewModel { ProfileViewModel(get(), get()) } }

  64. module { viewModel { ProfileViewModel(get(), get(), get()) } }

  65. None
  66. DAGGER KOIN Automatic injection at the hands of compile-time code

    generator Manual injection using a bunch of lambdas for every type
  67. module { viewModel<ProfileViewModel>() }

  68. module { viewModel<ProfileViewModel>() single<UserRepository>() }

  69. module { viewModel<ProfileViewModel>() single<UserRepository>()
 factory<UsersPostsUseCase>() }

  70. inline fun <reified T : Any> create(context: Scope): T {

    val kClass = T"::class lateinit var instance: T val ctor = kClass.getFirstJavaConstructor() val args = getArguments(ctor, context) instance = ctor.makeInstance(args) return instance }
  71. compile time safety

  72. Is Koin really DI?

  73. With service locator the application class asks for it explicitly

    by a message to the locator. With injection there is no explicit request. - Martin Fowler
  74. class ProfileFragment : Fragment() { private val sharedPreferences by inject<SharedPreferences>()


    }
  75. class ProfileFragment : Fragment() {
 @Inject lateinit var sharedPreferences: SharedPreferences

    . . . override fun onCreate(savedInstanceState: Bundle?) { component.inject(this) } }
  76. component.inject(this) private val sharedPreferences by inject<SharedPreferences>() VS

  77. class ProfileFragment : Fragment() {
 @Inject lateinit var sharedPreferences: SharedPreferences

    . . . override fun onCreate(savedInstanceState: Bundle?) { component.inject(this) } }
  78. val fragment = ProfileFragment() component.inject(fragment)

  79. recap DAGGER ✓ Compile time safety
 ✓ Scales really well


    ✓ Ideal for large projects
 x Complexity
 x Build times KOIN ✓ Easy to learn
 ✓ Quick build times
 ✓ Ideal for mid sized projects
 x Breaks at runtime
 x Manual graph maintenance
 x Scales poorly
  80. THANK YOU! QUESTIONS?

  81. fueled.com/jobs