Chapters

Hide chapters

Dagger by Tutorials

First Edition · Android 11 · Kotlin 1.4 · AS 4.1

17. Hilt — Dagger Made Easy
Written by Massimo Carli

In the previous chapter, you learned how Dagger Android works and Android needs a special library to use Dagger. You learned that Dagger can’t instantiate Android standard components because their lifecycle is the responsibility of the Android system, which works as a container. Dagger Android has not been very successful but it taught Google lessons that they used when making architectural decisions for Hilt.

Dagger Android highlighted the most important topics to address:

  • Make Dagger easier to learn and to use.
  • Standardize the way developers create @Scopes and apply them to @Components.
  • Make implementing tests easier.

You’ll learn everything you need to know about testing with Hilt in this chapter, including:

  • What Hilt’s architectural decisions are.
  • How to install Hilt in your app.
  • What @AndroidEntryPoint is and how to use it.
  • What a Hilt @Module is and how it differs from Dagger’s.
  • How to deal with different @Scopes with Hilt.
  • Which tools Hilt provides to handle common use cases.

Of course, you’ll put all these things to work in the Busso App.

Note: At this time, Hilt is still in alpha release. Things might change in future versions of the library.

Hilt’s architectural decisions

Looking at a high-level summary of what you’ve done in the Busso app is a useful way to understand the main architectural decisions Google made for Hilt. In Busso, you:

  1. Created three different @Scopes: @ApplicationScope for objects that live as long as the Application, @ActivityScope for objects that live as long as a specific Activity and FragmentScope for objects that live as long as a Fragment.
  2. Implemented a different @Component or @Subcomponent for each @Scope. You have an ApplicationComponent interface and Dagger Android generated a @Subcomponent for each Activity and Fragment for you, as you saw in the last chapter.
  3. Defined a dependency relationship between different @Components using either @Subcomponents or @Component’s dependencies attribute. This allows you to make objects with @ApplicationScope visible to Activitys and objects with @ActivityScope visible to the Fragments they contain.
  4. Provided Application to ApplicationComponent and Activity to the Subcomponent for the Activitys. You also added these objects to the corresponding dependency graph.
  5. Defined different @Modules to tell Dagger how to bind a specific class to a given type.

To do this, you had to learn many different concepts and several ways to give Dagger the information it needs. As you’ll learn in detail in this chapter, Hilt makes things much easier by:

  1. Providing a predefined set of @Scopes to standardize the way you bind an object to a specific @Component’s lifecycle.

  2. Doing the same for @Components. Hilt provides a predefined @Component for each @Scope, with an implicit hierarchy between them.

  3. Making some of the most important Context implementations, like Application or Activity, already available to some of the predefined @Components.

  4. Defining a new way to bind a @Module to a specific @Component. Now you install a @Module in one or more @Components.

This will become clearer when you start using Hilt for your Busso App.

Migrating Busso to Hilt

Migrating the Busso App to Hilt is a relatively smooth process — and it gives you an opportunity to experience Hilt’s main concepts. You’ll dive into those concepts more deeply later in the tutorial.

For your migration, you need to:

  1. Install Hilt dependencies and plugins.
  2. Enable Hilt in Busso’s Application.
  3. Use a predefined @Scope for the ApplicationComponent.
  4. Migrate Activitys to Hilt using @AndroidEntryPoint.
  5. In the same way, migrate Fragments to Hilt.
  6. Install each @Module in the right @Component using @InstallIn.
  7. Build, run and enjoy Busso.

Now — it’s time to write some code!

Note: As you already know, refactoring with Dagger means that you usually need to complete all the configurations before you can successfully build and run the app. This is also true in this case. However, it’s good practice to try building the app at each step, anyway, allowing Dagger to generate what it can.

Installing what you need to use Hilt

Your first step is to install the Hilt plugin and dependencies in your app.

Start by opening the Busso App project in the starter folder of the materials for this chapter. This is the final project from the previous chapter, which uses Dagger Android. When you open the project, you’ll have this structure:

Figure 17.1 — Initial Project Structure
Figure 17.1 — Initial Project Structure

Now, open build.gradle in the main folder for the project, as in Figure 17.2:

Figure 17.2 — build.gradle in the root of the Busso project
Figure 17.2 — build.gradle in the root of the Busso project

Then, apply the following changes:

buildscript {
  ext.kotlin_version = "1.4.20"
  ext.hilt_version = "2.28-alpha" // 1
  repositories {
    google()
    jcenter()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:4.1.1'
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" // 2
  }
}
// ...

In this Gradle file, you:

  1. Include the definition of ext.hilt_version with the value of the latest version of Hilt.
  2. Add classpath for Hilt’s Gradle plugin.

Now, you have to apply the Hilt plugin to build.gradle for the modules that need it. For starters, open build.gradle from app, as in Figure 17.3:

Figure 17.3 — build.gradle for the app module
Figure 17.3 — build.gradle for the app module

Now, apply the following changes:

plugins {
  id 'com.android.application'
  id 'kotlin-android'
  id "kotlin-kapt"
  id "dagger.hilt.android.plugin" // 1
}
apply from: '../versions.gradle'

android {
  // ...
  compileOptions { // 2
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
  kotlinOptions {
    jvmTarget = '1.8'
  }
  // ...
}

dependencies {
  // ...
  // Hilt dependencies
  implementation "com.google.dagger:hilt-android:$hilt_version" // 3
  kapt "com.google.dagger:hilt-android-compiler:$hilt_version" // 4
  // ...
}

There are some interesting things to note in the Gradle file above. Here, you:

  1. Add the plugin for Hilt. The plugin isn’t necessary, and Hilt works without it, but it makes the developer experience better. For instance, it executes some bytecode transformations to make the code more IDE-friendly. This helps auto-complete the code without messing with auto-generated code. It also makes some code simpler. You’ll see this later, when you work with @HiltAndroidApp.
  2. Specify using Java 1.8, which Hilt requires. Busso already has the proper configuration in compileOptions.
  3. Add the dependency to the Hilt library.
  4. Indicate the specific library kapt uses for Hilt’s annotation processor.

Now, sync the Busso project with the Gradle file and you’re ready to use Hilt in your app. You just need a way to activate the Hilt plugin by providing an entry point to the Hilt world. You’ll do this by using Application. In Busso, you’ll find this class in Main.kt.

Enabling Hilt in Application

Building the Busso App now will give you some errors. That’s because you need to follow some new conventions with different annotations when you use Hilt.

Your next step in migrating Busso to Hilt is to define the entry point for the entire dependency graph. You must do this in your app’s Application implementation. If your app doesn’t have an Application implementation, you need to create one.

Defining the entry point for the dependency graph

In Busso, you need to add your Application implementation in Main.kt in app. Open Main.kt and change the content of the file to this:

@HiltAndroidApp // 1
class Main : Application() // 2

Hey, where’s all the code? Don’t worry, one of the main benefits of Hilt is that you don’t need all the previous code. Here, you:

  1. Used @HiltAndroidApp to tell Dagger that this is the entry point for Busso’s dependency graph.
  2. Created Main as a simple Application.

Adding Application to the dependency graph

That looks good, but you might wonder if something’s missing. In the previous implementation of Main, you had the following:

DaggerApplicationComponent
      .factory()
      .create(this, BussoConfiguration)

There, you created an instance of ApplicationComponent by passing the reference to:

  • Application.
  • NetworkingConfiguration.

That’s how you added Application and NetworkingConfiguration to ApplicationComponent’s dependency graph. That made them automatically available for injection into any other object of the app.

With Hilt, on the other hand, you:

  • Don’t need to do anything for Application. Hilt guarantees it’s automatically added to the dependency graph for ApplicationComponent, making it available for injection into any other object.
  • Need to find a different way to tell Dagger which NetworkingConfiguration to use and how to add it to the dependency graph for ApplicationScope.

To address the second point, you need to tell Dagger:

  1. How to bind BussoConfiguration to NetworkingConfiguration.
  2. To add that implementation to the dependency graph for ApplicationComponent.

Adding a @Module

You already know how to solve the first point: you need a @Module. Open ApplicationModule.kt in di and add the following definition:

@Module(
  includes = [
    LocationModule::class,
    NetworkModule::class,
    AndroidSupportInjectionModule::class
  ]
)
object ApplicationModule {

  @Provides
  fun provideNetworkingConfiguration(): NetworkingConfiguration =  // HERE
    BussoConfiguration
}

Here, you tell Dagger that whenever it needs NetworkingConfiguration, it’ll use BussoConfiguration.

Binding @Module’s objects to ApplicationComponent

Now, you need to bind the objects in this @Module to ApplicationComponent. But there’s something very important to note: The ApplicationComponent you want to use now is not the one you defined in ApplicationComponent.kt in di.

Be brave and delete ApplicationComponent.kt, then apply the following change to ApplicationModule.kt:

@Module(
  includes = [
    LocationModule::class,
    NetworkModule::class,
    AndroidSupportInjectionModule::class
  ]
)
@InstallIn(ApplicationComponent::class) // HERE
object ApplicationModule {

  @Provides
  fun provideNetworkingConfiguration(): NetworkingConfiguration =
    BussoConfiguration
}

You’ll see this in more detail later. At the moment, you’re using @InstallIn to tell Dagger that you want all the bindings you have in ApplicationModule.kt to be a part of the dependency graph for ApplicationComponent.

But which ApplicationComponent is that if you just deleted ApplicationComponent.kt? As you read earlier, Hilt has a set of predefined @Components — and ApplicationComponent, which you find in dagger.hilt.android.components — is one of those.

In this section, you did something very simple but powerful. You told Dagger:

  1. To use Main as the entry point for Busso’s dependency graph. You did this using @HiltAndroidApp.
  2. That you want to use BussoConfiguration as the NetworkingConfiguration implementation.
  3. That all the definitions you made in ApplicationModule will be part of the dependency graph for the predefined ApplicationComponent. You did this using @InstallIn, which you’ll learn more about later.

You also learned that Hilt makes Application automatically available to the dependency graph for the ApplicationComponent — which also makes it available to all the other predefined Hilt @Components.

Using a predefined @Scope for ApplicationComponent

In the previous section, you learned that Hilt creates ApplicationComponent for you. You just need to tell it which objects to put into its dependency graph. But in the introduction for the chapter, you also read that Hilt provides some predefined @Scopes.

You’ll learn about all the available @Scopes and @Components later. At the moment, it’s important to know that the @Scope for ApplicationComponent is @Singleton. To migrate from the existing custom @Scopes in the libs.di.scopes module to Hilt’s, make the following changes:

  1. Delete ApplicationScope.kt, ActivityScope.kt and FragmentScope.kt from libs.di.scopes.
  2. Add the Hilt dependencies in build.gradle for libs.di.scopes.
  3. Fix the dependent modules using the Hilt @Scopes instead of the ones you just deleted.

Note: Here, you’re using libs.di.scopes as the module containing the dependencies for Hilt. This makes things easier to explain but, of course, you could also completely delete libs.di.scopes and fix all the dependencies in the other modules accordingly.

Note: If you don’t want to remove your custom @Scopes, Hilt provides you the @AliasOf annotation, which allows you to use your custom @Scope annotation in place of the ones Hilt provides. You’ll see an example of this later.

After step 2, build.gradle for libs.di.scopes should look like this:

plugins {
  id 'com.android.library'
  id 'kotlin-android'
  id "kotlin-kapt"
  id "dagger.hilt.android.plugin"
}
apply from: '../../../versions.gradle'
android {
  compileSdkVersion compile_sdk_version
  buildToolsVersion build_tool_version
}
dependencies {
  api "javax.inject:javax.inject:$javax_annotation_version"
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"

  api "com.google.dagger:hilt-android:$hilt_version"
  kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
}

Of course, now you need to replace all the existing custom @Scopes with Hilt’s. But don’t worry about that, yet. At the moment, just replace the previous @ApplicationScope with @Singleton. You can find all the occurrences with a simple search:

Figure 17.4 — @ApplicationScope usages
Figure 17.4 — @ApplicationScope usages

After you replaced all the @ApplicationScopes with @Singletons, you can start migrating Busso’s Activitys.

Migrating Activitys to Hilt using @AndroidEntryPoint

In the previous section, you fixed most of the dependencies related to ApplicationComponent by using @Singleton. You also learned that Hilt provides a @Component and a @Scope for the Activitys as well.

This is true. Hilt provides ActivityComponent and @ActivityScoped. In Busso, you need to:

  1. Delete the existing ActivityBindingModule.kt from di.
  2. Tell Dagger that the objects in ActivityModule.kt should be installed in ActivityComponent.
  3. Replace the custom @ActivityScope with @ActivityScoped (note the ending d).

After deleting ActivityBindingModule.kt, open ActivityModule.kt in di.activities and change it like this:

@Module(
  includes = [
    NavigatorModule::class
  ]
)
@InstallIn(ActivityComponent::class) // HERE
interface ActivityModule {
  // ...
}

Here, you’re just using @InstallIn to install this @Module’s bindings in ActivityComponent.

Now, search for @ActivityScope and replace all the occurrences with @ActivityScoped, as in Figure 17.5:

Figure 17.5 — @ActivityScope usages
Figure 17.5 — @ActivityScope usages

After replacing those occurrences, there’s one more step to complete your work with Activitys: You need to execute the injection in MainActivity and SplashActivity.

To do this, Hilt defines @AndroidEntryPoint, which allows you to tag a standard Android component as a possible dependency target.

Later, you’ll see a detailed list of all the classes Hilt’s @AndroidEntryPoint annotation supports. At the moment, you just need to open SplashActivity.kt in ui.view.splash and change it like this:

@AndroidEntryPoint // 1
class SplashActivity : AppCompatActivity() {
  // ...
  override fun onCreate(savedInstanceState: Bundle?) {
    // AndroidInjection.inject(this) // 2 TO DELETE 
    super.onCreate(savedInstanceState)
    makeFullScreen()
    setContentView(R.layout.activity_splash)
    splashViewBinder.init(this)
  }  
  // ...  
}

In this case you:

  1. Add @AndroidEntryPoint.
  2. Remove the AndroidInjection.inject() invocation, which you don’t need anymore.

In the same way, open MainActivity.kt in ui.view.main and apply the following changes

@AndroidEntryPoint // 1
class MainActivity : AppCompatActivity() { // 2
  // ...
}

In MainActivity’s case, you:

  1. Added @AndroidEntryPoint.
  2. Restored AppCompatActivity as the parent class for MainActivity in place of DaggerAppCompatActivity. Note that this implicitly removes the AndroidInjection.inject() invocation.

At this point, it’s important to note that:

  1. All the bindings you defined in ActivityModule are now available in ActivityComponent’s dependency graph.
  2. MainActivity and SplashActivity are dependency targets for the @ActvityScoped objects in ActivityComponent.
  3. The @ActivityScoped dependency targets can also access objects with @Singleton scope. Hilt implicitly manages dependencies between @Components.
  4. Likewise, objects in the dependency graph for @ApplicationComponent access Application, and all objects in the dependency graph for ActivityComponent access Activity.

Now, it’s time to migrate Busso’s Fragments to Hilt. By now, you probably know how to do that. :]

Migrating Fragments to Hilt

In the previous step, you told Dagger which @ActivityScoped objects you want to inject in Busso’s Activitys. Now, you have to do the same for Fragments. In this case, you’ll need a @Scope and a @Component for Fragments that Hilt provides with:

  1. @FragmentScoped
  2. FragmentComponent

To use them, you just need to:

  1. Delete FragmentBindingModule.kt in di.
  2. Use @InstallIn to install FragmentModule in di.fragments in FragmentComponent.
  3. Use @AndroidEntryPoint to tag BusStopFragment and BusArrivalFragment as dependency targets for injection.
  4. Replace the existing @FragmentScope with @FragmentScoped (again, watch the final d).

After deleting FragmentBindingModule.kt, open FragmentModule.kt in di.fragments and change the FragmentModule definition to this:

@Module(includes = [InformationPluginEngineModule.FragmentBindings::class])
@InstallIn(FragmentComponent::class) // HERE
interface FragmentModule {
  // ...
}

Now, open BusStopFragment.kt in ui.view.busstop and apply the following changes:

@AndroidEntryPoint // 1
class BusStopFragment : Fragment() {
  // ...
  /* START REMOVE
  override fun onAttach(context: Context) { // 2
    AndroidSupportInjection.inject(this)
    super.onAttach(context)
  } END REMOVE
  */

  // ...
}

Here, you need to do two very important things:

  1. Add @AndroidEntryPoint to tell Hilt that BusStopFragment is a dependency target.
  2. Remove the existing inject() invocation. Hilt will inject the objects you require into BusStopFragment for you.

Now, you need to do the same for BusArrivalFragment. Open BusArrivalFragment.kt in ui.view.busarrival and apply the same change:

@AndroidEntryPoint // 1
class BusArrivalFragment : Fragment() {
  // ...
  /* START REMOVE
  override fun onAttach(context: Context) { // 2
    AndroidSupportInjection.inject(this)
    super.onAttach(context)
  } END REMOVE
  */
  // ...
}

As your final step, you need to replace @FragmentScope with Hilt’s @FragmentScoped, just as you did for @ActivityScoped and @Singleton. You can see this in Figure 17.6:

Figure 17.6 — @FragmentScope usages
Figure 17.6 — @FragmentScope usages

Great job! You’re almost done migrating Busso to Hilt. Now, you just need to check that you installed all the @Modules in the right @Components and do a bit of cleaning up.

Installing @Modules in @Components

Busso is a multi-module app that defines different @Modules in different Gradle modules. To complete the migration, you need to:

  1. Install NetworkModule, InformationPluginEngineModule and InformationSpecsModule in @ApplicationComponent.
  2. Fix the injection of the Navigator implementation.

These steps should be quite straightforward now. Open NetworkModule.kt in network and use @InstallIn to install it in the ApplicationComponent:

@Module(
  includes = [
    NetworkingModule::class
  ]
)
@InstallIn(ApplicationComponent::class) // HERE
object NetworkModule {
  // ...
}

Open ApplicationModule.kt in di and add the following definition:

@Module(
  includes = [
    LocationModule::class,
    NetworkModule::class,
    AndroidSupportInjectionModule::class,
    InformationPluginEngineModule::class // HERE ADD
  ]
)
@InstallIn(ApplicationComponent::class)
object ApplicationModule {
  // ...
}

In this case, it’s important to note how InformationPluginEngineModule doesn’t need to use @InstallIn itself. Hilt will install InformationPluginEngineModule in ApplicationComponent as part of ApplicationModule.

Now, you also need to install InformationSpecsModule by adding the following code in InformationSpecsModule.kt in plugins:

@Module(
  includes = [
    WhereAmIModule::class,
    WeatherModule::class
  ]
)
@InstallIn(ApplicationComponent::class) // HERE
object InformationSpecsModule

Fixing the Navigator injection

Build the app now and you’ll get an error related to the Navigator implementation. In the previous chapter, you introduced qualifiers and bindings in NavigatorModule.kt in di.navigator. The good news is — you don’t need these anymore. To fix the errors, you need to:

  1. Delete NavigatorModule.kt in di.navigator.
  2. Restore the use of NavigationModule in ActivityModule in place of the NavigatorModule you just removed.
  3. Delete every use of the @Named qualifier for Navigator.

So delete NavigatorModule.kt, then open ActivityModule.kt in di.activities and apply the following change:

@Module(
  includes = [
    NavigationModule::class // HERE
  ]
)
@InstallIn(ActivityComponent::class)
interface ActivityModule {
  // ...
}

Here, you replaced NavigatorModule with NavigationModule. Next, delete the @Named("Main") and @Named("Splash") qualifiers in MainPresenterImpl, BusStopListPresenterImpl and SplashViewBinderImpl.

Here’s how these classes will look now:

class MainPresenterImpl @Inject constructor(
  private val navigator: Navigator // HERE
) : MainPresenter {
  // ...
}
@FragmentScoped
class BusStopListPresenterImpl @Inject constructor(
  private val navigator: Navigator, // HERE
  private val locationObservable: Observable<LocationEvent>,
  private val bussoEndpoint: BussoEndpoint
) : BasePresenter<View, BusStopListViewBinder>(),
  BusStopListPresenter {
  // ...
}
class SplashViewBinderImpl @Inject constructor(
  private val navigator: Navigator // HERE
) : SplashViewBinder {
  // ...
}

For your last step, restore the encapsulation for libs.ui.navigation using the internal visibility modifier in the NavigatorImpl.kt file, like this:

// HERE
internal class NavigatorImpl(private val activity: Activity) : Navigator {
  // ...
}

You did it! You can now successfully build and run Busso, ending up with what you see in Figure 17.7:

Figure 17.7 — The Busso App
Figure 17.7 — The Busso App

It’s now time for a short summary.

Reviewing your achievements

At this point, it’s important to review what you learned while migrating Busso from using Dagger Android to Hilt. Here’s what you discovered:

  • Hilt provides a predefined set of @Scopes and @Components that already implement a dependency relationship. For instance, all the objects with @Singleton scope are visible to the @ActivityScoped objects.
  • For ApplicationComponent, you use @HiltAndroidApp in your app’s Application.
  • You use @InstallIn to make the bindings in a specific @Module available to one or more @Components.
  • Some of the @Components already provide Context implementations, like Application or Activity. For instance, the NavigationModule needs an Activity, which Hilt provides automagically.

You won’t believe it but these are the main things you need to know to use Hilt in your app. Of course, you only saw a few of the @Components and @Scopes available. Now, you’ll get a quick overview of everything Hilt provides.

Hilt’s main APIs

While migrating Busso to Hilt, you learned that the main concepts you need to know are:

  • Entry point and @AndroidEntryPoint.
  • Generated @Components and @Scopes.

You already know how to use them. In the following sections, you’ll just learn what’s available.

Entry point using @AndroidEntryPoint

In this book, you referred to Application, Activitys and Fragments as dependency targets. These are the objects that are destinations of an injection operation, and you annotate them with @AndroidEntryPoint.

Application has a special meaning, so you use @HiltAndroidApp, instead. In Busso, you did this with Application, Activitys and Fragments but you can do the same with all the following classes:

  • Application, by using @HiltAndroidApp
  • Activitys that extend androidx.activity.ComponentActivity
  • Fragments that extend androidx.Fragment
  • View
  • Service
  • BroadcastReceiver

You might notice that this list doesn’t match the Android standard components. It adds Fragments and Views but ContentProvider is missing. You might wonder when and how you could support more classes.

In theory, you should be able to use the existing entry points, but sometimes you need to handle third-party libraries or components that Hilt doesn’t support yet.

As you’ll learn in the next chapter, Hilt provides you with the APIs to create your own entry point, component and scope and integrate them with the ones you already have.

Using predefined @Components & @Scopes

In terms of @Components and @Scopes, Hilt provides what’s shown in Figure 17.8:

Figure 17.8 — Predefined @Components and @Scopes
Figure 17.8 — Predefined @Components and @Scopes

In this dependency diagram, you find ApplicationComponent and ActivityComponent, along with their related @Scopes: @Singleton and @ActivityScope. Looking at the other @Components, it’s worth saying more about:

  • ActivityRetainedComponent with @ActivityRetainedScoped
  • ViewWithFragmentComponent with @ViewScoped

ActivityRetainedComponent is similar to ActivityComponent but it uses a different lifecycle. It allows the bindings to survive configuration changes.

In this case, you need to take care that you don’t have Activity as default binding, but rather Application, as in ApplicationComponent.

ViewWithFragmentComponent also allows you to optimize the lifecycle of Views when part of the View hierarchy is attached to a Fragment.

Finally, here are the default bindings for each predefined @Component:

  • ApplicationComponent: Application
  • ActivityRetainedComponent: Application
  • ActivityComponent: Application and Activity
  • FragmentComponent: Application, Activity and Fragment
  • ViewComponent: Application, Activity and View
  • ViewWithFragmentComponent: Application, Activity, Fragment and View
  • ServiceComponent: Application and Service

When you need to decide which @Scope and @Component to use in your bindings, you need to consider:

  • The lifecycle of the objects you inject.
  • Which default bindings the specific @Component provides.

After that, you can apply the rules you just learned with the Busso App.

Hilt utility APIs

Hilt provides some utility APIs, which help migrate existing apps. The main ones are:

  1. @AliasOf
  2. @ApplicationContext
  3. @ActivityContext

You’ll see a simple example for each of those next.

Using @AliasOf

When you migrated Busso to Hilt, you deleted the custom @Scopes you implemented in the previous chapters. You deleted @ApplicationScope, @ActivityScope and @FragmentScope in favor of the predefined @Singleton, @ActivityScoped and @FragmentScoped.

Suppose you don’t like @Singleton and want to keep the same naming convention by using @ApplicationScoped, instead.

You’d create a new file named ApplicationScoped.kt in libs.di.scopes with the following code:

@Scope
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
@AliasOf(Singleton::class) // HERE
annotation class ApplicationScoped

With this definition, you tell Dagger that @ApplicationScoped is just an alias for @Singleton. To prove this, just search for @Singleton and replace all the instances with the new @ApplicationScoped. After that, build and run the app as usual and it will work.

Of course, the best use case for @AliasOf is when you already have a lot of occurrences for a custom @Scope and you want to avoid replacing too much code.

Using @ApplicationContext

Another very useful tool from Hilt is related to Context. You’ve seen that some of the predefined @Components have an Application for default bindings, while others have an Activity. As you know, they’re both implementations of the Android Context and if you want to distinguish one from the other, you use a qualifier. In this specific case, however, Hilt already provides the qualifier you need.

For instance, open LocationModule.kt in libs.location.rx and look at the following code:

@Module
class LocationModule {
  @ApplicationScoped
  @Provides
  fun provideLocationManager(application: Application): LocationManager = // HERE
    application.getSystemService(Context.LOCATION_SERVICE) as LocationManager
  // ...
}

Here, you used Application to get a reference to LocationManager. In this case, you really just need a Context and Hilt allows you to use that by applying the following change:

@Module
class LocationModule {
  @ApplicationScoped
  @Provides
  fun provideLocationManager(
    @ApplicationContext context: Context // HERE
  ): LocationManager =
    context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
  // ...
}

Here, you replaced Application with Context, using @ApplicationContext to tell Dagger that the Context you want is same as Application.

Using @ActivityContext

You can do the same when you need the Context of an Activity. In that case, you also have the opportunity to improve Busso’s resource management.

Open LocationModule.kt in libs.location.rx and replace the current code with the following:

// 1
class LocationModule {

  @Module
  object ApplicationBindings { // 2
    @ApplicationScoped
    @Provides
    fun provideLocationManager(
      @ApplicationContext context: Context
    ): LocationManager =
      context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
  }

  @Module
  object ActivityBindings { // 3
    @ActivityScoped // 4
    @Provides
    fun providePermissionChecker(
      @ActivityContext context: Context // 5
    ): GeoLocationPermissionChecker =
      GeoLocationPermissionCheckerImpl(context)

    @Provides
    @ActivityScoped // 4
    fun provideLocationObservable(
      locationManager: LocationManager,
      permissionChecker: GeoLocationPermissionChecker
    ): Observable<LocationEvent> = provideRxLocationObservable(locationManager, permissionChecker)
  }
}

As you see, this makes a few changes to LocationModule. In particular:

  1. LocationModule is not a @Module anymore — it’s just a container for a few other @Modules for bindings with different scopes.
  2. ApplicationBindings is a @Module that contains the bindings with @ApplicationScoped or @Singleton.
  3. ActivityBindings is the @Module containing the bindings with @ActivityScoped.
  4. You now use @ActivityScoped for the GeoLocationPermissionChecker and Observable<LocationEvent>, whose optimal lifecycle is the same as Activity.
  5. Use @ActivityContext to tell Dagger that the Context you require for GeoLocationPermissionChecker is Activity.

Updating where you had LocationModule

Now, you need to fix the places where you used LocationModule. Open ApplicationModule.kt in di and add the following definition:

@Module(
  includes = [
    LocationModule.ApplicationBindings::class, // HERE
    NetworkModule::class,
    AndroidSupportInjectionModule::class,
    InformationPluginEngineModule::class
  ]
)
@InstallIn(ApplicationComponent::class)
object ApplicationModule {
  // ...
}

Then, open ActivityModule.kt in di.activities and add the following definition:

@Module(
  includes = [
    NavigationModule::class,
    LocationModule.ActivityBindings::class // HERE
  ]
)
@InstallIn(ActivityComponent::class)
interface ActivityModule {
  // ...
}

Now, you can build and run Busso as usual and verify it works.

Key points

  • The main goal of Hilt is to make Dagger easier to learn and to use.
  • Hilt provides a predefined set of @Components and @Scopes.
  • Using @InstallIn, you install the binding of a @Module in a specific @Component.
  • Each predefined Hilt @Component provides a set of default bindings.
  • There’s an implicit hierarchy between Hilt’s predefined @Components.
  • @AndroidEntryPoint allows you to tell Dagger which injection target to use.
  • @AliasOf simplifies the migration of existing apps to Hilt.
  • Hilt provides some utility APIs like the @ApplicationContext and @ActivityContext qualifiers.

Congratulations! In this chapter, you learned the main concepts of Hilt, the new framework with the goal of simplifying Dagger usage in Android apps. However, Hilt is more than that. In the next chapter, you’ll learn more about it — in particular, how to use customize it and how to use it with Android architecture components.

See you there!

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.