Home » JVM Languages » Kotlin » Kotlin dependency injection with Koin

About Michael Scharhag

Michael Scharhag
Michael Scharhag is a Java Developer, Blogger and technology enthusiast. Particularly interested in Java related technologies including Java EE, Spring, Groovy and Grails.

Kotlin dependency injection with Koin

Dependency injection is a common technique in today’s software design. With dependency injection we pass dependencies to a component instead of creating it inside the component. This way we can separate construction and use of dependencies.

In this post we will look at Koin, a lightweight Kotlin dependency injection library. Koin describes itself as a DSL, a light container and a pragmatic API.

Getting started with Koin

We start with adding the Koin dependency to our project:

1
2
3
4
5
<dependency>
    <groupId>org.koin</groupId>
    <artifactId>koin-core</artifactId>
    <version>2.2.2</version>
</dependency>

Koin artifacts are available on jcenter.bintray.com. If not already available you can add this repository with:

1
2
3
4
5
6
7
<repositories>
    <repository>
        <id>central</id>
        <name>bintray</name>
        <url>https://jcenter.bintray.com</url>
    </repository>
</repositories>

Or if you are using Gradle:

1
2
3
4
5
6
7
repositories {
    jcenter()    
}
 
dependencies {
    compile "org.koin:koin-core:2.2.2"
}

Now let’s create a simple UserService class with a dependency to an AddressValidator object:

1
2
3
4
5
6
7
class UserService(
    private val addressValidator: AddressValidator
) {
    fun createUser(username: String, address: Address) {
        // use addressValidator to validate address before creating user
    }
}

AddressValidator simply looks like this:

1
2
3
4
5
class AddressValidator {
    fun validate(address: Address): Boolean {
        // validate address
    }
}

Next we will use Koin to wire both components together. We do this by creating a Koin module:

1
2
3
4
val myModule = module {
    single { AddressValidator() }
    single(createdAtStart = true) { UserService(get()) }
}

This creates a module with two singletons (defined by the single function). single accepts a lambda expression as parameter that is used to create the component. Here, we simply call the constructors of our previously defined classes.

With get() we can resolve dependencies from a Koin module. In this example we use get() to obtain the previously defined AddressValidator instance and pass it to the UserService constructor.

The createdAtStart option tells Koin to create this instance (and its dependencies) when the Koin application is started.

We start a Koin application with:

1
2
3
val app = startKoin {
    modules(myModule)
}

startKoin launches the Koin container which loads and initializes dependencies. One or more Koin modules can be passed to the startKoin function. A KoinApplication object is returned.

Retrieving objects from the Koin container

Sometimes it necessary to retrieve objects from the Koin dependency container. This can be done by using the KoinApplication object returned by the startKoin function:

1
2
// retrieve UserService instance from previously defined module
val userService = app.koin.get<UserService>()

Another approach is to use the KoinComponent interface. KoinComponent provides an inject method we use to retrieve objects from the Koin container. For example:

1
2
3
4
5
6
class MyApp : KoinComponent {
    
    private val userService by inject<UserService>()
 
    ...
}

Factories

Sometimes object creation is not as simple as just calling a constructor. In this case, a factory method can come in handy. Koin’s usage of lambda expressions for object creation support us here. We can simply call factory functions from the lambda expression.

For example, assume the creation of a UserService instance is more complex. We can come up with something like this:

01
02
03
04
05
06
07
08
09
10
11
val myModule = module {
 
    fun provideUserService(addressValidator: AddressValidator): UserService {
        val userService = UserService(addressValidator)
        // more code to configure userService
        return userService
    }
 
    single { AddressValidator() }
    single { provideUserService(get()) }
}

As mentioned earlier, single is used to create singletons. This means Koin creates only one object instance that is then shared by other objects.

However, sometimes we need a new object instance for every dependency. In this case, the factory function helps us:

1
2
3
4
5
val myModule = module {
    factory { AddressValidator() }
    single { UserService(get()) }
    single { OtherService(get()) } // OtherService constructor takes an AddressValidator instance
}

With factory Koin creates a new AddressValidator objects whenever an AddressValidator is needed. Here, UserService and OtherService get two different AddressValidator instances via get().

Providing interface implementations

Let’s assume AddressValidator is an interface that is implemented by AddressValidatorImpl. We can still write our Koin module like this:

1
2
3
4
val myModule = module {
    single { AddressValidatorImpl() }
    single { UserService(get()) }
}

This defines a AddressValidatorImpl instance that can be injected to other components. However, it is likely that AddressValidatorImpl should only expose the AddressValidator interface. This way we can enforce that other components only depend on AddressValidator and not on a specific interface implementation. We can accomplish this by adding a generic type to the single function:

1
2
3
4
val myModule = module {
    single<AddressValidator> { AddressValidatorImpl() }
    single { UserService(get()) }
}

This way we expose only the AddressValidator interface by creating a AddressValidatorImpl instance.

Properties and configuration

Obtaining properties from a configuration file is a common task. Koin supports loading property files and giving us the option to inject properties.

First we need to tell Koin to load properties which is done by using the fileProperties function. fileProperties has an optional fileName argument we can use to specify a path to a property file. If no argument is given Koin tries to load koin.properties from the classpath.

For example:

01
02
03
04
05
06
07
08
09
10
val app = startKoin {
    
    // loads properties from koin.properties
    fileProperties()
     
    // loads properties from custom property file
    fileProperties("/other.properties")
     
    modules(myModule)
}

Assume we have a component that requires some configuration property:

1
class ConfigurableComponent(val someProperty: String)

.. and a koin.properties file with a single entry:

1
foo.bar=baz

We can now retrieve this property and inject it to ConfigurableComponent by using the getProperty function:

1
2
3
val myModule = module {
    single { ConfigurableComponent(getProperty("foo.bar")) }
}

Summary

Koin is an easy to use dependency injection container for Kotlin. Koin provides a simple DSL to define components and injection rules. We use this DSL to create Koin modules which are then used to initialize the dependency injection container. Koin is also able to inject properties loaded from files.

For more information you should visit the Koin documentation page. You can find the sources for this post on GitHub.

Published on Java Code Geeks with permission by Michael Scharhag, partner at our JCG program. See the original article here: Kotlin dependency injection with Koin

Opinions expressed by Java Code Geeks contributors are their own.

(0 rating, 0 votes)
You need to be a registered member to rate this.
Start the discussion Views Tweet it!
Do you want to know how to develop your skillset to become a Java Rockstar?
Subscribe to our newsletter to start Rocking right now!
To get you started we give you our best selling eBooks for FREE!
1. JPA Mini Book
2. JVM Troubleshooting Guide
3. JUnit Tutorial for Unit Testing
4. Java Annotations Tutorial
5. Java Interview Questions
6. Spring Interview Questions
7. Android UI Design
and many more ....
I agree to the Terms and Privacy Policy
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments