How to replace Koin with Hilt for Dependency Injection

Bhavesh Sharma
Powerplay
Published in
4 min readOct 19, 2022

--

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

  1. At first, we created module to inject the API interface classes.
  2. Secondly, APIs were injected into the Repository classes.
  3. Then we injected the Repositories into the ViewModels.
  4. We have logical components as well in our code. We call them Utils or UseCases. So now it’s their time. They were also crushed in this. ☠️
  5. After that, all Activities, Fragments, & Views were targeted.
  6. We have achieved 70% DI in our app by Hilt, and it is running smoothly.
  7. 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.
  8. For better or extracted implementation of our worker classes, we have to build WorkerInterfaces.
  9. In the end, finally, the Application class & build.gradle is freed from Koin.

A glimpse of common code & problems with solutions/tips

  1. 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 and AssistedInject 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 calling OneTimeWorkRequest.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 the views/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. ✨

--

--

Bhavesh Sharma
Powerplay

GSoC@21 | Android App Developer | Open Source Enthusiastic