Migrating From Dagger to Hilt
Learn about Hilt and its API. Discover how Hilt facilitates working with Dagger by migrating the code of an existing app from Dagger to Hilt. By Massimo Carli.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Migrating From Dagger to Hilt
20 mins
- Getting Started
- Understanding Hilt Design Principles
- Using Predefined Scopes and Components
- Adding Hilt Dependencies
- Fixing ApplicationComponent
- Understanding Hilt’s Code Generation
- Removing AppComponent
- Refactoring @FeatureScope
- Understanding Entry Points
- Using @AndroidEntryPoint With Activities
- Using @AndroidEntryPoint With Fragments
- What About @Scopes?
- IDE Support for Hilt
- Where to Go From Here?
Dagger is notorious for its steep learning curve. It requires the developer to know many coding principles and the code it generates isn’t intuitive. Google made an effort to simplify Dagger by trying to standardize its components, improve its performance, and making it easier to implement with different build types like test, debug and release.
The result of Google’s work is Hilt, a library that makes Dagger easier to use. Hilt uses Dagger underneath, and is, therefore, not a different dependency injection framework.
In this tutorial, you’ll learn:
- The main design decisions behind Hilt.
- How to configure Hilt with Gradle in your project.
- Which standard components and scopes Hilt provides and how to use them in your app.
- How to migrate the scoped component in your existing app to Hilt.
- What an entry point is and why you might need one.
Note is that Hilt is Android-only, which means that you can’t use it on the server side.
This is the third installment of a multi-part Dagger tutorial. If you’re not familiar with Dagger, read these two tutorials before starting: Dagger 2 Tutorial For Android: Advanced and Dagger 2 Tutorial for Android: Advanced – Part 2.
This is the third installment of a multi-part Dagger tutorial. If you’re not familiar with Dagger, read these two tutorials before starting: Dagger 2 Tutorial For Android: Advanced and Dagger 2 Tutorial for Android: Advanced – Part 2.
Now, it’s time to dive in.
Getting Started
Download and unzip the materials for this tutorial using the Download Materials button at the top or bottom of this page. Open the starter project using Android Studio 3.5 or greater, then build it and run it. You’ll see this:
In this tutorial, you’ll work on RWNEws, a basic app that displays a list of news items. The user can select any news item to read its content.
The app is very simple, but its functionality isn’t as important as its internal construction.
Look at the project structure in Android Studio. It uses a classic Model–View–Presenter architectural pattern with definitions as in the following UML (Unified Modeling Language) class diagram:
This is the same app from the previous tutorials. It uses Dagger, but you’ll migrate it to Hilt. Before doing so, you need some theory, however. :]
Understanding Hilt Design Principles
In this section, you’ll learn about one of the most important design principles for Hilt.
In Android Studio, look at the code within di:
As you see, this package contains, among other things, two different components: AppComponent
and FeatureComponent
.
The main difference between them is the scope. AppComponent
uses a @Singleton
scope, whereas FeatureComponent
applies a @FeatureScope
scope. As you know, every Android app contains objects with a lifecycle that can be bound to different parts of the app, like Application, Activities or Fragments.
The main change that Hilt introduces to help the developer is using predefined components for predefined scopes. This is quite similar to the pattern used in RWNEws, as you saw previously.
This approach simplifies the code and reduces the developer effort since you don’t need custom components and scopes. Different apps can now use the same naming conventions. If all components and scopes are the same, you can more easily share code between different apps.
Using Predefined Scopes and Components
You don’t need to define custom components anymore, since Hilt comes with a set of predefined components that it generates for you.
Look at the following:
In the diagram, each rectangle represents a predefined component together with its corresponding scope annotation. You also see arrows that represent dependency relationships between components. A component can access dependencies on any ancestor component. For instance, an object annotated with @ActivityScope
can access any @Singleton
scoped component.
Everything you learned about dealing with @Subcomponent
and @Component
dependencies is no longer relevant when working with Hilt.
FragmentComponent
. Each Fragment will have its own instance using the different scoped component instances.
Alright, enough theory for the moment. It’s time to start writing some code. :]
Adding Hilt Dependencies
To use Hilt, you must install a Gradle plugin. To do this, open your project’s root build.gradle and add the following definitions in the buildscript
section:
ext.hilt_android_version = "2.28-alpha"
Then add the following to the dependencies
section:
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_android_version"
The result should look like this:
buildscript {
- - -
ext.hilt_android_version = "2.28-alpha"
- - -
dependencies {
- - -
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_android_version"
}
}
Now, apply the plugin like this:
- - -
apply from: '../versions.gradle'
// 1
apply plugin: 'dagger.hilt.android.plugin'
android {
- - -
}
dependencies {
- - -
// 2
implementation "com.google.dagger:hilt-android:$hilt_android_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_android_version"
- - -
}
With these changes you:
- Applied the Hilt plugin to RWNEws.
- Added the runtime dependencies for Hilt and for the kapt compiler.
Now, remove the existing Dagger dependencies, since you don’t need them anymore:
implementation "com.google.dagger:dagger:$dagger_version" // REMOVE
kapt "com.google.dagger:dagger-compiler:$dagger_version" // REMOVE
Build and run the app now and you’ll get an error like this:
[Hilt]
com.raywenderlich.rwnews.di.AppModule must also be annotated with @InstallIn.
[1;31m[Hilt] Processing did not complete.
At this stage, RWNEws is broken and needs your help to work again. Your next step will be to fix ApplicationComponent
.
Fixing ApplicationComponent
Hilt uses a dependency graph to represent how each component depends on the others. To build this graph, Hilt needs a single point from which to start generating the standard components you learned earlier.
In this single place, the app needs to access all the classes, with different scopes, of all the objects you need to eventually inject. You can achieve this with an Application
object annotated with @HiltAndroidApp
.
Next, you’ll use @HiltAndroidApp
to refactor the current implementation.
Open init/InitApp.kt within the app module. At the moment, the code in this file is as follows:
// 1
class InitApp : Application() {
// 2
lateinit var appComponent: AppComponent
override fun onCreate() {
super.onCreate()
// 3
appComponent = DaggerAppComponent.create()
}
// 4
fun appComp() = appComponent
}
Here’s a breakdown of this code:
- Defines a class that extends from
Application
. - Creates an
AppComponent
property, which is the current component with Singleton scope. - Creates the actual instance of the
AppComponent
implementation. - Adds a utility method for accessing
AppComponent
from other places in the code.
Now you don’t need to create any components because Hilt does that for you. Hilt already knows that there must be a component with the Application scope, so you can replace the previous code with the following:
@HiltAndroidApp
class InitApp : Application()
With this code, you’re telling Hilt to generate the dependency graph that you’ll use to facilitate the injection later. All apps using Hilt must contain an Application
annotated with @HiltAndroidApp
.
Wow, the first step wasn’t bad at all! You removed a bunch of lines of code. :]
Build and run now — you’ll still have a compilation error. Before you fix that, read on to understand what happened with the code generated in this step.