Dependency Injection: Dagger-Hilt vs Koin
Dependency injection from zero to hero with Koin and Hilt
Topics
- What is DI (dependency injection)?
- Why Koin and Hilt
- Koin Vs Dagger-Hilt
- Setting up Koin and Hilt
- Explanation
- Examples.
What is Dependency Injection?
As we know that, S.O.L.I.D is one of the most significant acronyms in Oops concepts. Using SOLID principle in android development could be helpful and effective to follow the clean code principle. These five basic principles of oops design by Uncle Bob(Robert C. Martin).In SOLID, It is the fifth principle is Dependency Inversion. Dependency Injection is a design pattern used to implement inverse of control, meaning the flow of an application is inverted. We can create the dependent object outside of the class and provide those objects to the class in different ways. DI can help with, moving the creation and binding of the dependent objects outside of the class that depends on them.
Implementing DI provides you with the following advantages:
- Reusability of code
- Ease of refactoring
- Ease of testing
Why Koin and Dagger-Hilt?
You can’t talk about dependency injection in Android without mentioning Dagger, right? Like every other Android developer, I’m a big sufferer of Dagger 2. It’s unnecessarily complicated, there are so many (confusing) ways of configuring it and it’s verbose.
If you are an Android developer, chances are you might have used Dagger, which, undoubtedly, is a great library to implement DI.
But android development is evolving rapidly; with Kotlin, we can write platform-agnostic code in a common module. Meaning the business logic can be written in a single module that can be used by all the supported platforms. We can’t use Dagger2 in Kotlin multi-platform projects. So I did some digging and found this cool, lightweight library called Koin(with multi-platform support). With less learning curve and boilerplate code, it seems better than Dagger2. Koin is written in pure Kotlin using functional resolution only: no proxy, no code generation, no reflection!
And what about Hilt? Hilt is the new dependency injection framework built on top of Dagger. The aim is to reduce the boilerplate code required for Dagger and make dependency injection less painful.
How Koin works ?
Koin works on a simple DSL model. Here we have to create a module first. In this module all the dependent objects are there, then we have to load one or more modules in Koin. Then, we are ready to use this object. Generally, we are loading the module into Koin in the application class by calling startKoin method, then we can inject the object wherever we want, this how the Koin works.
Setting up Koin
Understanding Terminologies in Koin
While working with Koin, there are few terminologies we need to understand before getting started.
- module — it creates a module in Koin which would be used by Koin to provide all the dependencies.
- single — it creates a singleton that can be used across the app as a singular instance.
- factory — it provides a bean definition, which will create a new instance each time it is injected.
- get() — it is used in the constructor of a class to provide the required dependency.
Now, we will create API, model, repository packages inside the data layer. It will have files like. We will understand with the reference of this package.
Now, we will create a package di -> module and inside it, we will create AppModule, ViewModelModule.
Inside AppModule first, we will create the functions we want to provide as dependencies. We will update the file like,
Here, to provide the dependency as a singleton instance we use single{} and inside it, we will pass the function which will return the dependency as an instance to be used across the app.
We are using get() here to pass the dependency to the constructor. Using get it will only provide the constructor whose instance is already been provided by Koin.
Here, AppRepository requires ApiInterface in the constructor which will be provided by the Koin here. Now, to pass the ApiInterface as a param, we need to provide it as well from the module.
Here, you can see we are not using single, and in place of that we are using viewModel and inside it, we resolve the dependencies which we are providing from viewModelModule.
Now, we are done providing the dependencies. Let us start now by injecting the dependencies.
Now, we need to create the instance of ViewModel in the GalleryFragment so the values passed in the constructor of the ViewModel are passed to it.
To pass the instance of ViewModel we will create a variable called GalleryViewModel like,
private val viewModel : GalleryViewModel by viewModel()
Here, by viewModel() creates the instance for the ViewModel and it will also resolve the dependency required by it.
To initialize Koin in the project we will update the onCreate() of the application like,
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@ImageSearchApp)
modules(listOf(appModule, repoModule, viewModelModule))
}
}
All the instances of required dependency full fill by using koin on kotlin platform in an easy way.
How Hilt Work?
Before starting with Dagger-Hilt we need to understand Dagger basics. In this section, we will help you understand the Dagger and its terminologies.
Basically, to understand Dagger we have to understand the 4 major annotations,
- Module
- Component
- Provides
- Inject
To understand it better in a basic way, think module as a provider of dependency and consider an activity or any other class as a consumer. Now to provide dependency from provider to consumer we have a bridge between them, in Dagger, Component work as that specific bridge.
Now, a module is a class and we annotate it with @Module for Dagger to understand it as Module.
A component is an interface, which is annotated with @Component and takes modules in it. (But now, this annotation is not required in Dagger-Hilt)
Provides are annotation which is used in Module class to provide dependency and,
Inject is an annotation that is used to define a dependency inside the consumer.
Same Project structure for Hilt that given above Pic.
Setting up Hilt
We will first update our Application class App like,
class ImageSearchApp : Application()
and we will update the Manifest file like,
android:name=".ImageSearchApp"
Now, to begin working with Dagger we need to annotate the application class with @HiltAndroidApp. The updated code will look like this,
@HiltAndroidApp
class ImageSearchApp : Application()
If you are planning to use Dagger-Hilt in your app, the above mention step is a mandatory one. It generates all the component classes which we have to do manually while using Dagger.
In AppRepository we will pass ApiInterface in the constructor of the repository. AppRepository looks like,
@Singleton
class AppRepository @Inject constructor(private val apiInterface: ApiInterface):ViewModel() {
fun getSearchResult(query : String) =
Pager(
config = PagingConfig(
pageSize = 20,
maxSize = 100,
enablePlaceholders = false
),
pagingSourceFactory = {PagingSourceData(apiInterface,query)}
).liveData
}
Now, if you can see we have passed ApiInterface in AppRepository. So, to inject everything in the constructor we also need to provide it using @Provide annotation in Dagger.
We will create a class ApplicationModule and annotate it with @Module. Using this annotation will make the dagger understand that this class is a module.
@Module
class AppModule { }
Now, we will need to plug this module class into the specific component. In this case, we need to this at the application level so we will install it in ApplicationComponent like,
@Module
@InstallIn(ApplicationComponent::class)
class AppModule {}
Here, you can see that we have used @InstallIn annotation to install it in ApplicationComponent. ApplicationComponent is provided by Dagger-Hilt.
This means that the dependencies provided here will be used across the application. Let’s consider that we want to use at the activity level we install the module in,
@InstallIn(ActivityComponent::class)
Similarly like ApplicationComponent/ActivityComponent, we have a different type of components like,
Now, inside AppModule
, we will provide all the dependencies one by one and the updated code of ApplicationModule class
Now, since everything is set up, now we need to use/inject them into the Android classes. In our case, we need our activity to start using them.
So to make any Android class supported by Dagger-Hilt we use,
@AndroidEntryPoint
So, in our code, we will create another package ui, and inside it will create another sub-package called main which will have MainActivity, MainViewModel, and MainAdapter to show the list of users.
Now, we will add the AndroidEntryPoint
Annotation in MainActivity like,
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {}
Here, @AndroidEntryPoint means Dagger-Hilt can now inject dependencies in this class.
@AndroidEntryPoint annotation can be used in,
- Activity
- Fragment
- View
- Service
- BroadcastReceiver
Hilt currently only supports activities that extend
ComponentActivity
and fragments that extend androidx libraryFragment.
Impact on performance
There is some advantage of the fact that Koin does not generate any code: it has a much smaller impact on our built time. Dagger needs to use an annotation processor to scan our code and generate appropriate classes. It may take some time and it may slow down our build. On the other hand, because Koin resolves dependencies at runtime it has slightly worse runtime performance.
Summary
My goal here is not to tell you which one of those libraries to use. I used both Koin and Hilt in two different, quite big projects. To be honest, I think the decision of which one to choose, Dagger or Koin, is way less important than to just anything that allows you to write code that is clean, simple, and easy to unit test. And I think both these libraries fulfil this purpose.