Migrating to Hilt can vary widely in difficulty depending on the state of your codebase and which practices or patterns your codebase follows. This page offers advice on some common issues migrating apps may encounter. This page assumes that you already generally understand the basic Hilt APIs. If that is not the case, take a look at our Quick Start guide for Hilt first. This page also assumes a general understanding of Dagger, which should be the case since this page is only useful for those migrating a codebase that already uses Dagger. If your codebase does not use Dagger, add Hilt to your app by going through the Quick Start guide as this guide only deals with migrations from non-Hilt Dagger setups.
Refactoring tip: Whenever you modify the code of a class, check that the unused or no longer existing imports are removed from the file.
Table of Contents
- 0. Plan your migration
- 1. Migrate the
Application
- 2. Migrate Activities and Fragments (and other classes)
- 3. Other Android components
- What to do with … ?
0. Plan your migration
When migrating to Hilt, you’ll want to organize your work into steps. This guide
should lay out the general approach that should work for most cases, but every
migration will be different. The recommended approach is to start at the
Application
or @Singleton
component and incrementally grow from there. After
Application
and @Singleton
, migrate activities and then fragments after
that. This should generally be doable as an incremental migration. Even if you
have a relatively small codebase, doing the migration incrementally will give
you a chance to build in between steps to check your progress.
Compare component hierarchies
The first thing to do is to compare your current component hierarchy to the one in Hilt. You’ll want to decide which components map to which Hilt component. Hopefully these should be relatively straightforward, but if there is not a clear mapping, you can keep custom components as manual Dagger components. These components can be children of the Hilt components. However, Hilt does not allow inserting components into the hierarchy (e.g. changing the parent of a Hilt component). See the custom components section of the guide below. The rest of this guide assumes a migration where the components all map directly to Hilt components.
Also, if your code uses component dependencies, you should read the component dependencies section below first as well. The rest of this guide assumes usage of subcomponents.
If you are using the dagger.android @ContributesAndroidInjector
and are unsure
about your component hierarchy, then your hierarchy should roughly match the
Hilt components.
Be aware of when Hilt injects classes
You can find out when Hilt injects classes for each Android class here. These hopefully should be similar to where your code currently injects, but if not, be aware in case it causes any differences in your code.
Migration Overview
At the end of the migration, the code should be changed as follows:
- All
@Component
/@Subcomponent
(or if using dagger.android@ContributesAndroidInjector
) usages should be removed. - All
@Module
classes should be annotated with@InstallIn
. - All
Application
/Activity
/Fragment
/View
/Service
/BroadcastReceiver
classes should be annotated with@AndroidEntryPoint
(assuming use of injection in those classes). - Any code instantiating or propagating components (like interfaces on your Activity to expose the component) should be removed.
- All
dagger.android
references should be removed.
1. Migrate the Application
The first thing to change will be to migrate your Application
and @Singleton
component to the generated Hilt SingletonComponent
. To do this, we’ll first
want to make sure that everything that is installed in your current component is
installed in the Hilt SingletonComponent
.
Migrating a Component
To migrate the Application, we need to migrate everything in the pre-existing
@Singleton
component to the SingletonComponent
.
a. Handle the modules
First, we should install all of the modules into the SingletonComponent
.
This can be done by annotating each module currently installed in your component
with @InstallIn(SingletonComponent.class)
. If there are a lot of modules,
instead of changing all of those now, you can create and install a single
aggregator @Module
class that includes all of the current modules. This is
just a temporary solution, however, since in order to take full advantage of
Hilt features like replacing bindings, you
will need to break up the aggregator module in the future.
// Starting with this component
@Component(modules = {
FooModule.class,
BarModule.class,
...
})
interface MySingletonComponent {
}
// Becomes the following classes
@InstallIn(SingletonComponent.class)
@Module(includes = {
FooModule.class,
BarModule.class,
...
})
interface AggregatorModule {}
// Starting with this component
@Component(modules = [
FooModule::class,
BarModule::class,
...
])
interface MySingletonComponent {
}
// Becomes the following classes
@InstallIn(SingletonComponent::class)
@Module(includes = [
FooModule::class,
BarModule::class,
...
])
interface AggregatorModule {}
Warning: Modules that are not annotated with @InstallIn
are not used by
Hilt. Hilt by default raises an error when unannotated modules are found, but
this error can be disabled.
b. Handle any extended interfaces or methods
A similar process can be used for any interfaces your current component extends
using @EntryPoint
.
Interfaces on components are generally used to either add inject methods or get access to types like bindings or subcomponents. In Hilt many of these won’t be needed once the migration is complete because Hilt will generate them for you or they will be replaced by Hilt tools. For the migration though, this section will describe how to preserve current behavior so that code continues to work. You should be looking at all of these methods though and evaluating if they are still needed as the migration continues.
Moving everything with @EntryPoint
Annotate any interface your component extends with @EntryPoint
and
@InstallIn(SingletonComponent.class)
. If there are many interfaces, create a
single aggregator interface to collect them all just like the modules. Any
method defined directly on the component interface can be moved to either the
aggregator interface or one the aggregator extends.
Example:
// Starting with this component
@Component
@Singleton
interface MySingletonComponent extends FooInjector, BarInjector {
void inject(MyApplication myApplication);
Foo getFoo();
}
// Becomes the following class
@InstallIn(SingletonComponent.class)
@EntryPoint
interface AggregatorEntryPoint extends FooInjector, BarInjector {
// This is moved as an example, but further below we will see that inject
// methods for the Application can just be removed.
void inject(MyApplication myApplication);
Foo getFoo();
}
// Starting with this component
@Component
@Singleton
interface MySingletonComponent : FooInjector, BarInjector {
fun inject(myApplication: MyApplication)
fun getFoo() : Foo
}
// Becomes the following class
@InstallIn(SingletonComponent::class)
@EntryPoint
interface AggregatorEntryPoint : FooInjector, BarInjector {
// This is moved as an example, but further below we will see that inject
// methods for the Application can just be removed.
fun inject(myApplication: MyApplication)
fun getFoo() : Foo
}
Inject methods
Hilt handles injecting your Application
class under the hood, so if you had
any inject methods for the Application
, those can be removed. Inject methods
for other Android types should also eventually be removed as those are later
migrated to use @AndroidEntryPoint
.
@Component
@Singleton
interface MySingletonComponent {
// Hilt takes care of Application injection for you, so this can be deleted.
void inject(MyApplication myApplication);
// This can be deleted once FooActivity is migrated to use @AndroidEntryPoint
void inject(FooActivity fooActivity);
}
@Component
@Singleton
interface MySingletonComponent {
// Hilt takes care of Application injection for you, so this can be deleted.
fun inject(myApplication: MyApplication)
// This can be deleted once FooActivity is migrated to use @AndroidEntryPoint
fun inject(fooActivity: FooActivity)
}
Accessing the interfaces
Your code likely has a method where you returned the component either directly
or as one of the interface types so that other code could get access to inject
methods or accessor methods. To keep this code working as you migrate, you can
get a reference by using the
EntryPoints
class. As your migration
continues, you should be able to remove these methods and have calling code use
the Hilt EntryPoints
API directly.
// If you started with code like this:
public final class MyApplication extends Application {
MySingletonComponent component() {
return component;
}
}
// After adding the aggregator entry point, it will look like the following:
@InstallIn(SingletonComponent.class)
@EntryPoint
interface AggregatorEntryPoint extends LegacyInterface, ... {
}
@HiltAndroidApp
public final class MyApplication extends Application {
// The return type changed to AggregatorEntryPoint, but that should be
// okay as this implements all the interfaces the old component used to.
AggregatorEntryPoint component() {
// Use EntryPoints to get an instance of the AggregatorEntryPoint.
return EntryPoints.get(this, AggregatorEntryPoint.class);
}
}
// If you started with code like this:
class MyApplication : Application() {
fun component(): MySingletonComponent {
return component
}
}
// After adding the aggregator entry point, it will look like the following:
@InstallIn(SingletonComponent::class)
@EntryPoint
interface AggregatorEntryPoint : LegacyInterface, ... {
}
@HiltAndroidApp
class MyApplication : Application() {
// The return type changed to AggregatorEntryPoint, but that should be
// okay as this implements all the interfaces the old component used to.
fun component(): AggregatorEntryPoint {
// Use EntryPoints to get an instance of the AggregatorEntryPoint.
return EntryPoints.get(this, AggregatorEntryPoint::class.java)
}
}
c. Scopes
When migrating a component to Hilt, you’ll also need to migrate your bindings to
use the Hilt scope annotations. In the case of the SingletonComponent
, this
is @Singleton
. You can find which annotations correspond to which component
in the component lifetimes section. If you
aren’t using @Singleton
and have your own scoping annotation, you can tell
Hilt that your annotation is equivalent to a Hilt scoping annotation using
scope aliases. This will allow you to migrate and remove
your scoping annotation at your leisure later in the process.
d. Handling component arguments
Hilt components cannot take component arguments because the initialization of the component is hidden from users. Usually, this is used to get an application instance (or for other components an activity/fragment instance) into the Dagger graph. For these cases, you should switch to using the predefined bindings in Hilt that are listed here.
If your component has any other arguments either through module instances passed
to the builder or @BindsInstance
, read this section on
handling those. Once you handle those, you can just remove your
@Component.Builder
interface as it will be unused.
e. Cleaning up aggregators
If you used an aggregator module or entry point, you will eventually need to go
back and remove the aggregator module and entry point class. You can do this by
individually annotating all of the included modules and implemented interfaces
with the same @InstallIn
annotation used on the aggregator.
@InstallIn(SingletonComponent.class)
@Module(includes = {FooModule.class, ...})
interface AggregatorModule {
}
// Remove FooModule from the list above and annotate it directly
@InstallIn(SingletonComponent.class)
@Module
interface FooModule {
}
@InstallIn(SingletonComponent::class)
@Module(includes = [FooModule::class, ...])
interface AggregatorModule {
}
// Remove FooModule from the list above and annotate it directly
@InstallIn(SingletonComponent::class)
@Module
interface FooModule {
}
Adding Hilt to the Application
Now you can just annotate your Application
with @HiltAndroidApp
as described
in our Quick Start guide. Apart from that, it should be empty of
any code related to building or storing an instance of your component. You can
delete your @Component
class and @Component.Builder
class if you haven’t
already.
dagger.android Application
If your Application
either extends from DaggerApplication
or implements
HasAndroidInjector
, keep this code until all your dagger.android
activities/fragments have been also migrated. This will likely be one of the
final steps of your migration. These parts of dagger.android are there for
making sure getting dependencies works (e.g. when an Activity
tries to inject
itself). The difference is now they are being satisfied by the Hilt
SingletonComponent
instead of the component removed in the above steps.
For example, a migrated dagger.android Application
that supports both Hilt
activities and dagger.android activities may look like this:
@HiltAndroidApp
public final class MyApplication implements HasAndroidInjector {
@Inject DispatchingAndroidInjector<Object> dispatchingAndroidInjector;
@Override
public AndroidInjector<Object> androidInjector() {
return dispatchingAndroidInjector;
}
}
@HiltAndroidApp
class MyApplication : HasAndroidInjector {
@Inject lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Object>
override fun androidInjector() = dispatchingAndroidInjector
}
Or if you were using DaggerApplication
before you can do the following. The
@EntryPoint
class is to make the Dagger component implement
AndroidInjector<MyApplication>
. This is likely what your previous Dagger
component was doing before.
@HiltAndroidApp
public final class MyApplication extends DaggerApplication {
@EntryPoint
@InstallIn(SingletonComponent.class)
interface ApplicationInjector extends AndroidInjector<MyApplication> {
}
@Override
public AndroidInjector<MyApplication> applicationInjector() {
return EntryPoints.get(this, ApplicationInjector.class);
}
}
@HiltAndroidApp
class MyApplication : DaggerApplication() {
@EntryPoint
@InstallIn(SingletonComponent::class)
interface ApplicationInjector : AndroidInjector<MyApplication>
override fun applicationInjector(): AndroidInjector<MyApplication> {
return EntryPoints.get(this, ApplicationInjector::class.java)
}
}
When you have migrated all of the other dagger.android usages and are ready to
remove this code, simply extend from Application
and remove the overridden
methods and the DispatchingAndroidInjector
classes.
dagger.android Test Application
If your application will be used in a Hilt test, then it’s important to note
that Hilt does not currently allow field injection in test applications.
(See “Early Entry Points” for more details).
Thus, your test application cannot extend DaggerApplication
since that class
uses field injection under the hood. Instead, implement HasAndroidInjector
and
use an entry point to get the DispatchingAndroidInjector
, as shown below:
// Generates TestApplication_Application
@CustomTestApplication(BaseApplication.class)
interface TestApplication {}
abstract class BaseApplication extends Application implements HasAndroidInjector {
// Hilt test applications cannot use field injection, so you an entry point instead
@EntryPoint
@InstallIn(SingletonComponent.class)
interface InjectorEntryPoint {
DispatchingAndroidInjector<Object> dispatchingAndroidInjector();
}
@Override
public AndroidInjector<Object> androidInjector() {
return EntryPoints.get(this, InjectorEntryPoint.class).dispatchingAndroidInjector();
}
}
// Generates TestApplication_Application
@CustomTestApplication(BaseApplication.class)
interface TestApplication {}
abstract class BaseApplication: Application, HasAndroidInjector {
// Hilt test applications cannot use field injection, so you an entry point instead
@EntryPoint
@InstallIn(SingletonComponent::class)
interface InjectorEntryPoint {
fun dispatchingAndroidInjector(): DispatchingAndroidInjector<Object>
}
override fun androidInjector(): AndroidInjector<Object> {
return EntryPoints.get(this, InjectorEntryPoint::class).dispatchingAndroidInjector()
}
}
Check your build
You should be able to stop and build/run your app successfully at this point.
Your app is successfully using Hilt for the SingletonComponent
.
2. Migrate Activities and Fragments (and other classes)
Now that the application supports Hilt, you should be able to start migrating
your activities and then fragments to Hilt. While migrating your app, it is okay
to have @AndroidEntryPoint
activities and non-@AndroidEntryPoint
activities
together. The same is true for fragments within an activity. The only
restriction with mixing Hilt with non-Hilt code is on the parent. Hilt
activities need to be attached to Hilt applications. Hilt fragments must be
attached to Hilt activities. We recommend doing all the activities before doing
any of the fragments, but if that is problematic there is a tool to help relax
that constraint with optional injection.
Migrating activities and fragments are going to be pretty similar to the
application component in terms of mechanics. You should take all the modules
from your current component and install them in the proper component with an
@InstallIn
module. Similarly, take all of the current component’s extended
interfaces and install them in the proper component with an @InstallIn
entry point. Go back to this section
above for details, but also read below on some of the extra consideration that
must be taken for activities and fragments.
Note: If you are using dagger.android’s @ContributesAndroidInjector
, then when
following this section on migrating a component the
modules in @ContributesAndroidInjector
are the modules you need to migrate.
You do not have any interfaces to migrate with @EntryPoint
.
Be aware of differences with monolithic components
One of the design decisions of Hilt is to use a single component for all of the activities and a single component for all of the fragments. If you’re interested, you can read about the reasons here. The reason this is important is that if you had a separate component for each activity (as is the default in dagger.android), you will be merging the components into a single component when migrating to Hilt. Depending on your code base, you could run into problems.
The two most frequent issues are:
Conflicting bindings
This occurs if you defined the same binding key differently in two activities. When they are merged, you get a duplicate binding. This is a limitation of the global binding key space of Hilt and you’ll need to redefine that binding to have a single definition. Usually this isn’t too bad and is done by basing logic off of the injected activity. See the section on component arguments for examples.
Depending on the specific activity type
Because of the merged component, bindings for a FooActivity
or BarActivity
often won’t make sense anymore since when the component is used for a
BarActivity
(or any other activity), a FooActivity
binding won’t be able to
be satisfied. Usually code doesn’t really rely on the actual child type of the
activity and just needs an Activity or common subtype like FragmentActivity
.
Code using the child type needs to be refactored to use a more generic type. If
you need a common subtype that isn’t automatically provided by Hilt, you can
provide a binding with a cast (example here), but be
careful!
Example of replacing a usage with a common subtype:
// This class only uses the activity to get the FragmentManager. It can instead
// use the non-specific FragmentActivity class.
final class Foo {
private final FooActivity activity;
@Inject Foo(FooActivity activity) {
this.activity = activity;
}
void doSomething() {
activity.getSupportFragmentManager()...
}
}
// Changed to FragmentActivity when migrating to Hilt class Foo @Inject
final class Foo {
private final FragmentActivity activity;
@Inject Foo(FragmentActivity activity) {
this.activity = activity;
}
void doSomething() {
activity.getSupportFragmentManager()...
}
}
// This class only uses the activity to get the FragmentManager. It can instead
// use the non-specific FragmentActivity class.
class Foo @Inject constructor(private val activity: FooActivity) {
fun doSomething() {
activity.getSupportFragmentManager()...
}
}
// Changed to FragmentActivity when migrating to Hilt class Foo @Inject
class Foo @Inject constructor(private val activity: FragmentActivity) {
fun doSomething() {
activity.getSupportFragmentManager()...
}
}
Retained fragments
Hilt does not support retained fragments. You can find more info about why
here. If you have any retained
fragments, a common way to address this is to move any retained state into a
ViewModel
.
Adding Hilt to the Activity/Fragment
Now you can just annotate your Activity
or Fragment
with
@AndroidEntryPoint
as described in our Quick Start guide. Base
classes, even if they perform field injection, don’t need to be annotated
(unless there is a situation where they are instantiated directly as the
childmost class).
@AndroidEntryPoint
public final class FooActivity extends AppCompatActivity {
@Inject Foo foo;
}
@AndroidEntryPoint
class FooActivity : AppCompatActivity() {
@Inject lateinit var foo: Foo
}
Note: 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.
Dagger
Now you can remove any component initialization code or injection interfaces if you have them.
dagger.android
If you are using @ContributesAndroidInjector
for this class, you can remove
that now. You can also remove any calls to
AndroidInjection
/AndroidSupportInjection
if you have them. If your class
implements HasAndroidInjector
, and it is not the parent of any non-Hilt
fragments or views, you can remove that code now.
If your Activity or Fragment either extends from DaggerAppCompatActivity
,
DaggerFragment
, or similar classes, these need to be removed and replaced with
non-Dagger equivalents (like AppCompatActivity
or a regular Fragment
). If
you have any child fragments or views that are still using dagger.android,
you’ll need to implement HasAndroidInjector
by injecting a
DispatchingAndroidInjector
(see example below).
When you have migrated all of the children off of dagger.android, come back
later to remove the HasAndroidInjector
code.
A simple dagger.android example
The following example shows migrating an activity while still allowing it to support both Hilt and dagger.android fragments.
Initial state:
public final class MyActivity extends DaggerAppCompatActivity {
@Inject Foo foo;
}
@Module
interface MyActivityModule {
// If you have a scope annotation, see the section on scope aliases
@ContributesAndroidInjector(modules = { FooModule.class, ... })
MyActivity bindMyActivity()
}
class MyActivity : DaggerAppCompatActivity() {
@Inject lateinit var foo: Foo
}
@Module
interface MyActivityModule {
// If you have a scope annotation, see the section on scope aliases
@ContributesAndroidInjector(modules = [ FooModule::class, ... ])
fun bindMyActivity(): MyActivity
}
Intermediate state that allows both Hilt and dagger.android fragments:
@AndroidEntryPoint
public final class MyActivity extends AppCompatActivity
implements HasAndroidInjector {
@Inject Foo foo;
// Remove the code below later when all the children have been migrated
@Inject DispatchAndroidInjector<Object> androidInjector;
@Override
public AndroidInjector<Object> androidInjector() {
return androidInjector;
}
}
// If the list of modules is very short, you don't need this aggregator
// module, just put the @InstallIn(ActivityComponent.class) annotation on
// all the modules in includes list like FooModule
@Module(includes = { FooModule.class, ...})
@InstallIn(ActivityComponent.class)
interface MyActivityAggregatorModule {}
@AndroidEntryPoint
class MyActivity : AppCompatActivity(), HasAndroidInjector {
@Inject lateinit var foo: Foo
// Remove the code below later when all the children have been migrated
@Inject lateinit var androidInjector: DispatchAndroidInjector<Object>
override fun androidInjector() = androidInjector
}
// If the list of modules is very short, you don't need this aggregator
// module, just put the @InstallIn(ActivityComponent.class) annotation on
// all the modules in includes list like FooModule
@Module(includes = [ FooModule::class, ...])
@InstallIn(ActivityComponent::class)
interface MyActivityAggregatorModule
Final state:
@AndroidEntryPoint
public final class MyActivity extends AppCompatActivity {
@Inject Foo foo;
}
// Each activity module is annotated with @InstallIn(ActivityComponent.class)
@AndroidEntryPoint
class MyActivity : AppCompatActivity() {
@Inject lateinit var foo: Foo
}
// Each activity module is annotated with @InstallIn(ActivityComponent::class)
Check your build
You should be able to stop and build/run your app successfully after migrating an activity or fragment. It is a good idea to check after migrating each class to make sure you’re on the right track.
3. Other Android components
View
, Service
, and BroadcastReceiver
types should follow the same formula
as above and be ready to migrate now. Once you have moved everything, you are
done!
Remember to:
- Go back and clean up any leftover
HasAndroidInjector
usages. - Clean up any leftover aggregator modules or entry point interfaces. In
general, you shouldn’t need to use
@Module(includes=)
with Hilt, so if you see that, you’ll want to remove it and just put an@InstallIn
annotation on the included module. - Migrate any old scope annotation and the scope alias if you used that feature
- Migrate any
@Binds
you had to put in place to make component argument bindings match
What to do with … ?
Qualifiers
The qualifiers you have in your project are still valid, they’ll be used by Hilt in the same way they were used by Dagger.
If you have your own @ApplicationContext
and @ActivityContext
qualifiers to
differentiate between different Contexts
in your app, you can add an @Binds
to map them together and then choose to replace your usage with the Hilt
qualifiers at your leisure.
@InstallIn(SingletonComponent.class)
@Module
interface ApplicationContextModule {
@Binds
@my.app.ApplicationContext
Context bindAppContext(
@dagger.hilt.android.qualifiers.ApplicationContext Context context);
}
@InstallIn(SingletonComponent::class)
@Module
interface ApplicationContextModule {
@Binds
@my.app.ApplicationContext
fun bindAppContext(
@dagger.hilt.android.qualifiers.ApplicationContext context: Context) :
Context
}
Component arguments
Because component instantiation is hidden when using Hilt, it is not possible to
add in your own component arguments with either module instances or
@BindsInstance
calls. If you have these in your component, you’ll need to
refactor your code away from using these. Hilt comes with a set of default
bindings in each component which can be seen
here. Depending on what your component
arguments are, you may want to have some of them depend on those default
bindings. This sometimes requires a slight redesign, but most cases can be
solved this way using the following strategies. If that is not the case though,
you may need to consider using a custom component.
For example, in the simplest case, sometimes the binding didn’t need to be
passed in at all and it could be just a regular static @Provides
method. In
another simple case, your argument may just be a variation of the default
binding like a custom BaseFragment type. Hilt can’t know that all Fragments are
going to be an instance of your BaseFragment
, so if you need the actual type
bound to be your BaseFragment
, you’ll need to do that with a cast.
@Component.Builder
interface Builder {
@BindsInstance
Builder fragment(BaseFragment fragment);
}
@InstallIn(FragmentComponent.class)
@Module
final class BaseFragmentModule {
@Provides
static BaseFragment provideBaseFragment(Fragment fragment) {
return (BaseFragment) fragment;
}
}
@Component.Builder
interface Builder {
@BindsInstance
fun fragment(fragment: BaseFragment): Builder
}
@InstallIn(FragmentComponent::class)
@Module
object BaseFragmentModule {
@Provides
fun provideBaseFragment(fragment: Fragment) : BaseFragment {
return fragment as BaseFragment
}
}
In other cases, your argument may be something on one of the default bindings,
like the activity Intent
.
@Component.Builder
interface Builder {
@BindsInstance
Builder intent(Intent intent);
}
@InstallIn(ActivityComponent.class)
@Module
final class IntentModule {
@Provides
static Intent provideIntent(Activity activity) {
return activity.getIntent();
}
}
@Component.Builder
interface Builder {
@BindsInstance
fun intent(intent: Intent): Builder
}
@InstallIn(ActivityComponent::class)
@Module
object IntentModule {
@Provides
fun provideIntent(activity: Activity) : Intent {
return activity.getIntent()
}
}
Finally, you may have to redesign some things if they were configured differently for different activity or fragment components. For example, you could use a new interface on the activity to provide the object.
@Component.Builder
interface Builder {
@BindsInstance
Builder foo(Foo foo); // Foo is different per Activity
}
// Define an interface the activity can implement to provide a custom Foo
interface HasFoo {
Foo getFoo();
}
@InstallIn(ActivityComponent::class)
@Module
final class FooModule {
@Provides
@Nullable
static Foo provideFoo(Activity activity) {
if (activity instanceof HasFoo) {
return ((HasFoo) activity).getFoo();
}
return null;
}
}
@Component.Builder
interface Builder {
@BindsInstance
fun foo(foo: Foo): Builder // Foo is different per Activity
}
// Define an interface the activity can implement to provide a custom Foo
interface HasFoo {
fun getFoo() : Foo
}
@InstallIn(ActivityComponent::class)
@Module
object FooModule {
@Provides
fun provideFoo(activity: Activity) : Foo? {
if (activity is HasFoo) {
return activity.getFoo()
}
return null
}
}
Custom components
If you have other components that do not map to the Hilt components, you should first consider if they can be simplified into the Hilt components. If not though, you can keep your components as manual Dagger components. Choose the section below based on if you want to use component dependencies or subcomponents.
Component dependencies
Component dependencies can be hooked up with an @EntryPoint
.
For example, if you had a component dependency off of the
SingletonComponent
, you can keep it working by factoring out the needed
methods into an interface that is annotated with @EntryPoint
.
// Starting with this component dependency
@Component
interface MySingletonComponent {
// These bindings are exposed for MyCustomComponent
Foo getFoo();
Bar getBar();
Baz getBaz();
...
}
@Component(dependencies = {MySingletonComponent.class})
interface MyCustomComponent {
@Component.Builder
interface Builder {
Builder appComponent(MySingletonComponent appComponent);
MyCustomComponent build();
}
}
// It can be migrated to Hilt with the following classes
@InstallIn(SingletonComponent.class)
@EntryPoint
interface CustomComponentDependencies {
Foo getFoo();
Bar getBar();
Baz getBaz();
...
}
@Component(dependencies = {CustomComponentDependencies.class})
interface MyCustomComponent {
@Component.Builder
interface Builder {
Builder appComponentDeps(CustomComponentDependencies deps);
MyCustomComponent build();
}
}
// Starting with this component dependency
@Component
interface MySingletonComponent {
// These bindings are exposed for MyCustomComponent
fun getFoo(): Foo
fun getBar(): Bar
fun getBaz(): Baz
...
}
@Component(dependencies = [MySingletonComponent::class])
interface MyCustomComponent {
@Component.Builder
interface Builder {
fun appComponent(appComponent: MySingletonComponent): Builder
fun build(): MyCustomComponent
}
}
// It can be migrated to Hilt with the following classes
@InstallIn(SingletonComponent::class)
@EntryPoint
interface CustomComponentDependencies {
fun getFoo(): Foo
fun getBar(): Bar
fun getBaz(): Baz
...
}
@Component(dependencies = [CustomComponentDependencies::class])
interface MyCustomComponent {
@Component.Builder
interface Builder {
fun appComponentDeps(deps: CustomComponentDependencies): Builder
fun build(): MyCustomComponent
}
}
When building the custom component, you can get an instance of the
CustomComponentDependencies
by using EntryPoints
.
DaggerMyCustomComponent.builder()
.appComponentDeps(
EntryPoints.get(
applicationContext,
CustomComponentDependencies.class))
.build();
DaggerMyCustomComponent.builder()
.appComponentDeps(
EntryPoints.get(
applicationContext,
CustomComponentDependencies::class.java))
.build()
Subcomponents
Subcomponents can be added as a child of any Hilt component in the same way you
would install a normal subcomponent with an injectable subcomponent builder in
Dagger. Just install the subcomponent in a module with the appropriate
@InstallIn
of the parent.
For example, if you have a FooSubcomponent
that is a child of the
SingletonComponent
, you can install it like the following example:
@InstallIn(SingletonComponent.class)
@Module(subcomponents = FooSubcomponent.class)
interface FooModule {}
@InstallIn(SingletonComponent::class)
@Module(subcomponents = FooSubcomponent::class)
interface FooModule {}
Component dependencies for components that map to Hilt components
If you currently use component dependencies and your components map relatively well to the Hilt components, then as you migrate you’ll also need to keep in the mind the differences between component dependencies and subcomponents. You may also want to check out this page which describes some of the reasons Hilt chose to use subcomponents.
The main differences to be aware of will be that bindings are automatically inherited from the parent. This means likely getting rid of extra methods for exposing bindings as well as dealing with any duplicate bindings that may arise if a binding is defined in both the parent and child components. Getting rid of those extra methods for exposing bindings is optional as they will not technically break your build, but it is recommended as they can prevent some dead code pruning. They can be safely migrated though as described in this section.
Here is an example of the exposed bindings:
@Component
interface MySingletonComponent {
// These bindings were likely exposed for component dependencies.
// Consider getting rid of them.
Foo getFoo();
Bar getBar();
Baz getBaz();
...
}
@Component
interface MySingletonComponent {
// These bindings were likely exposed for component dependencies.
// Consider getting rid of them.
fun getFoo(): Foo
fun getBar(): Bar
fun getBaz(): Baz
...
}
Then when you follow steps above to migrate components, if your component has a dep on a component that is equivalent to the Hilt parent, just remove the dep as you remove the rest of the component.
// Just delete these deps as you follow the migration guide for migrating
// the rest of the component
@Component(deps = {MySingletonComponent.class})
interface MyActivityComponent {
...
}
// Just delete these deps as you follow the migration guide for migrating
// the rest of the component
@Component(deps = [MySingletonComponent::class])
interface MyActivityComponent {
...
}