How to replace Koin with Hilt for Dependency Injection
Hi everyone! I am back with another blog of my previous sprints. With our rapidly increasing code size, we introduced dependency injection (DI) in our codebase. When we compared the two most popular dependency injection frameworks on Android, Koin & Dagger, we observed that Koin -
- Requires fewer lines of code to inject dependency,
- Doesn’t have many complications of the dependency graph, and
- Can inject objects anywhere, even in object class holding static references
Because of these advantages, we implemented Koin initially. Few classes down the line, however, we started to see many flaws with it, forcing us to make a switch to Dagger-Hilt. We realised Dagger-Hilt excelled in several ways too.
- Dagger-Hilt gave us errors at compile time unlike Koin which threw them at runtime, resulting in fewer bugs
- Dagger’s runtime initialisation also reduced the app’s startup time, and
- Hilt created a testable code architecture setup
Our app has 30+ network API classes, 30+ Repositories, 50+ ViewModels & 100+ views. At this scale, it was a gargantuan task to implement Hilt. Let’s discuss the architectural approach followed we followed to get this done.
Steps at a glance
- At first, we created module to inject the API interface classes.
- Secondly, APIs were injected into the Repository classes.
- Then we injected the Repositories into the
ViewModels
. - We have logical components as well in our code. We call them
Utils
orUseCases
. So now it’s their time. They were also crushed in this. ☠️ - After that, all Activities, Fragments, & Views were targeted.
- We have achieved 70% DI in our app by Hilt, and it is running smoothly.
- Next, our target is WorkManagers. We are using
WorkManagers
for a lot of tasks in our app. We have approximate 20 workers. It’s a time when we need to think about the right way of its implementation. - For better or extracted implementation of our worker classes, we have to build WorkerInterfaces.
- In the end, finally, the Application class &
build.gradle
is freed from Koin.
A glimpse of common code & problems with solutions/tips
- 8/10 time I face this issue 🤯😪
Suggestion: This is a useless error that does not signify the exact problem in your code. The solution that worked for me is to just go back to the changed files, check the Hilt annotations used and see how DI is implemented. The first time I came across it was when I missed adding some dependencies, which helps in multi-module Hilt communication.
Note: I am not getting the actual error in build root.
2. How to implement DI if some data is to be passed in the ViewModel
constructor at runtime and other dependencies are injected by Dagger. I have found the following three approaches work best:
- Dagger factory + components, etc. (lengthy approach)
- Use an external library like
AutoFactory
, etc. - Use
AssistedFactory
in the latest version of the Dagger & Hilt.
I chose the last way. Here, we write the function signature and leave other things to Hilt. Following is a code snippet of AssistedFactory’s usage.
3. Use of SavedStateHandle
in ViewModel
Sometimes you need some data in ViewModel
, which is coming from the intent of the activity. So Activity-A passes data to Activity-B. Activity-B extracts the info and passes it to Activity-B’s ViewModel
via the constructor. There is a better approach to do it.
Correct: With the help of SavedStateHandle
, you can access the corresponding view’s intent or bundle data directly into the ViewModel
. There is no need to pass it from activity/view. It's Hilt's responsibility to inject it into ViewModel
.
4. Dagger does not support injection into static scope
Some of our static methods are in the companion objects but depend on the external classes. These methods' dependencies are easily injected by Koin, But it is not possible with the Dagger. So we moved the methods out of static scope.
For some classes, we are bound to create them as object classes due to their heavy uses. For them, I came up with an idea to inject a dependency into them.
Example: SharedPreference
is one such object which needs context to build its object, which is used in our BaseSharedPreference
class. But, we want our BaseSharedPreference
to be an object instead of a class due to its heavy uses. Since it’s a one-time initialisation, memory consumption is not a big problem for us. We passed context from the Application
class via a setter.
5. Hilt with WorkManagers
- I have used the
HiltWork
annotation andAssistedInject
to inject workers into the app - We are utilising the same worker at multiple places. Therefore, we have moved the work request creation to the
Worker
class instead of callingOneTimeWorkRequest.Builder
multiple places - But the problem is that we can’t create the object of a worker class annotated with
AssistedInject
, so we can’t call our common work request creation function from theviews/ViewModels
. - It’s a challenging implementation where we extracted its uses with the help of interfaces. Now we are injecting the WorkerInterface into the
views/ViewModels
, and calling work requests creation functions with their help.
6. What to do for the classes where Hilt doesn’t provide direct support to add them to Hilt Graph?
Solution: Create EntryPoint Interface for them and inject the required dependencies through it.
Powerplay: We at Powerplay are reimagining construction management one step at a time; Have a look at the app. Link for Android and Apple.
Do you want to join us? Check out Powerplay Career
Thank you for reading, hope it helps. Feedback & suggestions are most welcome in the comment section. ✨