Dagger-android to Hilt Migration— A Successful Attempt
At slice, we’ve bi-monthly android meeting and while discussing how we can improve our codebase, someone suggested dagger-android to hilt migration. The first thing we asked is why do we need to do so. In our codebase, dagger-android was working “perfectly” but we all know the complexities that it brings like when implementing factories for AndroidX libraries like ViewModels and WorkManager. For example, suppose we have a repo class that needs to be injected in viewmodel. In order to use that viewmodel, we’ve to create a viewmodel factory, and to do so we usually use multibinding, which is one of the “most complex” topics in dagger in general.
Reasons:
- Understanding existing dagger implementation for junior developers can be challenging and time-consuming and Hilt is relatively easy to start with(as per our experience).
- Hilt generates a lot of boilerplate codes for us(example, generating components, factories for viewmodel/workmanager)
- Interoperability of hilt and dagger. Hilt is basically a wrapper over dagger2, so most of the hilt conventions are just the same as that of dagger. So, this also means that you can also migrate your codebase gradually(or in our case, one module at a time).
- Last but not least, if you have made up your mind to do the migration. there are a lot of articles by the android community that can help you with this migration a lot.
Steps:
We had a few modules(ActivityInjectorModule
, ViewModelModule
) that defines which subcomponents dagger-android should create, generated with @ContributesAndroidInjector
. And, each activity and fragment has its own subcomponents generated with the same in its own module.
The first thing that we did was, adding @HiltAndroidApp
to our application class and then going to all the fragments and activities that were mentioned in those modules, and annotating them with @AndroidEntryPoint
. For other modules that are used for other types of binding like helper classes, we added @InstallIn
annotation and passing component class as needed.
@InstallIn(SingleComponent::class)
@Module
class ExternalDependenciesModule {
Now, the next step is opening ViewModelModule
class and opening all viewmodels mentioned in that class, and adding @HiltViewModel
annotation such that we don’t have to use any factory to instantiate viewmodels in our activities or fragments.
@HiltViewModel
class PricingViewModel @Inject constructor(
private val pricingUseCase: PricingUseCase
) : ViewModel() {
We also use a dynamic feature module and at the time of writing this article, Hilt doesn’t support dynamic feature module yet. Just to give you an idea that since dynamic feature modules are downloaded and installed on demand, this imposes some problems in constructing DI graph. So basically, dagger-android tree cannot be constructed because it hard-codes the Subcomponents from the ContributesAndroidInjector
annotated classes and it generates these codes at compile time. This is a good article explaining the same if you’re interested.
What we did was, created a module interface annotated with @EntryPoint
and passing SingleComponent::class
in InstallIn
annotation and then used that module to pass any dependencies that we require in our dynamic feature module.
@EntryPoint
@InstallIn(SingletonComponent::class)
interface HnsModuleDependencies {
And then using this to perform field injection in the activity of our module. Check this android official documentation page for using hilt in multi-module app.
Now comes the fun part, deleting all modules that were using ContributesAndroidInjector
annotation for generating subcomponents, components class, ViewModelModule, ViewModelFactory, ViewModelKey annotation etc.
Obstacles:
- We were using
fragment
tag forNavHostFragment
to host nav graph, and after migrating to hilt, this was creating a problem in inflating the XML layout file at runtime. The solution is to useFragmentContainerView
as suggested by lint too, but I‘m still figuring out the exact reason for this. Basically, the underlying issue with the<fragment>
tag is the fragments added via that tag go through lifecycle states entirely differently from the other Fragments added to theFragmentManager
. Check out this very interesting thread on this if you’re more curious. - Patience plays a strong role in this migration as we encountered a lot of crashes and build failed issues during this migration but fortunately, there are a lot of articles and questions answered on StackOverflow that helped this migration smoothly.
Results: (+577, -2,398)
We deleted a lot of files and our codebase becomes much simpler than before and more friendly to new developers.
Conclusion:
The major takeaway from this article should be that Hilt is relatively easier to start with and has a few extra advantages over dagger. But the main point is these kinds of tasks don’t affect your customers directly and requires extra effort and bandwidth of developers. So, do make sure that you’ve some good reasons for this migration if you’re doing this in your company’s codebase and your product managers are aligned with it 😉