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.

Leave a rating/review
Download materials
Save for later
Share

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.

Note: This tutorial assumes you’re familiar with Android development and Android Studio. If these topics are new to you, read Beginning Android Development and Kotlin for Android tutorials first.

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:

RWNEws App

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:

RWNEws Architecture

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:

Code Structure for di package

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:

Hilt component and scope hierarchy

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.

Note: By default, Hilt uses a unique definition for each scoped component. For example, if your app has several Fragments using different objects, Hilt will generate a single class called 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"
  }
}
Note: Version 2.28-alpha is the version available at the time of writing. When you read this tutorial, you might need to update this value.

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:

  1. Applied the Hilt plugin to RWNEws.
  2. 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.

Note: Unfortunately, the app won’t build successfully until you finish the refactoring. Fasten your seat belt, code along and everything will be fine :]

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:

  1. Defines a class that extends from Application.
  2. Creates an AppComponent property, which is the current component with Singleton scope.
  3. Creates the actual instance of the AppComponent implementation.
  4. 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.