Dependency injection with Kotlin using the Koin library
by Elly
Many experienced Java Developers are familiar with Dagger, some may have used libraries like Guice, Weld or the Context Dependency Injection (CDI) available in the Spring framework for managing dependencies in their apps. With the adoption of Kotlin you may be wondering which tool/library to use for dependency injection in your next app. Whereas you can use Dagger (for all Dagger lovers out there) in a Kotlin app, thanks to the 100% Kotlin interoperability with Java, there are a couple of Kotlin libraries designed specifically for this. Our focus here will be exploring the Kotline dependency injection library Koin, but another library that’s worth mentioning is KODEIN.
Koin is a lightweight dependency injection framework written purely in Kotlin that uses neither code generation, nor proxies, nor reflections. It’s inspired by Dagger, and thus transitioning from Dagger is not much of a pain since the concepts are related. But before we dive into dependency injection using Koin, what is dependency injection and why is it useful?
Dependency injection is a programming technique that makes a class independent of its dependencies by decoupling the usage of an object from its creation. It’s derived from the fifth principle of the famous object oriented programming principles S.O.L.I.D designed by Uncle Bob. (Here is a nice article on the S.O.L.I.D principles.) As per the S.O.L.I.D principles a class should concentrate on fulfilling its own responsibilities and should not be concerned with creating objects to fulfill those responsibilities, and that’s where dependency injection comes in.
Consider class A
that depends on class B
. We can naively create an instance of B
in the constructor of class A
such that the instance of class B
is always supplied when we create an object of class A
. (Easy isn’t it, but hold on). Suppose the configuration of class B
changes? Then we would need to adapt all the instances of class B
to use the new configuration in every place that class B
has been used in the project. To add more complexity, what if B
depends on other classes? You can imagine the hassle involved, it’s a sure path to spaghetti code.
Benefits of dependency injection
Your team can benefit from dependency injection (DI) in these ways (note that DI can sometimes be overused yielding less benefits so be sure to use it meaningfully)
- Assists in unit testing
- Reducing boilerplate code, because dependency initialization is handled separately by the injector component
- Loose coupling and strong cohesion of code
- Easy to extend/modify applications
Ways of injecting dependencies
There are 3 common ways to inject dependencies:
- Constructor injection – the dependencies are provided through a class constructor (most common).
- Setter injection – the client exposes a setter method that the injector uses to inject the dependency.
- Interface injection – the dependency provides an injector method that will inject the dependency into any client passed to it. Clients must implement an interface that exposes a setter method that accepts the dependency.
Koin – Dependency Injection Framework
As mentioned, Koin is a lightweight dependency injection framework written in the Kotlin language that uses Kotlin’s functional resolution features instead of reflection. Koin does not make use of code generators through annotations and does not use proxies. For the purpose of our demo we shall use Koin in a simple Git client Android app. Let’s dig in.
The first step in our application is to add the Koin dependency to the build.gradle
file.
// Add Jcenter to your repositories if needed
repositories {
jcenter()
}
dependencies {
// Koin for Android
implementation 'org.koin:koin-android:2.0.0-rc-1'
}
Our components
Let’s create a UsersRepository
to provide some data.
interface UserRepository {
fun fetchUsers(): List<GithubUser>
}
class UserRepositoryImpl() : UserRepository {
override fun fetchUsers(): List<GithubUser>{
return listOf(
GithubUser(name = "Benerd", username = "@bernd23"),
GithubUser(name = "Abardb Barim", username = "@barimls"),
GithubUser(name = "Leundy Marib", username = "@maribxy"),
GithubUser(name = "Martin ", username = "@malcomx"),
GithubUser(name = "Brunette Rasly", username = "@raslybr"),
)
}
}
We are just making things short and simple here by returning a hard-corded list of github users. Normally this information can be obtained from Github’s REST API.
GithubUser
is the model class with only two properties, name
and username
, created using the Kotlin data
class.
data class GithubUser(var name: String?, val username: String)
Now let’s create a presenter class to consume the UsersRepository
class we implemented earlier.
class GithubUserPresenter(val repo: UserRepository) {
fun getGithubUsers(): List<GithubUser> = repo.fetchUsers()
}
Declaring Modules
Modules are declared in Koin using the module function.
val appModule = module {
// Single instance of UserRepository - Singleton
single<UserRepository> { UserRepositoryImpl() }
// Simple Presenter Factory - new instances created each time
factory { GithubUserPresenter(get()) }
}
NOTE: When a class is declared as
factory
a new instance will be created each time it is needed. Classes declared assingle
are instantiated once and the same instance is used throughout the lifetime of the application.
Starting Koin
Once the modules are declared we can start Koin at the application level. To do this we will need to extend the Android Application
class. Remember to add the GitApplication
class to the Android manifest.xml
.
class GitApplication : Application(){
override fun onCreate() {
super.onCreate()
// Start Koin
startKoin {
androidLogger()
androidContext(this@GitApplication)
modules(appModule)
}
}
}
The startkoin()
function will start Koin and load the dependencies we defined in the module. The androidLogger()
function makes it possible to receive logs from Koin.
Injecting dependencies
Now that we have started Koin, let’s see how to inject a dependency in our MainActivity
class. There are two ways to retrieve instances of our components (using inject
or get
functions). The inject
function allows us to retrieve Koin instances at runtime (lazily) whereas get
directly retrieves the instance (eagerly).
class MainActivity : AppCompatActivity() {
// Lazy injected GithubUserPresenter
val githubPresenter: GithubUserPresenter by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("GithubUsers", githubPresenter.getGithubUsers())
}
}
Here we are simply retrieving the list of github users and logging to the Android console.
When the GithubUserPresenter
object is created it is also supplied the UserRepository
class that it depends on.
Conclusion
That’s it folks. We’ve just shown how easy it is to inject dependencies with the Koin DI framework. To quickly recap the flow when working with Koin: begin by creating components, next expose your components through a Koin module, then start koin, and finally inject you dependencies as desired. If you want to take this further, take a look at the full documentation of Koin.
Happy coding!