KOIN — A dependency injection framework

I just started with exploring dependency injection frameworks other than Dagger and then i came across Koin. I thought of implementing this in a sample app with MVP architecture so as to get deeper insights into the framework.

This article explains what Koin is, how to start with it and its comparison to Dagger2(a widely used dependency injection framework)

What is Koin?

Koin is a simple yet powerful Dependency injection framework.It helps in making developers life easy by reducing a lot of boilerplate code which needs to be written in other frameworks like Dagger.
Written in Kotlin, this is a small and simple library which makes dependency injection an easy task.

Core components of Koin

1. startKoin: It is the starting point. This method receives a list of “modules

2. provide/bean/factory: each “provide definition” represents a component to be injected. Into the lambda of the “provide definition”, we register a function with the capability to create that component.. “bean” and “factory” are extensions of provide(which is now deprecated).

“bean” is a singleton implementation of provide

provide( isSingleton = true ) { UserService() as UserRepository}

factory is same as provide

factory {SampleUseCase()} //provide the actual object to be injected,

3. get(): resolves a component dependency. When you are trying to provide a component that needs another component (which will be injected), the get() solves it. Conceptually, it looks for and finds a provider for that type and uses it.

e.g GetNameUseCase needs an implementationUserRepository(an interface)

class GetNameUseCase constructor(val userRepository: UserRepository) {
fun getUserName(): String = userRepository.getUserName()
}

The bean definition for above can be written as :

factory { GetNameUseCase(get()) }
bean { EmployeeService() as UserRepository }

How to start using Koin

1. Include dependencies

When using Koin, we only need to add following dependency and we are ready to go:

compile ‘org.koin:koin-android:0.9.2’

2. Create module list

Modules contain the “provide definitions” declared under the applicationContext function

val modules = org.koin.dsl.module.applicationContext {
factory { SampleUseCase() }
}

3. Start Koin

To start Koin,we just need to call ‘startKoin’ function with list of Modules to be loaded. Following is code snippet:

class KoinApplication : Application() {
override fun onCreate() {
        super.onCreate()
        startKoin(this, listOf(modules))
    }
}

startKoin can only be called once, so make sure that all modules to be loaded are in the list

4. Inject component:

We can use by (in Kotlin for delegation) to inject the components. e.g MainActivity needs to use MainPresenter

val presenter: MainPresenter by inject()

Advanced uses of Koin:

The examples mentioned above were quite simple and did not cater to actual scenarios faced during app development. I have tried to handle these scenarios in sample app following MVP architecture having activity, presenter, usecase and repository.

1. Managing properties:

Properties are static values that can be loaded from external files. In this project we created koin.properties file under assets directory

To use the url property , we can simplify modify modules as below

val modules = org.koin.dsl.module.applicationContext {
   factory { MainPresenter(getProperty("url")) }
}

We can also provide default values for properties.

factory { MainPresenter(getProperty("url",”www.yahoo.com”)) }

In Android, Application,Context, Activity, Fragment, Service classes already have KoinComponent features and they can use setProperty function to set value of a property.

2. Multiple implementations of an interface

We may face a situation where we need to provide 2 implementations of same interface. E.g GetNameUseCase needs an implementation of UserRepository.

class GetNameUseCase constructor(val userRepository: UserRepository) {
fun getUserName(): String = userRepository.getUserName()
}

But UserRepository has 2 implementations — EmployeeService and UserService

factory { GetNameUseCase(get()) }
bean { EmployeeService() as UserRepository }
bean { UserService() as UserRepository }

In above scenario nothing will fail but GetNameUseCase would be injected with UserService as it last one to be declared

3. Resolve to a particular implementation

A better way to solve above problem is to provide named definitions to our beans:

val modules = org.koin.dsl.module.applicationContext {
factory { GetNameUseCase(get(“employeedata”)) }
bean(“employeedata”) { EmployeeService() as UserRepository }
bean(“userdata”) { UserService() as UserRepository }
}

In case, we forget to add the refer implementation by name in GetNameUseCase, we will face following error

Error scenario

val modules = org.koin.dsl.module.applicationContext {
factory { GetNameUseCase(get()) }
bean(“employeedata”) { EmployeeService() as UserRepository }
bean(“userdata”) { UserService() as UserRepository }
}

4. Lifecycle & context management

Koin modules make you describe an application context and sub contexts. Those sub contexts can be dropped to suit Android components lifecycle. For the given context:

class TodoAppModule : AndroidModule() {
override fun context() = applicationContext {
context(name = "Tasks") { ... }
}

Components from this context will be dropped with calling releaseContext("Tasks")

override fun onPause() {
    releaseContext(contextName)
    super.onPause()
}

5. Logging

The Android startKoin() function has a logger argument, that allows us to define what Koin Logger you want to use if needed (default is set to AndroidLogger()). It can be set to EmptyLogger() if we don’t want Koin to log at runtime.

Reasons to switch to Koin from Dagger:

1. Dependencies: The no of dependencies to be included for Koin is much less. No separate annotation processor needed.

2. Boilerplate code: With Koin we do not need to have lot of files( PerScope, PerFragment, component and Module files), need not write inject() functions in every activity or so.

3. Easy to understand and adapt: Koin is much more easier to incorporate and understand than Dagger

4. Support for properties: Koin also has support for properties which makes it easier to pass through static values in presenters.Otherwise, the usual process was to make a call from activity to presenter.

Why Dagger and not Koin

1. Koin is still Work-In-Progress and hence may not be good enough for large projects

2. Dagger does all the processing at compile time and if there is any error in dependency graph, developer will know it earlier.

3. We cannot inject using a simple keyword like inject as in Dagger. Everything needs to be defined in module list.

4. Not much of online help is available for Koin right now.