Hilt dependency injection for Android
Hilt is Google's recommended DI framework for non-trivial Android apps. Apps SHOULD use Hilt with the KSP annotation processor and prefer constructor injection. @Singleton SHOULD be reserved for genuinely app-scoped state — over-scoping is the most common Hilt mistake.
Setup
- Use KSP (
com.google.devtools.ksp) for the Hilt compiler, not the legacy KAPT. KAPT is in maintenance and slower; KSP is the durable choice and supports KSP2 on Kotlin 2.x. - Apply the
com.google.dagger.hilt.androidGradle plugin and addhilt-androidplus the compiler viaksp(...). - Annotate the
Applicationsubclass with@HiltAndroidApp. This is the root of the dependency graph and MUST exist exactly once. - Annotate Android entry points (
Activity,Fragment,Service) needing field injection with@AndroidEntryPoint.
Prefer constructor injection
- Classes you own SHOULD declare dependencies via an
@Inject constructor. Hilt then provides them without a module. - Use a
@Modulewith@Providesonly for types you do not own (third-party, builders) or interfaces. Bind an interface to its implementation with@Bindsin anabstractmodule — it generates less code than@Provides. - Install every module into a component with
@InstallIn(...). Choose the narrowest component that fits the binding's lifetime.
Scope to the right component
| Scope | Component | Lifetime | Use for |
|---|---|---|---|
@Singleton |
SingletonComponent |
Application | App-wide singletons (DB, network client, app-scoped state) |
@ActivityRetainedScoped |
ActivityRetainedComponent |
Survives config change | Shared state across recreation |
@ViewModelScoped |
ViewModelComponent |
One ViewModel | Per-ViewModel collaborators |
@ActivityScoped |
ActivityComponent |
One Activity | Activity-tied dependencies |
@FragmentScoped |
FragmentComponent |
One Fragment | Fragment-tied dependencies |
- An unscoped binding MUST be assumed to create a new instance per injection point — that is the correct default for stateless collaborators.
- A longer-lived component MUST NOT depend on a shorter-lived one (e.g. a
@Singletonholding anActivity). This is a captive-dependency leak. @SingletonSHOULD NOT be applied for performance "just in case." Scope only stateful, expensive-to-build, or app-shared instances.
Jetpack integration
- Annotate ViewModels with
@HiltViewModeland an@Inject constructor. Hilt supplies the factory; injectSavedStateHandlefor navigation/saved args. Do not construct these ViewModels manually. - For Compose, retrieve them with
hiltViewModel()(androidx.hilt:hilt-navigation-compose). - Annotate
CoroutineWorker/Workersubclasses with@HiltWorker. Runtime params (Context,WorkerParameters) MUST use@AssistedInject+@Assisted; other dependencies inject normally. InjectHiltWorkerFactoryinto theApplicationand wire it through a customWorkManagerConfiguration(or theConfiguration.Provideron theApplication).
Testing
- Use
hilt-android-testingwithkspTest/kspAndroidTest. Annotate tests with@HiltAndroidTestand drive injection withHiltAndroidRule. - Replace bindings in tests with
@TestInstallInor@BindValuerather than mutating production modules.
Anti-patterns
- Do not inject the Hilt-managed graph by passing
Contextaround to build objects manually — that defeats the framework. - Do not field-inject where constructor injection is possible; reserve field injection for framework-instantiated entry points.
- Do not create god modules. Split modules by feature and install them into the narrowest applicable component.