Let’s build our own simplified version of Koin
Demystifying the amazing Koin Dependency Injection framework by building a simplified version of it

Koin is an amazing Dependency Injection Framework for Kotlin and Android projects. It has gained huge popularity over the past two years and many Android developers (including myself) have adopted it, use it in production relying on its powerful mechanism to help perform dependency injection. And we just love it!
We love Koin because it’s simpler than Dagger 2 (we’re not gonna talk about Hilt yet). We find it to be very simple because Koin does a lot of magic tricks under the hood for us and provides a really nice and elegant API for dependency declaration & manipulation.
Of course, there is a debate here about if Koin is a real Dependency Injection framework or a Service Locator but we’re not going to spread out on that question in this post.
The goal of this post is to try to replicate something that looks like Koin but with a naive approach. Our DI framework will be inspired by Koin, we will try to achieve the same API and implement our own core logic for dependency management. We will call this unicorn LiteKoin.
This post will be long so make sure you grab a cup of coffee.
To understand what we will be doing here, you must be familiar with these concepts:
- Building DSLs in Kotlin (Higher Order Functions, Lambdas with receiver, Extension Functions)
- Kotlin Generics, Operator Overloading, Extension Properties, Kotlin Reflection, Type Aliases
Service Locator
We will base our approach on the Service Locator design pattern. This pattern is actually very simple to understand.
The service locator pattern is a design pattern used in software development to encapsulate the processes involved in obtaining a service with a strong abstraction layer. This pattern uses a central registry known as the “service locator”, which on request returns the information necessary to perform a certain task. — Wikipedia
There are several ways to implement a Service Locator. In our case, we want to identify services by their types. This will help with the reflection part.
Service
We start by creating a Service interface.
interface Service {
val type: KClass<*>
val instance: Any
}
Then we can create a DefaultService class.
We add a little factory method to help create instances of DefaultService
Now the ServiceLocator class could be built like this:
This is enough to keep all our dependencies in a single place and get them by their types.
Modules and Declarations
From Koin documentation we can read: A Koin module is a “space” to gather Koin definition. It’s declared with the module
function.
val myModule = module {
// Declare dependencies here...
}
It’s basically a DSL that helps build modules. So we’re gonna have to implement a Module concept where the dependencies are declared. But before implementing the Module class, let introduce another concept: Declaration. A Declaration will be an important concept in our LiteKoin framework. A Declaration will be a representation of a declared dependency. For example,
factory { Repository() }
{ Repository() }
is what we call here a Declaration. You can call it Definition if you want, I just prefer Declaration. We will see later why this is important. For now, let see what Declaration looks like:
typealias Declaration<T> = () -> T
It’s just a simple type alias on a lambda function. So from this, you will understand that the Declaration is not Repository()
but instead{ Repository() }.
Now our implementation of the Module class could look like this:
The module class maintains a map of Declarations to help us keep track of every Declaration in the module. We also add two utility functions. The first one plus is an operator overload of the + operator on the Module class. This will help us add two modules like this:
mod1 + mod2
The second one does the same thing but on a List of Modules level.
But the most important function in the module class is the factory function. This is the function that helps capture a Declaration just like Koin does with its definitions. As you can see, it’s just a function taking the Declaration as a parameter. Since Declaration is a lambda we can then write:
factory { Repository() }
Once we’ve captured the Declaration, we store it in the declarationRegistry
Module builder
Now that we have the Module class, we can start creating our DSL.
With this builder we are now able to do this:
val myModule = module {
factory { UseCase() }
}
The API
The most mysterious thing about Koin is its get()
function. This get()
function will help resolve and retrieve any dependency registered in the Service Locator dependency Registry.
Actually, there will be two get()
functions. The first one is to get a dependency inside the module builder at the moment of the dependency declaration.
factory { UseCase(get()) }
The second one is the one to get a dependency anywhere else after LiteKoin has been created and initialized.
val viewModel: ViewModel = get()
The Module’s get()
The first get()
will be part of the Module class. We take advantage of Kotlin's reified generic types.
Before going any further in our framework API design, let’s see an import component of our framework.
The LiteKoin class
The LiteKoin class will be the central point of our framework. Its responsibility will be to access the main ServiceLocator. It will collect the modules and provide them to the ServiceLocator
If you wonder what modules.declarationRegistry
is, well, it’s just an extension property on List<Modules>
. What does it do? it accumulates the Declarations
of each module in the list in one big Declaration
list.
The API returns
Now that we’ve got this important component set up, let’s continue with the framework’s API.
Here you can see the implementation of the second get()
function. We also have the inject()
function just like Koin does. inject()
is just get()
but lazy.
At this point, you might have noticed the LiteKoinContext
object in the snippet above. This is basically an entry point to our system. It helps start LiteKoin and load modules just like Koin does it at the initialization.
Pay attention to the startLiteKoin()
function that is also part of our DSL. It helps load modules on app start. It uses a Lambda with Receiver of type LiteKoinContext
so that we can directly call the function modules()
of the latter.
With all this work, we are now able to start LiteKoin
like this:
startLiteKoin {
modules(mod1 + mod2)
}
Looks familiar right?
Ok, take a break. I was serious when I was talking about coffee 😏.
We’re almost done here but there is only one more thing that I have to tell you.
Declarations are The Key
If you take a look at our ServiceLocator
class, you will see that it registers and provides Services
but Modules
on the other hand, register Declarations
and declarations are lambda functions. That is actually the real trick about this framework. We keep working with Declarations
until the moment we have to load the modules in the ServiceLocator
. Then, we will convert the Declarations
into Services
.
Kotlin is such a great language, it let us create an extension function on a type alias even when the underlying type is a lambda function.
We can then take advantage of that and create an extension function of Declaration
You can replace this()
with this.invoke()
if you want.
With this, we can now complete the ServiceLocator
and add two new functions to it. The full implementation of our ServiceLocator
looks like this:
Pay attention to line 19. addService(it.value.toService)
this is where we convert a Module’s Declaration
into a Service
And we are done!
Now look at what we can do with this framework:
If you run this code, you will see this in the console:
Text from repository
That means it works! We have successfully declared three dependencies in two modules, started LiteKoin with those modules, and used inject()/get()
to create an instance of the ViewModel.
Last words
Of course, it’s not perfect. You will probably find several things to improve but as I said, this is a naive approach and the real Koin framework is much more complex than that. I simply built this in my spare time on a weekend. So, it’s not perfect at all. But if you check out Koin source code you may find some little similarities.
We deliberately left out some concepts like single
and scope
. But If you think about it for a second, implementing Scope cannot be very complicated, we could, for example, move the ServiceLocator
access to the Scope
class, then the LiteKoin
class will handle a list of Scopes and a global Scope. Anyways, scopes are just out of the “scope” of this post (Yes, I made a pun 😏).
The full source code is available on Github.
Improve it, rethink it, break it, do whatever you want with it. If you want to improve my version, just send your PRs. You can use it for your toy projects or maybe in your little Gradle plugins? I don’t know. Just take it as a base for your own simplified version of Koin.
I believe Arnaud Giuliani and all the contributors to Koin are doing an amazing job. This post is a humble tribute to them.
Thank you for reading 😉.