This article is a guest post from Caroline Riekert
Table of Contents
Motivation
There are a lot of reasons to use Dependency Injection, or even a Framework for it.
In this Article you’ll learn what Dependency Injection is, what benefits and downsides it has and how to use it with the usage of the Koin Framework.
Let’s look at a example of a House with a Doorbell to better understand what dependency injection is.
The House class has a function that returns a String representing the current Ringtone according to the installed doorbell.
Without Dependency Injection:
class House() { private val myDoorbell: Doorbell init { myDoorbell = SpeakingDoorbell() } fun ringDoorbell(): String { return "Current ringtone " + myDoorbell.getRingtone() } } fun main() { val myHouse = House() println(myHouse.ringDoorbell()) }
With Dependency Injection:
class House(private val myDoorbell: Doorbell) { fun ringDoorbell(): String { return "Current ringtone " + myDoorbell.getRingtone() } } fun main() { val myDoorbell: Doorbell = SpeakingDoorbell() val myHouse = House(myDoorbell) println(myHouse.ringDoorbell()) }
As you can see in the variant without Dependency Injection the House class has a direct dependency to the SpeakingDoorbell class.
With Dependency Injection this reference no longer exists. The only reference
remaining is to the generalized Doorbell Interface.
Benefits of Dependency Injection
Flexibility
Lets suppose another class FunnyDoorbell should be used in the future, which also implements the Doorbell interface.
Exchanging the SpeakingDoorbell class later on is less work with Dependency Injection.
In the first example the House class would have to change its implementation to switch to the FunnyDoorbell class.
But with Dependency Injection this change can be done without touching the House class.
In fact the House class does not even know whether it got an instance of SpeakingDoorbell or FunnyDoorbell.
This allows to write more flexible code which can be modified and extended easier.
A looser coupling between classes is achieved.
Testability
When writing tests one cannot test the House class in the first example in isolation to the SpeakingDoorbell class.
With Dependency Injection it’s very easy to insert mocks instead of the real instance.
Downsides of Dependency Injection
There is one part that got more complex though. The main function now knows not only the House class, but also the SpeakingDoorbell class.
This ultimatley leads to a really big main function, that knows almost every component.
In this small example this is no issue, but when developing large applications we want the benefits of Dependency Injection, but as few downsides as possible.
This is exactly where Dependency Injection Frameworks comes into play!
They help to structure the whole block how and when to instantiate which classes and usually bring with them a bunch of useful features.
So let us look into the Dependency Injection Framework Koin.
Setting up Koin
Gradle
Add the following to your gradle configuration.
Get the latest Koin version [here](https://insert-koin.io/docs/setup/v3).
// Add Maven Central to your repositories if needed repositories { mavenCentral() } dependencies { // Koin for Android implementation "io.insert-koin:koin-android:$koin_version" }
Application
Create an application class if you have none yet (don’t forget to add it to the manifest).
class MyApplication : Application() { override fun onCreate() { super.onCreate() startKoin { androidContext(this@MyApplication) modules( module { single { House(get()) } single<Doorbell> { SpeakingDoorbell() } } ) } } }
Profit
That’s it! This is the basic Koin setup. As you can see the House class now even fetches its needed instance of Doorbell.
Simply call startKoin, configure the AndroidContext and define some Beans. These can also be injected like this into Android components like activities:
val myVariable: MyVariableType by inject()
ViewModel injection
The avid reader might already have noticed something. “What about our ViewModels. They have a lifecycle and should not get created just like that”.
To fix this issue there exists an extension for Koin.
Instead of declaring a Bean like that
single { MyClass() }
we use
viewModel<MyViewModelType> { MyViewModel() }
The injection is then done inside the fragment like that:
val myViewModel: MyViewModelType by viewModel()
Conclusion
Using Dependency Injection, especially with a framework, can be intimidating for a new programmer.
But in the most cases the positive aspects outweigh the negatives and the complexity of setting up a dependency
injection framework is quite low.
There are a lot of other things Koin can do like defining factories instead of singles, directly helping out with tests, instantiating fragments, or much more.
Have a glance at the official documentation