What is up with Koin

Jan Stoltman
6 min readSep 15, 2018

--

What is Koin and how does it do in an android App

Official Koin logo — From the official site

Goal

In this article I present basics of Koin and compare it to other dependency injection frameworks. An example app was built using Koin, Fast Android Networking and ViewModels together with LiveData, to provide some useful example of Koin in practice. The goal is to try out dependency injection with Koin, so I will focus on this aspect leaving everything else a little bit more aside.

For now the app will allow users to log in, see their basic profile info and get a list of the newest releases on Spotify . It will be based on Spotify API, the whole code is available here.

What is Koin?

According to its creators, Koin is pretty much a lightweight alternative to other more robust frameworks (e.g. Dagger, Guice and Spring).

A pragmatic lightweight dependency injection framework for Kotlin developers.

However, according to this reddit thread it is not that simple

Koin isn’t a dependency injector but a service locator with a clever reified trick that you can use to manually perform dependency injection, the boilerplate will scale disproportionally.

What it means for developers is that Koin should be a great alternative for other frameworks when it comes to small projects, in bigger ones on the other hand there is a risk that the overhead of Koin’s code generation will outpace the possible advantages.

But let’s face it, it is quite obvious that bigger, older and more mature frameworks are the most reasonable choice when it comes to big projects with dozens of developers on-board. Koin however, seems like a great alternative for small and medium sized apps.

Speed and overhead are one thing, the other important properties are the ease of use and the amount of boilerplate needed to get the app up and running.

In this article I will use Dagger for comparison, as it is the most known and most widely used DI framework for Android.

App’s architecture

Before venturing forth, one must gather their app’s architecture.

Well, the word ‘Architecture’ might be a little big for this project but even the smallest app needs some kind of a design in place. I will be using simplified Clean Architecture with MVVM pattern for the presentation layer and RxJava for getting off the main thread as quickly as possible in the domain layer.

Arrows point downstream (toward consumers)

In this architecture, Koin will help us abide by the dependency inversion principle and decouple our code.

Where to use Koin

So far so good. However, this article is not about designing apps, it is about trying out new dependency injection framework, so let us focus on how it is used in the example app.

Koin injection path goes pretty much the same way as the architecture. Views receive ViewModels, ViewModels receive Interactors and Interactors are injected with any Repository that they require.

The next part of this article will focus on Koin itself. Use my project in cases when something is not clear or as an example. Complete code might be useful in understanding how it all works.

Let us get started with Koin

Dependencies

First things first, let us take care of Gradle dependencies

// Project's build.gralde
allprojects {
repositories {
jcenter()
}
}
// Module's build.gradle
dependencies {
def koin_version = "1.0.0-RC-1"


// Just Koin
implementation "org.koin:koin-android:$koin_version"

// Support for Android scopes
implementation "org.koin:koin-androidx-scope:$koin_version"

// Support for Android ViewModels
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
}

Koin DSL keywords

Koin is based around its DSL which consists of these keywords:

  • module { } - create a Koin module or a submodule (inside a module)
  • factory { } - provide a factory bean definition
  • single { } - provide a bean definition
  • get() - resolve a component dependency (eager)
  • inject() - resolve a component dependency in Android components runtime (lazy)
  • bind - additional Kotlin type binding for given bean definition
  • getProperty() - resolve a property

Modules

Modules define how our injected dependencies are created. They are a nice replacement of modules/components of Dagger. Being much easier to use and get a hang of, the downside here is the lack of compile-time errors when some dependency doesn’t have its provider. You have to actually hit the proverbial wall before you realize that you have forgotten about one of the providers.

val viewModelModule = module {
//
Provides an instance of ViewModel and binds it to an Android Component lifecycle
viewModel { LauncherViewModel(get()) }
}
val repositoryModule = module {
//
Provides a singleton instance
single { LoginStatusRepository(get()) }
}
val interactorModule = module {
//
Provides a new instance for each call
factory { LoginStatusInteractor() }
}

Remember, to use a module you have to call startKoin() in your Application subclass.

class MyApplication : Application() {
override fun onCreate() {
startKoin(this, listOf(viewModelModule, repositoryModule), interactorModule)
}
}

Inject it

Injecting is also crazy easy in Koin. Resolving a dependency in a lazy fashion is as easy as using inject().

val loginStatusInteractor: LoginStatusInteractor by inject()
val launcherViewModel: LauncherViewModel by viewModel()

This will provide lazy delegates for our values which will then ‘inject’ the requested values in a lazy fashion.

If for some reason you would rather get your dependency in a eager fashion get() is there for you.

val loginStatusInteractor: LoginStatusInteractor = get()

Obviously, constructor injection is also available. Parameters can be requested in a constructor and provided in a module by the use of get() keyword or by simply creating them.

// Repository and Service are declared in a module
class ReleasesRepository(private val apiService: ApiService) {
fun fetchReleases(accessToken: String) = apiService.getNewReleases("Bearer $accessToken")
}
// Inject constructor in a module with injection by get()
single { ReleasesRepository(get()) }
// Or just create the required object
single { ReleasesRepository(ApiService()) }

The lack of auto-resolving constructor injection is a little bit of an issue there, as we have to remember to declare our constructors in modules every time we create one.

Detour

Let us take a little detour now and compare what we have achieved so far with Koin to what would have to be done in Dagger to get the same result:

// We can provide LoginStatusInteractor by constuctor injection
@Singleton class LoginStatusInteractor @Inject constructor() { }
// Vomponent
@Singleton @Component interface MyComponent {
fun loginStatusInteractor(): LoginStatusInteractor
}
// Create dagger component
val myComponent = DaggerMyComponent.create()
// Finally we can inject
val interactor = myComponent.loginStatusInteractor()

Edit: I make changes to the code thanks to u/Zhuinden, who has shown me how to inject Dagger the right way. Also, according to this article lazy injection can also be easily achieved with Dagger. Thanks Reddit!

Named definitions

When you want to have two definitions of the same type you can use named definitions (these are very similar to Dagger’s Qualifiers). Just define and request your definitions with some kind of a name.

// Both return Scheduler type
single("IO_SCHEDULER") { Schedulers.io() }
single("MAIN_THREAD_SCHEDULER") { AndroidSchedulers.mainThread()
// Get them later by calling a named get()
get("IO_SCHEDULER")
get("MAIN_THREAD_SCHEDULER")
// Or inject them by using named delegate
val io : Scheduler by inject(name = "IO_SCHEDULER")
val mainThread : Scheduler by inject(name = "MAIN_THREAD_SCHEDULER")

Testing

As any good developer knows “If it isn’t tested, it’s broken”. Luckily, Koin makes injecting mocks into our test super easy. Just replace your modules in your test app with mock modules and you are good to go.

// Lazy delegate will be evaluated when called
val service : ApiService by inject()
@Test
fun myTest() {
startKoin(myModules)
// Close Koin when you are done with it for this test so that singletons can be recreated for the next one
closeKoin()
}

So, what is up with Koin?

The good

  • Modules are super easy to create and use
  • Support for ViewModels is nice
  • Lazy injection is always a plus
  • Naming providers are convenient and don’t require a separate Qualifier to work
  • Injecting mock modules in tests is also easy
  • There is much less boilerplate when compared to Dagger
  • It is much easier to understand than Dagger

The bad

  • No auto constructor injection is a bummer
  • No compile time dependency trees validation forces programmers to find errors at runtime

The ugly

  • Growing overhead cost makes Koin a risky choice for bigger projects. It might turn out that in few years your project will become too big for your dependency injection framework

With Dagger there’s a certain amount of fixed overhead but then you rarely have to significantly alter the shape of your graph as bindings propagate throughout injected types automatically. With manual dependency injection, you have to propagate bindings throughout injected types manually.

More good news! Stable version of Koin (1.0.0) has just been released. Check out this article by the creator of Koin. It is yet another reason to give it a try in your next project.

--

--