Loading...
Home
  • Tech Blogs
  • Videos
  • Conferences
    • Droidcon News
    • Upcoming Conferences
    • Become a Partner
    • Past Events
    • Keep Me Informed
    • Diversity Scholarships
  • Community
    • droidcon Team
    • How to Hold a Droidcon
  • Android Careers
Sign In

Global CSS

 

Let’s build our own simplified version of Koin

Demystifying the amazing Koin Dependency Injection framework by building a simplified version of it

 

 
Rygel Louv
Kotlin, Android, and Python developer. Passionate about Software Engineering
Published: July 28, 2020
Tweet
Share
 

Image from Micheile Henderson on Unsplash

 

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 developmentto 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 singleand scope. But If you think about it for a second, implementing Scope cannot be very complicated, we could, for example, move the ServiceLocatoraccess to the Scopeclass, then the LiteKoinclass 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 😉.

 

 

Tags: Koin, Kotlin, Android, Android App Development, AndroidDev

 

View original article at: 


 

Originally published: July 13, 2020

Android News
Compose CameraX on Android
Compose CameraX on Android

By Peng Jiang

Android new UI toolkit Jetpack compose is in beta now, which has all the features you need to build production-ready apps. CameraX is another Jetpack support library, which let you control the camera easier. As compose is still under development, lots of the views are still not available the compose way.

By ProAndroidDev -
Android News
Getting… your BottomSheetScaffold working on Jetpack Compose Beta 03
Getting… your BottomSheetScaffold working on Jetpack Compose Beta 03

By Carlos Mota

It’s Monday, no releases this week, and… there’s a new version of Jetpack Compose — beta 03—available. What a perfect time to just increment 02 to 03 and see what’s new. The API is (almost) final so after updating from alpha to beta there weren’t any big changes to do. However, and remember that’s still in development, there’s always something that I need to update. 

By ProAndroidDev -
Android News
Noisy Code With Kotlin Scopes
Noisy Code With Kotlin Scopes

By Chetan Gupta

Scopes make your code more readable? think again... You are going to encounter these scope functions namely let, run, apply, also, within every Kotlin codebase, along with all the mischievous ways developers exploit their usage from the way they were intended for. Let see how popular opinion on those ends up just as a code noise.

By ProAndroidDev -
Android News
Improving Android DataBinding with Bindables library
Improving Android DataBinding with Bindables library

By Jaewoong Eum

DataBinding is one of the most important factors for MVVM architecture. The basic concept of DataBinding is to link the view and view model via observer patterns, properties, event callbacks, etc. Linking and automating communication between the view via the bound properties or something in the view model has a lot of benefits in the MVVM architecture concept.

By ProAndroidDev -
droidcon News

Tech Showcases,

Developer Resources &

Partners

/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/home-details/EmployerBrandingHeader
EmployerBrandingHeader
https://jobs.droidcon.com/
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/jobs-droidcon/jobs.droidcon.com
jobs.droidcon.com

Latest Android Jobs

http://www.kotlinweekly.net/
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/kotlin-weekly/Kotlin Weekly
Kotlin Weekly

Your weekly dose of Kotlin

https://proandroiddev.com/
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/pad/ProAndroidDev
ProAndroidDev

Android Tech Blogs, Case Studies and Step-by-Step Coding

/detail?content-id=/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/Zalando/Zalando
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/Zalando/Zalando
Zalando

Meet one of Berlin's top employers

/detail?content-id=/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/Academy for App Success/Academy for App Success
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/Academy for App Success/Academy for App Success
Academy for App Success

Google Play resources tailored for the global droidcon community

Follow us

Team droidcon

Get in touch with us

Write us an Email

 

 

Quicklinks

> Code of Conduct

> Terms and Conditions

> How to hold a conference

> FAQs

> Imprint

Droidcon is a registered trademark of Mobile Seasons GmbH Copyright © 2020. All rights reserved.

powered by Breakpoint One