Claude Code Plugins

Community-maintained marketplace

Feedback

Hilt dependency injection library for Android. Use for Android DI, Dagger integration, component hierarchy, ViewModel injection, testing with Hilt, and compile-time dependency injection.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name hilt
description Hilt dependency injection library for Android. Use for Android DI, Dagger integration, component hierarchy, ViewModel injection, testing with Hilt, and compile-time dependency injection.

Hilt Skill

Comprehensive assistance with hilt development, generated from official documentation.

When to Use This Skill

This skill should be triggered when:

  • Working with hilt
  • Asking about hilt features or APIs
  • Implementing hilt solutions
  • Debugging hilt code
  • Learning hilt best practices

Quick Reference

Common Patterns

Pattern 1: Hilt Benefits Gradle Setup Quick Start Core APIs → Components → Hilt Application → Android Entry Points → View Models → Modules → Entry Points → Custom Components Testing → Testing overview → Robolectric testing → Instrumentation testing → Early Entry Points Migration → Guide → Custom inject → Optional inject → Scope aliases Flags Creating Extensions Design Decisions → Design overview → Testing philosophy → Monolithic components → Subcomponents The @EarlyEntryPoint annotation provides an escape hatch when a Hilt entry point needs to be created before the singleton component is available in a Hilt test. Note that, although @EarlyEntryPoint and EarlyEntryPoints are mostly used in production code, they only have an effect during Hilt tests. In production, these entry points behave the same as @EntryPoint and EntryPoints, respectively. Background In a Hilt test, the singleton component’s lifetime is scoped to the lifetime of a test case rather than the lifetime of the Application. This is useful to prevent leaking state across test cases, but it makes it impossible to access entry points from a component outside of a test case. To get a better understanding of why/when this becomes an issue, let’s look at a typical lifecycle of an Android Gradle instrumentation test. # Typical Application lifecycle during an Android Gradle instrumentation test - Application created - Application.onCreate() called - Test1 created - SingletonComponent created - testCase1() called - Test1 created - SingletonComponent created - testCase2() called ... - Test2 created - SingletonComponent created - testCase1() called - Test2 created - SingletonComponent created - testCase2() called ... - Application destroyed As the lifecycle above shows, Application#onCreate() is called before any SingletonComponent can be created, so calling an entry point from Application#onCreate() is not possible. (For the same reason, there are similar issues with calling entry points from ContentProvider#onCreate()). While these cases should be rare, sometimes they are unavoidable. This is where @EarlyEntryPoint comes in. Usage Annotating an entry point with @EarlyEntryPoint instead of @EntryPoint allows the entry point to be called at any point during the lifecyle of a test application. (Note that an @EarlyEntryPoint can only be installed in the SingletonComponent). For example: Java Kotlin @EarlyEntryPoint @InstallIn(SingletonComponent.class) public interface FooEntryPoint { Foo foo(); } @EarlyEntryPoint @InstallIn(SingletonComponent::class) interface FooEntryPoint { fun foo(): Foo } Once annotated with @EarlyEntryPoint, all usages of the entry point must go through EarlyEntryPoints#get() (rather than EntryPoints#get() ) to get an instance of the entry point. This requirement makes it clear at the call site which component will be used during a Hilt test. For example: Java Kotlin // A base application used in a Hilt test that injects objects in onCreate public abstract class BaseTestApplication extends Application { @Override public void onCreate() { super.onCreate(); // Entry points annotated with @EarlyEntryPoint must use // EarlyEntryPoints rather than EntryPoints. foo = EarlyEntryPoints.get(this, FooEntryPoint.class).foo(); } } // A base application used in a Hilt test that injects objects in onCreate public abstract class BaseTestApplication: Application { override fun onCreate() { super.onCreate() // Entry points annotated with @EarlyEntryPoint must use // EarlyEntryPoints rather than EntryPoints. foo = EarlyEntryPoints.get(this, FooEntryPoint::class).foo() } } Caveats The component used with EarlyEntryPoints does not share any state with the singleton component used for a given test case. Even @Singleton scoped bindings will not be shared. The component used with EarlyEntryPoints does not have access to any test-specific bindings (i.e. bindings created within a specific test class such as @BindValue or a nested module). Finally, the component used with EarlyEntryPoints lives for the lifetime of the application, so it can leak state across multiple test cases (e.g. in Android Gradle instrumentation tests). When not to use EarlyEntryPoint Most usages of @EarlyEntryPoint are needed to allow calling entry points from within Application#onCreate() or ContentProvider#onCreate(). However, before switching to @EarlyEntryPoint, try the alternatives listed below. Entry points for Application getter methods If the entry point is used to initialize a field that will later be returned in a getter method, consider removing the field and getter method and replacing it with a @Singleton scoped binding that other classes can inject directly rather than going through the application class. If the getter method is required (e.g. the application must extend an interface that requires it to be overriden) then try replacing the field with a @Singleton scoped binding and calling EntryPoints.get() lazily from the getter method. Entry points for initialization/configuration If the entry point is used to perform initialization/configuration (e.g. setting up a logger or prefetching data) then first consider whether this work is necessary for your tests. Most tests, e.g. tests for activities and fragments should not be dependent on this initialization to work properly, since activities and fragments should generally be designed to be reusable in other applications. If your test needs the initialization/configuration, consider whether it’s okay to only run the initialization/configuration once and share any state of that run between tests. If that’s not okay, then you may need to consider moving the logic into a TestRule instead.

@EarlyEntryPoint

Pattern 2: Hilt Benefits Gradle Setup Quick Start Core APIs → Components → Hilt Application → Android Entry Points → View Models → Modules → Entry Points → Custom Components Testing → Testing overview → Robolectric testing → Instrumentation testing → Early Entry Points Migration → Guide → Custom inject → Optional inject → Scope aliases Flags Creating Extensions Design Decisions → Design overview → Testing philosophy → Monolithic components → Subcomponents Why would you need optional injection? Hilt fragments need to be attached to Hilt activities and Hilt activities need to be attached to Hilt applications. While this is a natural restriction for pure Hilt codebases, it may be an issue during a migration to Hilt if you have a fragment or activity that is used in a non-Hilt context. For example, say you want to migrate a fragment to Hilt but it is used in too many places to migrate at once. Without optional injection, you would have to migrate every activity that uses that fragment to Hilt first otherwise the fragment will crash when looking for the Hilt components when it is trying to inject itself. Depending on the size of your codebase, this could be a large undertaking. How to use @OptionalInject If you mark an @AndroidEntryPoint class with @OptionalInject then it will only try to inject if the parent is using Hilt and not require it. Using this annotation will also cause a wasInjectedByHilt() method to be generated on the generated base class that returns true if it was successful injecting. Note: Because API generated on the base class is inaccessible to users of the gradle plugin, there is an alternative API to access this functionality using a static helper method in OptionalInjectCheck. This gives you the chance to provide dependencies in a different way (usually whichever way you were getting dependencies before using Hilt). For example: Java Kotlin @OptionalInject @AndroidEntryPoint public final class MyFragment extends Fragment { @Inject Foo foo; @Override public void onAttach(Activity activity) { super.onAttach(activity); // Injection will happen here, but only if the Activity used Hilt if (!OptionalInjectCheck.wasInjectedByHilt(this)) { // Get Dagger components the previous way and inject } } } @OptionalInject @AndroidEntryPoint class MyFragment : Fragment() { @Inject lateinit var foo: Foo override fun onAttach(activity: Activity) { super.onAttach(activity) // Injection will happen here, but only if the Activity used Hilt if (!OptionalInjectCheck.wasInjectedByHilt(this)) { // Get Dagger components the previous way and inject } } } Note that for activities, because Hilt injection is usually run as a part of super.onCreate() and it is recommended to do your own injection before fragments are restored which also happens during super.onCreate(), you likely need to use an OnContextAvailableListener to run your non-Hilt injection code. Hilt uses the same listener under the hood, so then the order would be Hilt’s OnContextAvailableListener would run, then yours, then fragments would be restored.

@OptionalInject

Pattern 3: Hilt Benefits Gradle Setup Quick Start Core APIs → Components → Hilt Application → Android Entry Points → View Models → Modules → Entry Points → Custom Components Testing → Testing overview → Robolectric testing → Instrumentation testing → Early Entry Points Migration → Guide → Custom inject → Optional inject → Scope aliases Flags Creating Extensions Design Decisions → Design overview → Testing philosophy → Monolithic components → Subcomponents Note: The following page assumes a basic knowledge of Dagger, including components, modules, scopes, and bindings. (For a refresher, see Dagger users guide.) Component hierarchy Unlike traditional Dagger, Hilt users never define or instantiate Dagger components directly. Instead, Hilt offers predefined components that are generated for you. Hilt comes with a built-in set of components (and corresponding scope annotations) that are automatically integrated into the various lifecycles of an Android application. The diagram below shows the standard Hilt component hierarchy. The annotation above each component is the scoping annotation used to scope bindings to the lifetime of that component. The arrow below a component points to any child components. As normal, a binding in a child component can have dependencies on any binding in an ancestor component. Note: When scoping a binding within an @InstallIn module, the scope on the binding must match the scope of the component. For example, a binding within an @InstallIn(ActivityComponent.class) module can only be scoped with @ActivityScoped. Components used for injection When using Hilt APIs like @AndroidEntryPoint to inject your Android classes, the standard Hilt components are used as the injectors. The component used as the injector will determine which bindings are visible to that Android class. The components used are shown in the table below: Component Injector for SingletonComponent Application ViewModelComponent ViewModel ActivityComponent Activity FragmentComponent Fragment ViewComponent View ViewWithFragmentComponent View with @WithFragmentBindings ServiceComponent Service Component lifetimes The lifetime of a component is important because it relates to the lifetime of your bindings in two important ways: It bounds the lifetime of scoped bindings between when the component is created and when it is destroyed. It indicates when members injected values can be used (e.g. when @Inject fields are not null). Component lifetimes are generally bounded by the creation and destruction of a corresponding instance of an Android class. The table below lists the scope annotation and bounded lifetime for each component. Component Scope Created at Destroyed at SingletonComponent @Singleton Application#onCreate() Application process is destroyed ActivityRetainedComponent @ActivityRetainedScoped Activity#onCreate()1 Activity#onDestroy()1 ViewModelComponent @ViewModelScoped ViewModel created ViewModel destroyed ActivityComponent @ActivityScoped Activity#onCreate() Activity#onDestroy() FragmentComponent @FragmentScoped Fragment#onAttach() Fragment#onDestroy() ViewComponent @ViewScoped View#super() View destroyed ViewWithFragmentComponent @ViewScoped View#super() View destroyed ServiceComponent @ServiceScoped Service#onCreate() Service#onDestroy() Scoped vs unscoped bindings By default, all bindings in Dagger are “unscoped”. This means that each time the binding is requested, Dagger will create a new instance of the binding. However, Dagger also allows a binding to be “scoped” to a particular component (see the scope annotations in the table above). A scoped binding will only be created once per instance of the component it’s scoped to, and all requests for that binding will share the same instance. Example: Java Kotlin // This binding is "unscoped". // Each request for this binding will get a new instance. final class UnscopedBinding { @Inject UnscopedBinding() {} } // This binding is "scoped". // Each request from the same component instance for this binding will // get the same instance. Since this is the fragment component, this means // each request from the same fragment. @FragmentScoped final class ScopedBinding { @Inject ScopedBinding() {} } // This binding is "unscoped". // Each request for this binding will get a new instance. class UnscopedBinding @Inject constructor() { } // This binding is "scoped". // Each request from the same component instance for this binding will // get the same instance. Since this is the fragment component, this means // each request from the same fragment. @FragmentScoped class ScopedBinding @Inject constructor() { } Warning: A common misconception is that all fragment instances will share the same instance of a binding scoped with @FragmentScoped. However, this is not true. Each fragment instance gets a new instance of the fragment component, and thus a new instance of all its scoped bindings. Scoping in modules The previous section showed how to scope a binding declared with an @Inject constructor, but a binding declared in a module can also be scoped in a similar way. Example: Java Kotlin @Module @InstallIn(FragmentComponent.class) abstract class FooModule { // This binding is "unscoped". @Provides static UnscopedBinding provideUnscopedBinding() { return new UnscopedBinding(); } // This binding is "scoped". @Provides @FragmentScoped static ScopedBinding provideScopedBinding() { return new ScopedBinding(); } } @Module @InstallIn(FragmentComponent::class) object FooModule { // This binding is "unscoped". @Provides fun provideUnscopedBinding() = UnscopedBinding() // This binding is "scoped". @Provides @FragmentScoped fun provideScopedBinding() = ScopedBinding() } Warning: A common misconception is that all bindings declared in a module will be scoped to the component the module is installed in. However, this isn’t true. Only bindings declarations annotated with a scope annotation will be scoped. When to scope? Scoping a binding has a cost on both the generated code size and its runtime performance so use scoping sparingly. The general rule for determining if a binding should be scoped is to only scope the binding if it’s required for the correctness of the code. If you think a binding should be scoped for purely performance reasons, first verify that the performance is an issue, and if it is consider using @Reusable instead of a component scope. Component default bindings Each Hilt component comes with a set of default bindings that can be injected as dependencies into your own custom bindings. Each component listed has the corresponding default bindings as well as any default bindings from an ancestor component. Component Default Bindings SingletonComponent Application2 ActivityRetainedComponent ActivityRetainedLifecycle ViewModelComponent SavedStateHandle, ViewModelLifecycle ActivityComponent Activity, FragmentActivity FragmentComponent Fragment ViewComponent View ViewWithFragmentComponent View ServiceComponent Service ActivityRetainedComponent lives across configuration changes, so it is created at the first onCreate and last onDestroy. ↩ ↩2 The Application binding is available using either @ApplicationContext Context or Application. [^3]: @ActivityRetainedSavedState SavedStateHandlemust be used with @OptIn(UnstableApi.class). This binding relies on an experimental implementation to lazily create SavedStateHandle, which should be safe to rely on, but it is still possible that a future release may remove the binding if a bug is uncovered. ↩

@InstallIn

Pattern 4: Hilt Benefits Gradle Setup Quick Start Core APIs → Components → Hilt Application → Android Entry Points → View Models → Modules → Entry Points → Custom Components Testing → Testing overview → Robolectric testing → Instrumentation testing → Early Entry Points Migration → Guide → Custom inject → Optional inject → Scope aliases Flags Creating Extensions Design Decisions → Design overview → Testing philosophy → Monolithic components → Subcomponents Introduction Hilt makes testing easier by bringing the power of dependency injection to your Android tests. Hilt allows your tests to easily access Dagger bindings, provide new bindings, or even replace bindings. Each test gets its own set of Hilt components so that you can easily customize bindings at a per-test level. Many of the testing APIs and functionality described in this documentation are based upon an unstated philosophy of what makes a good test. For more details on Hilt’s testing philosophy see here. Test Setup Note: For Gradle users, make sure to first add the Hilt test build dependencies as described in the Gradle setup guide. To use Hilt in a test: Annotate the test with @HiltAndroidTest, Add the HiltAndroidRule test rule, Use HiltTestApplication for your Android Application class. For example: Java Kotlin @HiltAndroidTest public class FooTest { @Rule public HiltAndroidRule hiltRule = new HiltAndroidRule(this); ... } @HiltAndroidTest class FooTest { @get:Rule val hiltRule = HiltAndroidRule(this) ... } Note that setting the application class for a test (step 3 above) is dependent on whether the test is a Robolectric or instrumentation test. For a more detailed guide on how to set the test application for a particular test environment, see Robolectric testing or Instrumentation testing. The remainder of this doc applies to both Robolectric and instrumentation tests. If your test requires a custom application class, see the section on custom test application. If your test requires multiple test rules, see the section on Hilt rule order to determine the proper placement of the Hilt rule. Accessing bindings A test often needs to request bindings from its Hilt components. This section describes how to request bindings from each of the different components. Accessing SingletonComponent bindings An SingletonComponent binding can be injected directly into a test using an @Inject annotated field. Injection doesn’t occur until calling HiltAndroidRule#inject(). Java Kotlin @HiltAndroidTest class FooTest { @Rule public HiltAndroidRule hiltRule = new HiltAndroidRule(this); @Inject Foo foo; @Test public void testFoo() { assertNull(foo); hiltRule.inject(); assertNotNull(foo); } } @HiltAndroidTest class FooTest { @get:Rule val hiltRule = HiltAndroidRule(this) @Inject lateinit var foo: Foo @Test fun testFoo() { hiltRule.inject() assertNotNull(foo) } } Accessing ActivityComponent bindings Requesting an ActivityComponent binding requires an instance of a Hilt Activity. One way to do this is to define a nested activity within your test that contains an @Inject field for the binding you need. Then create an instance of your test activity to get the binding. Java Kotlin @HiltAndroidTest class FooTest { @AndroidEntryPoint public static final class TestActivity extends AppCompatActivity { @Inject Foo foo; } // Create the activity through standard testing APIs and get an // instance as testActivity. Make sure the activity has gone through // onCreate() ... // Now just access the foo which has been injected on the activity directly Foo foo = testActivity.foo; } @HiltAndroidTest class FooTest { @AndroidEntryPoint class TestActivity : AppCompatActivity() { @Inject lateinit var foo: Foo } // Create the activity through standard testing APIs and get an // instance as testActivity. Make sure the activity has gone through // onCreate() ... // Now just access the foo which has been injected on the activity directly val foo = testActivity.foo } Alternatively, if you already have a Hilt activity instance available in your test, you can get any ActivityComponent binding using an EntryPoint. Java Kotlin @HiltAndroidTest class FooTest { @EntryPoint @InstallIn(ActivityComponent.class) interface FooEntryPoint { Foo getFoo(); } ... Foo foo = EntryPoints.get(activity, FooEntryPoint.class).getFoo(); } @HiltAndroidTest class FooTest { @EntryPoint @InstallIn(ActivityComponent::class) interface FooEntryPoint { fun getFoo() : Foo } ... val foo = EntryPoints.get(activity, FooEntryPoint::class.java).getFoo() } Accessing FragmentComponent bindings A FragmentComponent binding can be accessed in a similar way to an ActivityComponent binding. The main difference is that accessing a FragmentComponent binding requires both an instance of a Hilt Activity and a Hilt Fragment. Java Kotlin @HiltAndroidTest class FooTest { @AndroidEntryPoint public static final class TestFragment extends Fragment { @Inject Foo foo; } ... Foo foo = testFragment.foo; } @HiltAndroidTest class FooTest { @AndroidEntryPoint class TestFragment : Fragment() { @Inject lateinit var foo: Foo } ... val foo = testFragment.foo } Alternatively, if you already have a Hilt fragment instance available in your test, you can get any FragmentComponent binding using an EntryPoint. Java Kotlin @HiltAndroidTest class FooTest { @EntryPoint @InstallIn(FragmentComponent.class) interface FooEntryPoint { Foo getFoo(); } ... Foo foo = EntryPoints.get(fragment, FooEntryPoint.class).getFoo(); } @HiltAndroidTest class FooTest { @EntryPoint @InstallIn(FragmentComponent::class) interface FooEntryPoint { fun getFoo() : Foo } ... val foo = EntryPoints.get(fragment, FooEntryPoint::class.java).getFoo() } Warning:Hilt does not currently support FragmentScenario because there is no way to specify an activity class, and Hilt requires a Hilt fragment to be contained in a Hilt activity. One workaround for this is to launch a Hilt activity and then attach your fragment. Replacing bindings It’s often useful for tests to be able to replace a production binding with a fake or mock binding to make tests more hermetic or easier to control in test. The next sections describe some ways to accomplish this in Hilt. @TestInstallIn A Dagger module annotated with @TestInstallIn allows users to replace an existing @InstallIn module for all tests in a given source set. For example, suppose we want to replace ProdDataServiceModule with FakeDataServiceModule. We can accomplish this by annotating FakeDataServiceModule with @TestInstallIn, as shown below: Java Kotlin @Module @TestInstallIn( components = SingletonComponent.class, replaces = ProdDataServiceModule.class) interface FakeDataServiceModule { @Binds DataService bind(FakeDataService impl); } @Module @TestInstallIn( components = SingletonComponent::class, replaces = ProdDataServiceModule::class) interface FakeDataServiceModule { @Binds fun bind(impl: FakeDataService): DataService } A @TestInstallIn module can be included in the same source set as your test sources, as shown below: :foo |_ srcs/test/java/my/project/foo |_ FooTest.java |_ BarTest.java |_ FakeDataServiceModule.java However, if a particular @TestInstallIn module is needed in multiple Gradle modules, we recommend putting it in its own Gradle module (usually the same one as the fake), as shown below: :dataservice-testing |_ srcs/main/java/my/project/dataservice/testing |_ FakeDataService.java |_ FakeDataServiceModule.java // This depends on testImplementation project(":dataservice-testing") :foo/build.gradle // This depends on testImplementation project(":dataservice-testing") :bar/build.gradle Putting the @TestInstallIn in the same Gradle module as the fake has a number of benefits. First, it ensures that all clients that depend on the fake properly replace the production module with the test module. It also avoids duplicating FakeDataServiceModule for every Gradle module that needs it. Note that @TestInstallIn applies to all tests in a given source set. For cases where an individual test needs to replace a binding that is specific to the given test, the test can either be moved into its own source set, or it can use Hilt testing features such as @UninstallModules, @BindValue, and nested @InstallIn modules to replace bindings specific to that test. These features will be described in more detail in the following sections. @UninstallModules Warning:Test classes that use @UninstallModules, @BindValue, or nested @InstallIn modules result in a custom component being generated for that test. While this may be fine in most cases, it does have an impact on build speed. The recommended approach is to use @TestInstallIn modules instead. A test annotated with @UninstallModules can uninstall production @InstallIn modules for that particular test (unlike @TestInstallIn, it has no effect on other tests). Once a module is uninstalled, the test can install new, test-specific bindings for that particular test. Java Kotlin @UninstallModules(ProdFooModule.class) @HiltAndroidTest public class FooTest { // ... Install a new binding for Foo } @UninstallModules(ProdFooModule::class) @HiltAndroidTest class FooTest { // ... Install a new binding for Foo } There are two ways to install a new binding for a particular test: Add an @InstallIn module nested within the test that provides the binding. Add an @BindValue field within the test that provides the binding. These two approaches are described in more detail in the next sections. Note: @UninstallModules can only uninstall @InstallIn modules, not @TestInstallIn modules. If a @TestInstallIn module needs to be uninstalled the module must be split into two separate modules: a @TestInstallIn module that replaces the production module with no bindings (i.e. only removes the production module), and a @InstallIn module that provides the standard fake so that @UninstallModules can uninstall the provided fake. Nested @InstallIn modules Warning:Test classes that use @UninstallModules, @BindValue, or nested @InstallIn modules result in a custom component being generated for that test. While this may be fine in most cases, it does have an impact on build speed. The recommended approach is to use @TestInstallIn modules instead. Normally, @InstallIn modules are installed in the Hilt components of every test. However, if a binding needs to be installed only in a particular test, that can be accomplished by nesting the @InstallIn module within the test class. Java Kotlin @HiltAndroidTest public class FooTest { // Nested modules are only installed in the Hilt components of the outer test. @Module @InstallIn(SingletonComponent.class) static class FakeBarModule { @Provides static Bar provideBar(...) { return new FakeBar(...); } } ... } @HiltAndroidTest class FooTest { // Nested modules are only installed in the Hilt components of the outer test. @Module @InstallIn(SingletonComponent::class) object FakeBarModule { @Provides fun provideBar() = Bar() } ... } Thus, if there is another test that needs to provision the same binding with a different implementation, it can do that without a duplicate binding conflict. In addition to static nested @InstallIn modules, Hilt also supports inner (non-static) @InstallIn modules within tests. Using an inner module allows the @Provides methods to reference members of the test instance. Note: Hilt does not support @InstallIn modules with constructor parameters. @BindValue Warning:Test classes that use @UninstallModules, @BindValue, or nested @InstallIn modules result in a custom component being generated for that test. While this may be fine in most cases, it does have an impact on build speed. The recommended approach is to use @TestInstallIn modules instead. For simple bindings, especially those that need to also be accessed in the test methods, Hilt provides a convenience annotation to avoid the boilerplate of creating a module and method normally required to provision a binding. @BindValue is an annotation that allows you to easily bind fields in your test into the Dagger graph. To use it, just annotate a field with @BindValue and it will be bound to the declared field type with any qualifiers that are present on the field. Java Kotlin @HiltAndroidTest public class FooTest { ... @BindValue Bar fakeBar = new FakeBar(); } @HiltAndroidTest class FooTest { ... @BindValue @JvmField val fakeBar: Bar = FakeBar() } Note that @BindValue does not support the use of scope annotations since the binding’s scope is tied to the field and controlled by the test. The field’s value is queried whenever it is requested, so it can be mutated as necessary for your test. If you want the binding to be effectively singleton, just ensure that the field is only set once per test case, e.g. by setting the field’s value from either the field’s initializer or from within an @Before method of the test. Similarly, Hilt also has a convenience annotation for multibindings with @BindValueIntoSet, @BindElementsIntoSet, and @BindValueIntoMap to support @IntoSet, @ElementsIntoSet, and @IntoMap respectively. (Note that @BindValueIntoMap requires the field to also be annotated with a map key annotation.) Warning:Be careful when using @BindValue or non-static inner modules with ActivityScenarioRule. ActivityScenarioRule creates the activity before calling the @Before method, so if an @BindValue field is initialized in @Before (or later), then it’s possible for the Activity to inject the binding in its unitialized state. To avoid this, try initializing the @BindValue field in the field’s initializer. Custom test application Every Hilt test must use a Hilt test application as the Android application class. Hilt comes with a default test application, HiltTestApplication, which extends MultiDexApplication; however, there are cases where a test may need to use a different base class. @CustomTestApplication If your test requires a custom base class, @CustomTestApplication can be used to generate a Hilt test application that extends the given base class. To use @CustomTestApplication, just annotate a class or interface with @CustomTestApplication and specify the base class in the annotation value: Java Kotlin // Generates MyCustom_Application.class @CustomTestApplication(MyBaseApplication.class) interface MyCustom {} // Generates MyCustom_Application.class @CustomTestApplication(MyBaseApplication::class) interface MyCustom In the above example, Hilt will generate an application named MyCustom_Application that extends MyBaseApplication. In general, the name of the generated application will be the name of the annotated class appended with _Application. If the annotated class is a nested class, the name will also include the name of the outer class separated by an underscore. Note that the class that is annotated is irrelevant, other than for the name of the generated application. Best practices As a best practice, avoid using @CustomTestApplication and instead use HiltTestApplication in your tests. In general, having your Activity, Fragment, etc. be independent of the parent they are contained in makes it easier to compose and reuse it in the future. However, if you must use a custom base application, there are some subtle differences with the production lifecycle to be aware of. One difference is that instrumentation tests use the same application instance for every test and test case. Thus, it’s easy to accidentally leak state across test cases when using a custom test application. Instead, it’s better to avoid storing any test or test case dependendent state in your application. Another difference is that the Hilt component in a test application is not created in super#onCreate. This restriction is mainly due to fact that some of Hilt’s features (e.g. @BindValue) rely on the test instance, which is not available in tests until after Application#onCreate is called. Thus, unlike production applications, custom base applications must avoid calling into the component during Application#onCreate. This includes injecting members into the application. To prevent this issue, Hilt doesn’t allow injection in the base application. Hilt rule order If your test uses multiple test rules, make sure that the HiltAndroidRule runs before any other test rules that require access to the Hilt component. For example ActivityScenarioRule calls Activity#onCreate, which (for Hilt activities) requires the Hilt component to perform injection. Thus, the ActivityScenarioRule should run after the HiltAndroidRule to ensure that the component has been properly initialized. Note: If you’re using JUnit < 4.13 use RuleChain to specify the order instead. Java Kotlin @HiltAndroidTest public class FooTest { // Ensures that the Hilt component is initialized before running the ActivityScenarioRule @Rule(order = 0) public HiltAndroidRule hiltRule = new HiltAndroidRule(this); @Rule(order = 1) public ActivityScenarioRule scenarioRule = new ActivityScenarioRule(MyActivity.class); } @HiltAndroidTest class FooTest { // Ensures that the Hilt component is initialized before running the ActivityScenarioRule @get:Rule(order = 0) val hiltRule = HiltAndroidRule(this) @get:Rule(order = 1) val scenarioRule = ActivityScenarioRule(MyActivity::class.java) }

@HiltAndroidTest

Pattern 5: For example:

@HiltAndroidTest
public class FooTest {
  @Rule public HiltAndroidRule hiltRule = new HiltAndroidRule(this);
  ...
}

Pattern 6: Hilt Benefits Gradle Setup Quick Start Core APIs → Components → Hilt Application → Android Entry Points → View Models → Modules → Entry Points → Custom Components Testing → Testing overview → Robolectric testing → Instrumentation testing → Early Entry Points Migration → Guide → Custom inject → Optional inject → Scope aliases Flags Creating Extensions Design Decisions → Design overview → Testing philosophy → Monolithic components → Subcomponents 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 Compare component hierarchies Be aware of when Hilt injects classes Migration Overview 1. Migrate the Application Migrating a Component a. Handle the modules b. Handle any extended interfaces or methods Moving everything with @EntryPoint Inject methods Accessing the interfaces c. Scopes d. Handling component arguments e. Cleaning up aggregators Adding Hilt to the Application dagger.android Application dagger.android Test Application Check your build 2. Migrate Activities and Fragments (and other classes) Be aware of differences with monolithic components Conflicting bindings Depending on the specific activity type Retained fragments Adding Hilt to the Activity/Fragment Dagger dagger.android A simple dagger.android example Check your build 3. Other Android components What to do with … ? Qualifiers Component arguments Custom components Component dependencies Subcomponents Component dependencies for components that map to Hilt components 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. Java Kotlin // 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: Java Kotlin // 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. Java Kotlin @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. Java Kotlin // 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. Java Kotlin @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: Java Kotlin @HiltAndroidApp public final class MyApplication implements HasAndroidInjector { @Inject DispatchingAndroidInjector dispatchingAndroidInjector; @Override public AndroidInjector androidInjector() { return dispatchingAndroidInjector; } } @HiltAndroidApp class MyApplication : HasAndroidInjector { @Inject lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector 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. This is likely what your previous Dagger component was doing before. Java Kotlin @HiltAndroidApp public final class MyApplication extends DaggerApplication { @EntryPoint @InstallIn(SingletonComponent.class) interface ApplicationInjector extends AndroidInjector { } @Override public AndroidInjector applicationInjector() { return EntryPoints.get(this, ApplicationInjector.class); } } @HiltAndroidApp class MyApplication : DaggerApplication() { @EntryPoint @InstallIn(SingletonComponent::class) interface ApplicationInjector : AndroidInjector override fun applicationInjector(): AndroidInjector { 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: Java Kotlin // 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 dispatchingAndroidInjector(); } @Override public AndroidInjector 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 } override fun androidInjector(): AndroidInjector { 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: Java Kotlin // 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). Java Kotlin @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: Java Kotlin 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: Java Kotlin @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 androidInjector; @Override public AndroidInjector 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 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: Java Kotlin @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. Java Kotlin @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. Java Kotlin @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. Java Kotlin @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. Java Kotlin @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. Java Kotlin // 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. Java Kotlin 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: Java Kotlin @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: Java Kotlin @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. Java Kotlin // 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 { ... }

Application

Pattern 7: For example, if you have a FooSubcomponent that is a child of the SingletonComponent, you can install it like the following example:

FooSubcomponent

Pattern 8: Hilt Benefits Gradle Setup Quick Start Core APIs → Components → Hilt Application → Android Entry Points → View Models → Modules → Entry Points → Custom Components Testing → Testing overview → Robolectric testing → Instrumentation testing → Early Entry Points Migration → Guide → Custom inject → Optional inject → Scope aliases Flags Creating Extensions Design Decisions → Design overview → Testing philosophy → Monolithic components → Subcomponents Is a custom component needed? Hilt has predefined components for Android that are managed for you. However, there may be situations where the standard Hilt components do not match the object lifetimes or needs of a particular feature. In these cases, you may want a custom component. However, before creating a custom component, consider if you really need one as not every place where you can logically add a custom component deserves one. For example, consider a background task. The task has a reasonably well-defined lifetime that could make sense for a scope. Also, if there were a request object for that task, binding that into Dagger may save some work passing that around as a parameter. However, for most background tasks, a component really isn’t necessary and only adds complexity where simply passing a couple objects on the call stack is simpler and sufficient. Before commiting to adding a custom component, consider the following drawbacks. Adding a custom component has the following drawbacks: Each component/scope adds cognitive overhead. They can complicate the graph with combinatorics (e.g. if the component is a child of the ViewComponent conceptually, two components likely need to be added for ViewComponent and ViewWithFragmentComponent). Components can have only one parent. The component hierarchy can’t form a diamond. Creating more components increases the likelihood of getting into a situation where a diamond dependency is needed. Unfortunately, there is no good solution to this diamond problem and it can be difficult to predict and avoid. Custom components work against standardization. The more custom components are used, the harder it is for shared libraries. With those in mind, these are some criteria you should use for deciding if a custom component is needed: The component has a well-defined lifetime associated with it. The concept of the component is well-understood and widely applicable. Hilt components are global to the app so the concepts should be applicable everywhere. Being globally understood also combats some of the issues with cognitive overhead. Consider if a non-Hilt (regular Dagger) component is sufficient. For components with a limited purpose sometimes it is better to use a non-Hilt component. For example, consider a production component that represents a single background task. Hilt components excel in situations where code needs to be contributed from possibly disjoint/modular code. If your component isn’t really meant to be extensible, it may not be a good match for a Hilt custom component. Custom component limitations Custom component definitions currently have some limitations: Components must be a direct or indirect child of the SingletonComponent. Components may not be inserted between any of the standard components. For example, a component cannot be added between the ActivityComponent and the FragmentComponent. Adding a custom Hilt component To create a custom Hilt component, create a class annotated with @DefineComponent. This will be the class used in @InstallIn annotations. The parent of your component should be defined in the value of the @DefineComponent annotation. Your @DefineComponent class can also be annotated with a scope annotation to allow scoping objects to this component. For example: Java Kotlin @DefineComponent(parent = SingletonComponent.class) interface MyCustomComponent {} @DefineComponent(parent = SingletonComponent::class) interface MyCustomComponent A builder interface must also be defined. If this builder is missing, the component will not be generated since there will be no way to construct the component. This interface will be injectable from the parent component and will be the interface for creating new instances of your component. As these are custom components, once instances are built, it will be your job to hold on to or release component instances at the appropriate time. Builder interfaces are defined by marking an interface with @DefineComponent.Builder. Builders must have a method that returns the @DefineComponent type. They may also have additional methods (like @BindsInstance methods) that a normal Dagger component builder may have. For example: Java Kotlin @DefineComponent.Builder interface MyCustomComponentBuilder { MyCustomComponentBuilder fooSeedData(@BindsInstance Foo foo); MyCustomComponent build(); } @DefineComponent.Builder interface MyCustomComponentBuilder { fun fooSeedData(@BindsInstance foo: Foo): MyCustomComponentBuilder fun build(): MyCustomComponent } While the @DefineComponent.Builder class can be nested within the @DefineComponent, it is usually better as a separate class. It may be separated into a different class as long as it is a transitive dependency of the @HiltAndroidApp application or @HiltAndroidTest test. Since the @DefineComponent class is referenced in many places via @InstallIn, it may be better to separate the builder so that dependencies in the builder do not become transitive dependencies of every module installed in the component. For the same reason of avoiding excessive dependencies, methods are not allowed on the @DefineComponent interface. Instead, Dagger objects should be accessed via entry points. Java Kotlin @EntryPoint @InstallIn(MyCustomComponent.class) interface MyCustomEntryPoint { Bar getBar(); } public final class CustomComponentManager { private final MyCustomComponentBuilder componentBuilder; @Inject CustomComponentManager(MyCustomComponentBuilder componentBuilder) { this.componentBuilder = componentBuilder; } void doSomething(Foo foo) { MyCustomComponent component = componentBuilder.fooSeedData(foo).build(); Bar bar = EntryPoints.get(component, MyCustomEntryPoint.class).getBar(); // Don't forget to hold on to the component instance if you need to! } @EntryPoint @InstallIn(MyCustomComponent::class) interface MyCustomEntryPoint { fun getBar(): Bar } class CustomComponentManager @Inject constructor( componentBuilder: MyCustomComponentBuilder) { fun doSomething(foo: Foo) { val component = componentBuilder.fooSeedData(foo).build(); val bar = EntryPoints.get(component, MyCustomEntryPoint::class.java).getBar() // Don't forget to hold on to the component instance if you need to! }

ViewComponent

Example Code Patterns

Example 1 (java):

@HiltAndroidApp(MultiDexApplication.class)
public final class MyApplication extends Hilt_MyApplication {}

Example 2 (java):

@HiltAndroidApp(MultiDexApplication.class)
public final class MyApplication extends Hilt_MyApplication {}

Example 3 (java):

@GeneratesRootInput
public @interface GenerateMyModule {}

Example 4 (java):

@EntryPoint
@InstallIn(SingletonComponent.class)
public interface FooBarInterface {
  @Foo Bar bar();
}

Example 5 (java):

@HiltViewModel
public final class FooViewModel extends ViewModel {

  @Inject
  FooViewModel(SavedStateHandle handle, Foo foo) {
    // ...
  }
}

Reference Files

This skill includes comprehensive documentation in references/:

  • android.md - Android documentation
  • core.md - Core documentation
  • getting_started.md - Getting Started documentation
  • migration.md - Migration documentation
  • testing.md - Testing documentation

Use view to read specific reference files when detailed information is needed.

Working with This Skill

For Beginners

Start with the getting_started or tutorials reference files for foundational concepts.

For Specific Features

Use the appropriate category reference file (api, guides, etc.) for detailed information.

For Code Examples

The quick reference section above contains common patterns extracted from the official docs.

Resources

references/

Organized documentation extracted from official sources. These files contain:

  • Detailed explanations
  • Code examples with language annotations
  • Links to original documentation
  • Table of contents for quick navigation

scripts/

Add helper scripts here for common automation tasks.

assets/

Add templates, boilerplate, or example projects here.

Notes

  • This skill was automatically generated from official documentation
  • Reference files preserve the structure and examples from source docs
  • Code examples include language detection for better syntax highlighting
  • Quick reference patterns are extracted from common usage examples in the docs

Updating

To refresh this skill with updated documentation:

  1. Re-run the scraper with the same configuration
  2. The skill will be rebuilt with the latest information