Share
Embed
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
Transcript
81 slides
-
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) } } -
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) } } -
modules val appModule = module {
} single { createSharedPreferences(context = get()) } factory { UsersPostsUseCase(prefs = get()) } viewModel { UserProfileViewModel(userPostsUseCase = get()) } -
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 } } -
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 } } -
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 } } -
class ProfileFragment : Fragment() { @Inject lateinit var sharedPreferences: SharedPreferences
. . . override fun onCreate(savedInstanceState: Bundle?) { component.inject(this) } } clients -
class ProfileFragment : Fragment() { private val sharedPreferences by inject<SharedPreferences>()
private val viewModel: ProfileViewModel by viewModel() . . . } class UserPrefs(private val sharedPreferences: SharedPreferences) clients -
val appModule = module { single { (id: String) !->
SomeUseCase(id) } } parameters -
val appModule = module { single { (id: String) !->
SomeUseCase(id) } private val useCase: SomeUseCase by inject { parametersOf(getIntentData()) } } parameters -
multi-binding @Binds @IntoMap @ClassKey(Square"::class) abstract fun provideSquare(shape: Square): Shape @Binds
@IntoMap @ClassKey(Triangle"::class) abstract fun provideTriangle(shape: Triangle): Shape -
multi-binding @Binds @IntoMap @ClassKey(Square"::class) abstract fun provideSquare(shape: Square): Shape @Binds
@IntoMap @ClassKey(Triangle"::class) abstract fun provideTriangle(shape: Triangle): Shape -
multi-binding @Binds @IntoMap @ClassKey(Square"::class) abstract fun provideSquare(shape: Square): Shape @Binds
@IntoMap @ClassKey(Triangle"::class) abstract fun provideTriangle(shape: Triangle): Shape -
multi-binding @Binds @IntoMap @ClassKey(Square"::class) abstract fun provideSquare(shape: Square): Shape @Binds
@IntoMap @ClassKey(Triangle"::class) abstract fun provideTriangle(shape: Triangle): Shape -
multi-binding val shapes = component.shapesMap() interface Component { fun shapesMap():
Map<Class!!<*>, Shape> } -
multi-binding val userModule = module { single { Square() }
bind Shape"::class single { Triangle() } bind Shape"::class } -
multi-binding val userModule = module { single { Square() }
bind Shape"::class single { Triangle() } bind Shape"::class } val shape: Shape = bind<Shape, Square>() -
scopes @PerFragment @Subcomponent(modules = [ProfileModule"::class]) interface ProfileComponent { fun
inject(fragment: ProfileFragment) @Subcomponent.Builder interface Builder { fun build(): ProfileComponent } } -
scopes @PerFragment @Subcomponent(modules = [ProfileModule"::class]) interface ProfileComponent { fun
inject(fragment: ProfileFragment) @Subcomponent.Builder interface Builder { fun build(): ProfileComponent } } -
scopes @PerFragment @Subcomponent(modules = [ProfileModule"::class]) interface ProfileComponent { fun inject(fragment:
ProfileFragment) @Subcomponent.Builder interface Builder { fun build(): ProfileComponent } } -
scopes @PerFragment @Subcomponent(modules = [ProfileModule"::class]) interface ProfileComponent { fun inject(fragment:
ProfileFragment) @Subcomponent.Builder interface Builder { fun build(): ProfileComponent } } -
scopes @Singleton @Component(modules = [AppModule"::class]) interface AppComponent { .
. . val profileComponentBuilder: ProfileComponent.Builder } -
scopes class ProfileFragment : Fragment() { @Inject lateinit var
sharedPreferences: SharedPreferences override fun onCreate(savedInstanceState: Bundle?) { appComponent.profileComponentBuilder.build().inject(this) } } -
scopes class ProfileFragment : Fragment() { private val usersPostsUseCase: UsersPostsUseCase
by currentScope.inject() . . . } -
scopes private val scope: Scope by lazy { val customScope
= getKoin().createScope(SCOPE_ID, named(DEMO_SCOPE)) customScope } -
multi-module Core Feature Feature @Component(modules = [CoreModule"::class]) @Singleton interface CoreComponent
{ fun provideGson(): Gson } -
multi-module Core Feature Feature @Component(modules = [CoreModule"::class]) @Singleton interface CoreComponent
{ fun provideGson(): Gson } -
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 -
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 -
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 -
multi-module val coreComponent = DaggerCoreComponent.builder().build() . . . val
featureComponent = DaggerFeatureComponent.builder() .coreComponent(coreComponent).build().inject(this) -
testing class UserServiceTest : KoinTest { private val service: UserService
by inject() @Before fun before() { startKoin { modules(listOf(userModule)) } } } class UserService(private val repo: UserRepository) -
testing class UserServiceTest : KoinTest { private val service: UserService
by inject() @Before fun before() { startKoin { modules(listOf(userModule)) } } } class UserService(private val repo: UserRepository) -
testing class UserServiceTest : KoinTest { private val service: UserService
by inject() @Before fun before() { startKoin { modules(listOf(userModule)) } } } class UserService(private val repo: UserRepository) -
testing @Test fun `test getting user repo`() { declareMock<UserRepository> {
whenever(getRepoId()) doReturn "1" } val result = service.getUserRepo() assert(. . .) } class UserService(private val repo: UserRepository) -
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 -
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 -
class ProfileViewModel @Inject constructor( private val profileService: ProfileService, private val
errorHandler: ErrorHandler ) -
class ProfileViewModel @Inject constructor( private val profileService: ProfileService, private val
authService: AuthService, private val errorHandler: ErrorHandler ) -
DAGGER KOIN Automatic injection at the hands of compile-time code
generator Manual injection using a bunch of lambdas for every type -
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 } -
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 -
class ProfileFragment : Fragment() { @Inject lateinit var sharedPreferences: SharedPreferences
. . . override fun onCreate(savedInstanceState: Bundle?) { component.inject(this) } } -
class ProfileFragment : Fragment() { @Inject lateinit var sharedPreferences: SharedPreferences
. . . override fun onCreate(savedInstanceState: Bundle?) { component.inject(this) } } -
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