Dependency Injection with Koin on Android.
Koin
can bring easy dependency injection to android, standalone or web application projects. This tutorial will show how to use Koin
on your Android project.
If you just want to download the code and check it by yourself, the repository is : https://github.com/lluzalves/KoinApp
But what is dependency injection anyways?
An application can be composed of many objects that will eventually have to collaborate to perform tasks. When this happens, we call it a dependency.
Because of its dependencies, a class will have to obtain references to the required dependents objects and that will make it committed to a particular set of objects. Most of the time these required dependencies are passed through the constructor.
When an application becomes more complex and grows in size it might have issues related to code reusability and an increasing need to apply refactor. To handle this kind of issue, we can make use of the dependency injection technique, by diverging usage and creation of dependencies.
Let’s add Koin to our project!
To add Koin to the project, open the build.gradle
file and add the following Koin dependency
and sync :
implementation "org.koin:koin-android:2.0.1"
testImplementation "org.koin:koin-test:2.0.1"
testImplementation "com.nhaarman:mockito-kotlin:1.5.0"
If the application is modularized, change implementation
to api
so you can access Koin
it in other modules, if required.
Let’s write our first Koin module!
Now, create a Kotlin file named ApplicationModules
, it will contain the dependencies we want to inject.
A Koin
module is a space to declare components. It allows you to provide a collection of objects that will persist with the entire context lifetime that it belongs to.
To define a module use the module
block and declare the dependencies inside of it:
Now let’s initialize Koin!
In case the project don’t have a custom Application class
, you will need to create one and declare it in your manifest file.
Your custom App class should override OnCreate()
method and call startKoin()
, it is responsible to return a KoinApplication
instance that represents a Koin Container
configuration with all the modules and definitions that was declared in startKoin()
. Attention, startKoin()
should only be called once.
androidLogger()
is used to set the Koin log level.
androidContext()
can be used to set the application Context
to the Koin Container
but also to inject it when necessary.
modules()
is used to set the modules that we have declared to be attached to the container.
To finish this process open the Manifest
, and set the value of the property android:name
to your custom App class.
Injection time!
For Koin
we can have 3 types of scope
. We can define it as:
single
, which is a singleton
, this type of scope
will persist for the entire application lifetime.
factory
, for this type, Koin
will provide a new object for each time we require an instance.
scoped
, in this case, Koin
will provides an object that will persist as long the associated scope
lifetime exists.
In this tutorial, we will only talk about single
and factory
.
Use case as single
.
The most common ways to resolve dependencies with Koin
is by: implementing the KoinComponent
interface or providing the required dependency through constructor injection
.
Implementing the KoinComponent
interface is faster, it gives the class access to Koin
features like get()
and inject()
but we can solve this with a better approach by using constructor injection
.
The constructor injection
approach makes the process simpler, because the class will only care about the dependencies it needs and not about who will satisfy them.
It offers a more robust way to have consistent state, as soon as the class is constructed and not only when the field injection is completed and it makes testing easier because we are declaring the dependencies in the constructor.
Approach 1 - Constructor injection.
Let’s say your application needs to store and provide data using SharedPreferences
, this class should be able to provide a way to put and retrieve data from a SharedPreferences
and it will also have a Context
dependency.
When we started Koin
, the Context
was injected and now can be retrieved by calling androidContext()
.
We are going to declare a single
, and pass the Context
to the object constructor, that way Koin
can provide the AppPreferences
dependency that we want to use.
In our Activity
, Fragment
or Service
classes, it is easy to retrieve declared instances from Koin
modules. Theses classes have access to Koin
features like:
by inject()
— lazy injected
get()
— eager injected
release()
— release module’s instance
getProperty() / setProperty()
— get/set property
In this case, we will make use of by inject()
, to lazy inject the AppPreferences
.
Now it is possible to inject the AppPreferences
instance and use it to put or retrieve data from SharedPreferences
.
Approach 2 - Implementing the KoinComponent
.
If AppPreferences
implements the KoinComponent
, it gives the class access to the same Koin
features that we have in the MainActivity
, so it is possible to use by inject()
to inject the Context
lazily in AppPreferences
.
Use case as factory
.
While single
provides a unique instance during the entire application lifecycle, factory
will provide a new instance of the requested dependency every time it needs to be injected.
Let’s improve our previous example by applying the MVP design pattern to make use of factory
.
Now that things are more clear, the Presenter
will be responsible to return the profile name and should notify the View
when everything is completed, the MainActivity
that implements the View
will be responsible to show the data and it will have the presenter lazily injected by Koin
.
To declare the presenter dependency, we will use factory.
As long as a MainContract.MainView
is given, the factory
will return a new MainPresenter
instance. Add the following code to applicationModule
file :
MainView
needs to be provided so Koin
can be able to inject the presenter in the MainActivity
, to do this use paramentersOf()
expression available in Koin
, it allows to indicate input arguments for the object constructor and it will pass the required view to the presenter factory
.
Let’s Test.
Let’s see if the presenter is behaving as expected. The test class should implement KoinTest
interface, so we can do our injections properly. Koin needs to be started before we run the tests.
By using Mockito.verify()
we are checking if after calling presenter.onComplete()
any interaction happened to our view mock, specifically the showData()
method.
Now just run the test and see the result. That’s it for now. Thanks for reading. :)