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

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.

Shashank Mishra

September 25, 2019
Tweet

Other Decks in Programming

Transcript

  1. 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) }
 }
  2. 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) }
 }
  3. modules val appModule = module { 
 
 
 


    
 
 
 } single { createSharedPreferences(context = get()) } 
 factory { UsersPostsUseCase(prefs = get()) } 
 viewModel { UserProfileViewModel(userPostsUseCase = get()) }
  4. 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 } }
  5. 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 } }
  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. class ProfileFragment : Fragment() {
 @Inject lateinit var sharedPreferences: SharedPreferences

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

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

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

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

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

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

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

    @IntoMap @ClassKey(Triangle"::class) abstract fun provideTriangle(shape: Triangle): Shape
  15. multi-binding val userModule = module { single { Square() }

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

    bind Shape"::class single { Triangle() } bind Shape"::class } val shape: Shape = bind<Shape, Square>()
  17. scopes @PerFragment @Subcomponent(modules = [ProfileModule"::class]) interface ProfileComponent { 
 fun

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

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

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

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

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

    sharedPreferences: SharedPreferences override fun onCreate(savedInstanceState: Bundle?) { 
 appComponent.profileComponentBuilder.build().inject(this) 
 } }
  23. scopes private val scope: Scope by lazy { val customScope

    = getKoin().createScope(SCOPE_ID, named(DEMO_SCOPE)) customScope }
  24. 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
  25. 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
  26. 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
  27. multi-module val coreComponent = DaggerCoreComponent.builder().build() 
 . . . val

    featureComponent = DaggerFeatureComponent.builder() .coreComponent(coreComponent).build().inject(this)
  28. testing class UserServiceTest : KoinTest { private val service: UserService

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

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

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

    whenever(getRepoId()) doReturn "1" } val result = service.getUserRepo()
 assert(. . .) } class UserService(private val repo: UserRepository)
  32. 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
  33. class ProfileViewModel @Inject constructor( private val profileService: ProfileService, private val

    authService: AuthService, private val errorHandler: ErrorHandler )
  34. DAGGER KOIN Automatic injection at the hands of compile-time code

    generator Manual injection using a bunch of lambdas for every type
  35. 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 }
  36. 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
  37. class ProfileFragment : Fragment() {
 @Inject lateinit var sharedPreferences: SharedPreferences

    . . . override fun onCreate(savedInstanceState: Bundle?) { component.inject(this) } }
  38. class ProfileFragment : Fragment() {
 @Inject lateinit var sharedPreferences: SharedPreferences

    . . . override fun onCreate(savedInstanceState: Bundle?) { component.inject(this) } }
  39. 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