Hilt Rocks! 💪 Here is the Manual How to Migrate Your Project.

Ivana Tanova
8 min readJul 25, 2020

I migrated a large production codebase from Dagger to Hilt in two days. I didn’t followed the advice in the official documentation for migration to move everything with EntryPoint. This could be a lot of work for a large codebase, so just don’t do it. Here I will show you the exact steps I did to fully migrate my codebase in one big step.

What you will learn:

  • How to migrate your Application class
  • How to migrate your Activitis, Fragments, BroadcastRecevers, Services
  • How to migrate your ViewModels
  • How to add Hilt modules

Disclaimer: Hilt is still in alpha, don’t use it on production.

Few notes before we begin. Hilt uses same @Inject annotation as Dagger, so you don’t need to change the way you inject your obects as fields. However the way we provide the objects is a little bit different, but I will walk you step by step through the process.

Let’s start with the fun part — Dagger boilerplate extermination 💥

Hilt Setup 🚀

To use Hilt you need to add hilt-android-gradle-plugin to your project’s root build.gradle

buildscript {ext {
hilt_version = '2.28-alpha'
}
dependencies {
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
}
}

and then in the app/build.gradle file:

1. Apply the plugin

apply plugin: 'kotlin-kapt'
apply plugin: ‘dagger.hilt.android.plugin’

2. Add Java 8 support

Hilt uses Java 8 features. To enable Java 8 in your project:

android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions { jvmTarget = ‘1.8’ }}

3. Add Hilt dependencies:

// Hilt dependenciesimplementation “com.google.dagger:hilt-android:$hilt_version”kapt “com.google.dagger:hilt-android-compiler:$hilt_version”// Hilt ViewModel extensiondef hilt_jetpack_version = “1.0.0-alpha01”implementation “androidx.hilt:hilt-lifecycle-viewmodel:$hilt_jetpack_version”kapt “androidx.hilt:hilt-compiler:$hilt_jetpack_version”// Hilt testing dependenciesandroidTestImplementation “com.google.dagger:hilt-android-testing:$hilt_version”kaptAndroidTest “com.google.dagger:hilt-android-compiler:$hilt_version”kaptAndroidTest “androidx.hilt:hilt-compiler:$hilt_jetpack_version”

1. Migrate Application class

  • Remove all Dagger boilerplate from your application class. Yes, all of it.

🙅 Remove HasAndroidInjector implementation. For older Dagger remove HasActivityInjector, HasServiceInjector, HasBroadcastReceiverInjector implementations.

🙅Remove DispatchingAndroidInjector injections:

implements HasActivityInjector, HasServiceInjector, HasBroadcastReceiverInjector@Inject
DispatchingAndroidInjector<Activity> dispatchingActivityInjector;
@Inject
DispatchingAndroidInjector<Service> dispatchingServiceInjector;
@Inject
DispatchingAndroidInjector<BroadcastReceiver> dispatchingBroadcastReceiverInjector;
  • Now just annotate your application class with @HiltAndroidApp and extend Application or MultyDexApplication
@HiltAndroidApp
class MyApplication : Application() { … }

We are ready! Remember: leave your @Inject objects untouched.

2. Migrate your Acivities

  • Remove all Dagger boilerplate

🙅 Remove component injection in onCreate()

AndroidInjection.inject(this);
  • Now annotate the activity with @AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

With this annotation Hilt will know that this object wants to use injections.

If you have abstract BaseActivity do not add @AndroidEntryPoint. Abstract classes can’t be annotated, however they still can use injections just because their child classes are annotated.

If you annotate an Android class with @AndroidEntryPoint, then you also must annotate Android classes that depend on it. For example, if you annotate a fragment, then you must also annotate any activities where you use that fragment.

Note about objects injection (We will clarify that further).

There are two types of injections: field injections and constructor injections. Android SDK components cannot have constructor injections, because they are creted from the OS, so you can only use field injections in these classes. Here is how a field injection looks in your code:

@Inject 
lateinit var analytics: AnalyticsAdapter

Even if your activity doesn’t need field injection, if there are fragments attached to it that use @AndroidEntryPoint, you must migrate the activity to use @AndroidEntryPoint as well. So better annotate all activities and fragments.

3. Migrate your Fragments

  • Again - remove all Dagger boilerplate.

🙅 Remove HasSupportFragmentInjector implementation if you have it

extends Fragment implements HasSupportFragmentInjector

🙅Remove DispatchingAndroidInjection

@Inject
DispatchingAndroidInjector<Fragment> fragmentDispatchingAndroidInjector;

🙅 Remove this code in onAttach()

override fun onAttach(context: Context) {
AndroidSupportInjection.inject(this)
super.onAttach(context)
}
  • Now just add @AndroidEntryPoint annotation
@AndroidEntryPoint
class MyFragment : Fragment()

4. Migrate your Service, BroadcastReceiver

  • Remove Dagger boilerplate.

🙅 Clean unnecessary extensions like DaggerService andDaggerIntentService, DaggerBroadcastReceiver, DaggerContentProvider. Now your Service, BroadcastReceivers etc should extend vanila Android SDK parents.

MyBroadcastReceiver : DaggerBroadcastReceiver()
  • Annotate with @AndroidEntryPoint (only if you use injected objects inside)
@AndroidEntryPoint
class BootBroadcastReceiver : BroadcastReceiver()

5. Migrate your ViewModels

Good news: you don’t need to inject ViewModelProvider.Factory anymore! 🎊

Your ViewModels will be created directly from Hilt now. This is additional Hilt Jetpack library, that’s why we needed this gradle import:

def hilt_jetpack_version = “1.0.0-alpha01”implementation “androidx.hilt:hilt-lifecycle-viewmodel:$hilt_jetpack_version”kapt “androidx.hilt:hilt-compiler:$hilt_jetpack_version”
  • Remove Dagger biolerplate inside Fragments and Activities

🙅Remove ViewModelProvider.Factory injections

@Inject
lateinit var factory: ViewModelProvider.Factory

🙅 Remove ViewModel initialization with ViewModelProviders

viewModel = ViewModelProviders.of(this, factory).get(TriviaFragmentViewModel::class.java)

🙅 Remove Dagger @interface ViewModelKey

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@MapKey
public @interface ViewModelKey {
Class<? extends ViewModel> value();}

🙅 Remove public @interface WithMap

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface WithMap

🙅 Remove ViewModelProvider.Factory implementation

ViewModelFactory implements ViewModelProvider.Factory

🙅 Remove ViewModelModule

@Module
public abstract class ViewModelModule
@Binds
@IntoMap
@ViewModelKey(WelcomeFragmentViewModel.class)
abstract ViewModel bindWelcomeFragmentViewModel(WelcomeFragmentViewModel viewModel);
  • Instantiate ViewModels with ViewModel’s ktx extension for Kotlin:
private val viewModel: TriviaFragmentViewModel by viewModels()

*If you don’t have KTX, add following libraries:

//Android KTXimplementation "androidx.fragment:fragment-ktx:1.2.5"implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha05"implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"

For Java, use the old way:

viewModel = new ViewModelProvider(this).get(SendEmailVerificationViewModel.class);
  • Annotate ViewModel’s constructor with @ViewModelInject

👉 Just change old Dagger @Inject annotation with @ViewModelInject

class LoginViewModel @ViewModelInject constructor(
private val analyticsAdapter: AnalyticsAdapter
): ViewModel { … }

Every argument for the ViewModel constructor will be created by Hilt.

Ok, fun part is over, now we need to actually tell Hilt how to create all objects. Heads up, it is quite easy and we still gonna exterminate Dagger boilerplate 😎

How to create an object

As I said before there are two types of injections: field injections and constructor injections. You should prefer constructor injections because javac will ensure that no field is referenced before it has been set, which helps avoid NullPointerExceptions.

Hilt only knows how to create a object if its constructor is annotated with @Inject

Java:

public class AnalyticsAdapter {@Inject 
AnalyticsAdapter(AnalyticsService service)
}

Kotin

class AnalyticsAdapter @Inject constructor(
private val service: AnalyticsService
)

5. Hilt modules

When we use interfaces or third party libraries we cannot add @Inject annotation in front of those objects constructor. This is why we need to tell Hilt how to create them. For injecting interfaces and components created from third party libraries you still need to declare @Module as it was in Dagger. However the process is way much simpler than using Dagger.

Here comes the real beauty of Hilt. Forget about all Dagger @Component, @Subcomponent. You don’t need to provide modules for Android components also.

This is how a module looks like

@InstallIn(ApplicationComponent::class)
@Module
interface FooModule {}

Hilt Scopes

When you provide object within @Module. You need to tell Hilt for how long you want this object alive. With Hilt your options are: As long as the application is alive, as long as the activity in which it is injected, as long as the fragment, as long as the ViewModel. You do this when you declare your module using an annotation @InstallIn.

Objects that are created from Hilt with @Inject constructor annotation can be scoped with annotations: @Singleton, @ActivityRetainedScope, @ActivityScoped, @FragmentScoped..

@Singleton
class MyOwnObject @Inject constructor()

1. Objects needed as long as the Application is alive

Time for code extermination 💥

  • Remove Dagger boilerplate

🙅 Remove @Component module

@Component(modules = [
FooModule::class,
BarModule::class,
...
])
interface MySingletonComponent {
}

🙅 Remove ApplicationContextModule module

@Module
public abstract class ApplicationContextModule

🙅 Remove ActivityContextModule module

@Module
public abstract class ActivityContextModule
  • Create Hilt Module

Crate new pakage di and subpackages: foo, bar (for each submodule). For each submodule create a interface with following annotations.

@InstallIn(ApplicationComponent::class)
@Module
interface FooModule {}

@InstallIn (ApplicationComponent::class) means that the objects provided here will be available as long as the application is alive.

  • Provide objects

How we declare object instantiation? Almost the same way as in Dagger. Use @Provides annotation:

  @Provides
fun provideBar(): Bar {...}
  • Automatically provided arguments.

Hilt provides you automatically with following arguments — Application context, Activity context, Application class itself. If a component needs the Application context as argument just annotate the argument with @ApplicationContext

@Provides
fun provideBar(@ApplicationContext ctx: Context): Bar {...}

Application as argument? Sure, no worries!

@Provides
fun provideBar(app: Application): Bar {...}

You are not allowed to use Activity context argument in ApplicationComponent module. However you can use it in Activity and Fragment components.

  • Here Gson is annotated with @Singleton. These providers are only called once per ApplicationComponent instance.

2. Objects needed as long as a Activity is alive

Important!! You can provide here also objects used in fragments attached to the activity.

  • Create ActivityComponent scoped Hilt module
@InstallIn(ActivityComponent::class)
@Module
class ActivityComponentModule {
  • Look around in your old Dagger modules for objects provided with @ActivityScope and import them in this Hilt module
  • Remove Dagger boilerplate

👉 Remove all Dagger abstract classes that provide Activity or Fragment

@Module(includes = [ActivityModule::class])
abstract class ForceUpdateActivityModule {
@Binds
internal abstract fun activity(activity: ForceUpdateActivity): Activity
@FragmentScope
@ContributesAndroidInjector
internal abstract fun webViewFragmentInjector(): WebViewFragment
}

👉 Remove your ActivityModule

@Module(includes = {
ActivityContextModule.class,
ViewModelModule.class,
PermissionsManagerModule.class,
ActivityModelsModule.class})
public class ActivityModule {
}

3. Objects needed as long as Fragment is alive

  • Create FragmentComponent scoped Hilt module
@InstallIn(FragmentComponent::class)
@Module
class FragmentComponentModule {
  • Look around you old Dagger modules for objects provided with @FragmentScope and move them here. Do not provide Fragments or Activities!! We don’t use that anymore.

4. Objects needed as long as a ViewModel is alive

Thesee objects will be created every time ViewModel is crated.

  • Create ActivityRetainedComponent scoped Hilt module
@InstallIn(ActivityRetainedComponent::class)
@Module
class ActivityRetainedComponentModule {

5. Other scopes

You can create also ViewComponent, ViewWithFragmentComponent (View annotated with @WithFragmentBindings) and ServiceComponent

Hilt doesn’t generate a component for broadcast receivers because Hilt injects broadcast receivers directly from ApplicationComponent.

At the end

  • Remove Dager dependencies.
  • Search for all dagger.android.* imports and usages and remove them.

Happy coding!

--

--

Ivana Tanova

Android enthusiast and keen tea drinker. I love to learn and to be funny, both for me are passions.