Hilt Rocks! šŖ Here is the Manual How to Migrate Your Project.
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 extendApplication
orMultyDexApplication
@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 NullPointerException
s.
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!