From Dagger to Koin, a step by step migration guide
In my last public talks, I see that many people appreciate the simplicity of Koin and like the way it helps them to design their applications with ease. I’m clearly happy to see good feedback from this first stable version of Koin. But one question often comes up: how can I easily migrate my Dagger app to Koin? 🧐
Sorry, there is no IntelliJ plugin for that. But a small example is always a good occasion to understand things. Then, I propose you to migrate the well known Dagger’s thermosiphon app sample to Koin, in a step by step approach. This will help you understand how you can use Koin with another DI framework, and migrate all your app to Koin in a progressive manner.
All the sources are available on Github 👉 https://github.com/InsertKoinIO/thermosiphon_dagger2koin
The application ☕️
This big picture of this app is to demo dependency injection with a few components. This app is a “coffee maker” app… it will “make the coffee”. Here is the components:
- The Heater handles the heating part with
on()
off()
isHeating()
functions, with the ElectricHeater implementation. It prints the heating step on console. - The Pump handles the water pumping actions with the
pump()
function. The thermosiphon implementation will only pump hot water. It prints the pumping step on console. - The CoffeeMaker class run the coffee making process with
brew()
function. It needs a Pump and a Heater to make it. This last is lazy, as we will create it when we only need it. It prints the final coffee step on console.
Once the coffee making process is done, we have this ascii-art:
~ ~ ~ heating ~ ~ ~
=> => pumping => =>
[_]P coffee! [_]P
The components 📦
Below are the app details written in pure Kotlin:
- The CoffeeMaker — constructor with Heater & Pump. Lazy type is from Kotlin. We use a backing property to not have to write
heater.value
every time we need it
- The Heater & its ElectricHeater implementation
- The Pump & Thermosiphon implementation — constructor with Heater
We have a small app with all the ingredients to make it ready to be assembled:
- components filled by constructor (Kotlin property constructors)
- components used by their abstractions (interfaces)
Now we need a DI technology to assemble & run it 👍
Assembling it with Dagger
To make it with Dagger, we will need to use modules and a bunch of annotations to declare everything. Check the Dagger version here and below the details:
- A
DripCoffeeModule
class to declare ourHeater
component & link it withPumpModule
class
- The
PumpModule
class to declare ourPump
component. Note that here we have to annotate theThermosiphon
class constructor with@Inject
- The
CoffeeMaker
class constructor is annotated with@Inject
and we need aCoffeeApp
module to build it with theDripCoffeeModule
(The Lazy type below is the one from Dagger)
- To run it, we have to make code generation to make our code compile(
DaggerCoffeeApp.Builder().build()
) and run it
Assembling it with Koin
To make it with Dagger, we will need to use only one module file and one KoinComponent class. You can check the Koin version here (we don’t have any impact on the original Kotlin files). Below are the details the Koin version:
- Just declare components in one Koin module (note that we use here the Lazy Kotlin type):
- we need a small class to bootstrap the
CoffeeMaker
class (just tag it withKoinComponent
and unlock theinject()
delegate function):
- we have to start Koin & we can run the
CoffeeApp
class directly
It’s pretty simple and direct to make it with Koin. DSL is clearly readable even by people who don’t use Koin. And KoinComponent
interface allows an easy access to Koin features.
A step by step migration to Koin 🚀
This sample app is small enough to be completely rewritten with Koin in one time (remove everything from Dagger and declare things for Koin). But it’s not the case for every app to go in a big bang style. We have here a good candidate to make a Dagger to Koin migration step by step.
The idea of step by step migration is to allow your app to run with instances from both Dagger factories and Koin container. How to do that?
The key of this migration is the use of KoinComponent
interface to mark a class as allowed to pickup dependencies directly from the Koin container. With this, we will be able to request dependencies from Koin inside a Dagger component.
Our work will be to declare components one by one in the Koin module, and then unplug it from Dagger’s world. Let’s go 👍
Migrating the Heater component ✨
We begin we the lowest component of the hierarchy, as it should be one of the easiest components to migrate.
Our first step consists in unplugging everything around Heater
& ElectricHeater
, and declare it in Koin. The Thermosiphon
& CoffeeMaker
classes will make the bridge between Dagger & Koin instances. To make it:
- Remove
DripCoffeeModule
class - Begin the
CoffeeAppModule
file to write our Koin module and declare our first dependency: theHeater
component - Tag
Thermosiphon
&CoffeeMaker
classes withKoinComponent
interface to lazy injectHeater
into a property, instead of getting it from its constructor - Update the
CoffeeApp
module to usePumpModule
module - Update the
main
function to start Koin container
Migrating the Pump component ✨
Let’s migrate the Pump
& Thermosiphon
components into Koin. We have to unplug every thing around it & lazy inject it into CoffeeMaker
class. This last will make the bridge between Dagger & Koin.
- in
Thermosiphon
class remove constructor annotation,KoinComponent
interface and makeHeater
injected by constructor - Declare the
Pump
component into Koin module - Remove
PumpModule
class - Update the
CoffeeMaker
class to injectPump
component as lazy property - Update the
CoffeeApp
module to not use any other Dagger module
Migrating the CoffeeMaker component ✨
Final round! Let’s unplug everything from Dagger (you can even comment daggers dependencies from your Gradle file).
- Make
CoffeeMaker
class injected by its constructor and use the Kotlin lazy type - Update the Koin module to add
CoffeeMaker
definition - Replace
CoffeeApp
module by a simple class. Tag it byKoinComponent
. Just lazy injectCoffeeMaker
intomaker
property - Update the
main
function, to runCoffeeApp
class and use itsmaker
property.
That’s it 🌈
The app is now completely managed by Koin. The Github repository has 1 commit per step, allowing to understand each phase of migration.
Any feedback? Need more help? Just go on https://insert-koin.io 👍