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?
Understanding Hilt’s Code Generation
Select the Project View in Android Studio and open app/build/generated/source/kapt/debug. Next, look at what’s inside the generated code for the init package. You’ll see this:

This is all generated code that could change in future versions of Hilt.
Open InitApp_HiltComponents.java to find the definitions of all the predefined components and scopes.
If you focus on ActivityComponent, for example, you’ll see something like the following code:
@Generated("dagger.hilt.processor.internal.root.RootProcessor")
public final class InitApp_HiltComponents {
private InitApp_HiltComponents() {
}
@Module(
subcomponents = ActivityC.class
)
@DisableInstallInCheck
@Generated("dagger.hilt.processor.internal.root.RootProcessor")
abstract interface ActivityCBuilderModule {
@Binds
ActivityComponentBuilder bind(ActivityC.Builder builder);
}
- - -
@Subcomponent(
modules = {
DefaultViewModelFactories.ActivityModule.class,
HiltWrapper_ActivityModule.class,
FragmentCBuilderModule.class,
ViewCBuilderModule.class
}
)
@ActivityScoped
public abstract static class ActivityC implements ActivityComponent,
DefaultViewModelFactories.ActivityEntryPoint,
FragmentComponentManager.FragmentComponentBuilderEntryPoint,
ViewComponentManager.ViewComponentBuilderEntryPoint,
GeneratedComponent {
@Subcomponent.Builder
abstract interface Builder extends ActivityComponentBuilder {
}
}
- - -
}
This is the first step toward automatically generating all the code for the components you need in the app.
If you look at this code carefully, you’ll notice it contains Dagger annotations like @Module, @Subcomponent and more. You also see custom predefined @Scopes like @ActivityScoped.
This is the code you’d need to write on your own if you wanted to create the same @Component or @Subcomponent hierarchy that Hilt gives you automatically. This is also proof that Hilt isn’t a different entity from Dagger — it’s simply a tool that makes Dagger easier.
Removing AppComponent
You already learned that Hilt provides you with ApplicationComponent and that you don’t need to create a custom @Component for the @Singleton scope anymore.
While this is true, there’s still something you need to take care of. To understand what, open di/AppComponent.kt and look inside:
@Singleton
@Component(modules = [AppModule::class, FeatureModule::class])
interface AppComponent {
fun featureComp(): FeatureComponent
}
In this code, you’re not just defining a @Component for the @Singleton scope, you’re also telling Dagger which bindings this @Component should manage. Remember that a binding is a way to tell Dagger which specific class to instantiate to satisfy a dependency for a given type.
AppComponent also tells Dagger which objects have the same lifecycle as the Application and so, with @Singleton, the same scope.
Dagger defines bindings in @Module. In the previous code, you see that AppComponent provides references for the objects in the AppModule.
FeatureModule is only there because of the FeatureComponent subcomponent, which you’ll fix later.
Open di/AppModule.kt and look at the following code:
@Module
abstract class AppModule {
@Binds
abstract fun provideNewsRepository(
newsRepository: MemoryNewsRepository
): NewsRepository
}
This code provides a binding for the NewsRepository implementation.
Before deleting di/AppComponent.kt, you have find another way to give Dagger the same information, which it needs to instantiate the proper class for MemoryNewsRepository.
For this, Hilt follows a very simple approach. Instead of creating a @Component with a reference to the @Module containing the bindings it needs, it allows you to tell Dagger which @Components the bindings in a @Module belong to.
You do this using @InstallIn and applying a simple change: adding the following line to AppModule:
@Module
@InstallIn(ApplicationComponent::class) // HERE
abstract class AppModule {
@Binds
abstract fun provideNewsRepository(
newsRepository: MemoryNewsRepository
): NewsRepository
}
Using @InstallIn(ApplicationComponent::class) in AppModule.kt, you’re telling Dagger that all the bindings in AppModule will be part of the dependency graph for the ApplicationComponent that Hilt provides automatically.
Note how the @InstallIn annotation needs a parameter, which is the name of the component where you should add the related binding.
After this change, you can finally delete AppComponent.kt without any fear. :]
Refactoring @FeatureScope
The next step is to repeat the process for the objects with @FeatureScope. @FeatureScope is a custom annotation in RWNEws, which represents a scope equivalent to FragmentScope in Hilt.
Open di/FeatureModule.kt and add the following annotation to the class header:
@InstallIn(FragmentComponent::class)
The resulting code should look like the following:
@Module
@InstallIn(FragmentComponent::class) // HERE
abstract class FeatureModule {
// ...
}
With the @InstallIn annotation, you’re adding FeatureModule‘s’ bindings to the FragmentComponent with FragmentScope.
Do the same for di/StatsModule.kt. Open this file and add the same annotation to the class header. The resulting code becomes:
@Module
@InstallIn(FragmentComponent::class) // HERE
class StatsModule {
@Provides
@ElementsIntoSet
fun provideNewsStats(): Set<NewsStats> = setOf(
LengthNewsStats()
)
}
Finally, open thirdparty/ThirdPartyStatsModule.kt and repeat the same annotation as follows:
@Module
@InstallIn(FragmentComponent::class) // HERE
class ThirdPartyStatsModule {
@Provides
@IntoSet
fun provideWordsCountNewsStats(): NewsStats = WordCountNewsStats()
}
Now, delete FeatureComponent.kt and FeatureScope.kt since you don’t need them anymore.
Understanding Entry Points
In the previous sections, you helped Hilt create the dependency graph for objects with different scopes. Now, you need a way to inject those objects into Activities, Fragments or any other Android standard components.
To do this, Hilt uses entry points, which function as an interface between the dependency graph and the object destination of the injection. Every time you need to tag a class as the target of an injection, Hilt requires you to annotate it with @AndroidEntryPoint.
@AndroidEntryPoints. For instance, ContentProvider isn’t supported at the moment. On the other hand, Hilt supports Fragment components that, in theory, aren’t formally Android Standard Components, since you don’t define them in AndroidManifest.xml.
Using @AndroidEntryPoint With Activities
RWNEws is a simple app that contains a couple of Fragments and a simple Activity as their container. Open ui/MainActivity.kt and look at the following code:
// 1
typealias FeatureComponentProvider = Provider<FeatureComponent>
class MainActivity : AppCompatActivity(), FeatureComponentProvider {
// 2
lateinit var featureComp: FeatureComponent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.anchor, NewsListFragment())
.commit()
featureComp = (applicationContext as InitApp).appComp().featureComp() // 3
}
}
// 4
override fun get(): FeatureComponent = featureComp
}
Here, you:
- Create
codeFeatureComponentProvideras an alias ofProvider<FeatureComponent>. - Define
featureCompas a property containing theFeatureComponentyou need to return asFeatureComponentProvider. - Create the instance of
FeatureComponentto save infeatureComp. - Provide the implementation of
get()that theMainActivitymust provide when it implementsFeatureComponentProvider.
At this point, you might wonder why you’re looking at MainActivity if there’s nothing to inject. Remember that Hilt provides a @Component hierarchy and you don’t need to create them anymore. Therefore, you need to replace the previous code with the following:
@AndroidEntryPoint // HERE
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.anchor, NewsListFragment())
.commit()
}
}
}
Wait! All the @Component creation code is gone, so why do you need to annotate MainActivity with @AndroidEntryPoint? You have to do it because MainActivity is going to host Fragments that will be @AndroidEntryPoint as well, and Hilt needs to know that.