Hilt Dependency Injection Android: A Complete Step-by-Step Guide for 2026
Dependency injection sounds intimidating when you first hear it. Most tutorials make it worse by jumping straight into annotations and modules without explaining what problem is actually being solved.
So let’s start with the problem. Then we’ll walk through Hilt dependency injection Android step by step, exactly the way you’d set it up in a real project in 2025. No assumptions, no skipped steps.
What Is Dependency Injection and Why Does It Matter?
Imagine you’re building a ProfileViewModel. It needs a GetUserUseCase. That Use Case needs a UserRepository. That repository needs a UserDao from Room and a UserApiService from Retrofit.
Without Hilt dependency injection Android, you’d manually create all of these objects every time you need them, passing them through constructors across multiple classes. This is tedious, error-prone, and makes testing extremely painful.
Dependency injection means: instead of a class creating its own dependencies, those dependencies are provided from the outside. The class just declares what it needs, and something else handles the creation and wiring automatically. Hilt dependency injection Android is the tool that does all of that for you.
Why Hilt Specifically?
Before Hilt, many Android developers used Dagger 2. It’s powerful, but the setup is genuinely complex — especially for beginners learning architecture for the first time.
Hilt is built on top of Dagger 2 but adds Android-specific components and dramatically reduces the boilerplate. Google officially recommends Hilt dependency injection Android for all new projects. It integrates cleanly with ViewModel, WorkManager, Navigation, and the rest of the Jetpack ecosystem.
In 2025, Hilt is effectively the standard. If you’re starting a new Android project, this is where your dependency injection setup begins.
Step 1 — Add Hilt to Your Project
Open your project-level build.gradle and add the Hilt plugin:
kotlin
plugins {
id("com.google.dagger.hilt.android") version "2.51" apply false
}
Then in your app-level build.gradle, apply the plugin and add the dependencies:
kotlin
plugins {
id("com.google.dagger.hilt.android")
id("kotlin-kapt")
}
dependencies {
implementation("com.google.dagger:hilt-android:2.51")
kapt("com.google.dagger:hilt-android-compiler:2.51")
}
Check the Hilt releases page for the latest version number when you’re setting this up, as it updates regularly.
Step 2 — Set Up the Application Class
Hilt dependency injection Android needs an entry point. The Application class is that entry point. Create a class that extends Application and annotate it with @HiltAndroidApp:
kotlin
@HiltAndroidApp
class MyApp : Application()
Then register it in your AndroidManifest.xml:
xml
<application
android:name=".MyApp"
... >
This triggers Hilt’s code generation and sets up the dependency container for your entire app. Without this step, nothing else in your Hilt dependency injection Android setup will work at all.
Step 3 — Inject Into Activities and Fragments
To use Hilt dependency injection Android in an Activity or Fragment, annotate it with @AndroidEntryPoint:
kotlin
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
// Hilt can now inject into this Activity
}
For Fragments, the same annotation applies:
kotlin
@AndroidEntryPoint
class ProfileFragment : Fragment() {
// Hilt can now inject into this Fragment
}
That’s all you need. Hilt now knows these components participate in dependency injection and will manage their dependencies automatically.
Step 4 — Provide Dependencies Using Modules
Hilt dependency injection Android needs to know how to create objects it doesn’t know about by default. Retrofit and Room instances, for example, need to be configured with URLs, database names, and converter factories.
You tell Hilt how to create these through @Module classes:
kotlin
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Provides
@Singleton
fun provideUserApiService(retrofit: Retrofit): UserApiService {
return retrofit.create(UserApiService::class.java)
}
}
The @InstallIn(SingletonComponent::class) tells Hilt dependency injection Android that these dependencies should live for the entire app lifecycle — created once and reused everywhere they’re needed.
Step 5 — Inject a Repository
For classes that Hilt can construct itself — meaning all their dependencies are already known to Hilt — you use @Inject constructor:
kotlin
class UserRepositoryImpl @Inject constructor(
private val apiService: UserApiService,
private val userDao: UserDao
) : UserRepository {
override suspend fun getUser(userId: String): User {
return apiService.fetchUser(userId).toDomainModel()
}
}
Hilt dependency injection Android sees the constructor parameters, knows it has both UserApiService and UserDao already available, and constructs this class automatically whenever something requests it. No manual object creation anywhere.
Step 6 — Bind Interfaces to Implementations
This is the step that trips up most beginners with Hilt dependency injection Android. When your Domain layer defines a UserRepository interface and your Data layer provides UserRepositoryImpl, you need to tell Hilt which implementation to use when something asks for the interface.
You do this with a @Binds function inside an abstract module:
kotlin
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindUserRepository(
impl: UserRepositoryImpl
): UserRepository
}
Now whenever Hilt dependency injection Android sees a class that needs a UserRepository, it knows to provide UserRepositoryImpl as the concrete implementation. This binding is what connects your Domain layer interfaces to your Data layer implementations cleanly.
Step 7 — Inject Into ViewModels
ViewModels get special treatment in Hilt dependency injection Android — in a good way. You just add @HiltViewModel and @Inject constructor:
kotlin
@HiltViewModel
class ProfileViewModel @Inject constructor(
private val getUserUseCase: GetUserUseCase
) : ViewModel() {
private val _uiState = MutableStateFlow<ProfileUiState>(ProfileUiState.Loading)
val uiState: StateFlow<ProfileUiState> = _uiState.asStateFlow()
fun loadProfile(userId: String) {
viewModelScope.launch {
val user = getUserUseCase(userId)
_uiState.value = ProfileUiState.Success(user)
}
}
}
In your Fragment or Composable, you get the ViewModel using:
kotlin
val viewModel: ProfileViewModel = hiltViewModel()
Hilt dependency injection Android handles instantiation completely. You never manually create the ViewModel or pass its dependencies. The entire dependency chain — ViewModel → Use Case → Repository → DAO and API Service — is resolved automatically.
Step 8 — Field Injection When Constructor Injection Isn’t Possible
Sometimes you can’t use constructor injection — for example, in an Activity where Android itself creates the object. In those cases, Hilt dependency injection Android supports field injection:
kotlin
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var analytics: AnalyticsHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analytics.logScreenView("Main")
}
}
Hilt injects analytics before onCreate runs. Note that injected fields must be lateinit var and cannot be private — those are hard requirements for Hilt dependency injection Android field injection to work correctly.
Understanding Hilt Scopes
One thing worth understanding early with Hilt dependency injection Android is the scope system. Different scopes control how long a dependency lives and how widely it’s shared.
@Singleton means the dependency lives for the entire app lifetime. This is good for Retrofit, Room database instances, and repositories — things that are expensive to create and should be shared everywhere.
@ActivityScoped means the dependency lives as long as the Activity. Good for dependencies that are specific to one Activity and shouldn’t be shared across the whole app.
@ViewModelScoped means the dependency lives as long as the ViewModel. Good for dependencies that should be shared within one ViewModel but not across the entire application.
Choosing the wrong scope in Hilt dependency injection Android can cause memory leaks or unexpected behavior. When in doubt, be conservative — use the narrowest scope that meets your needs.
Testing With Hilt
One of the biggest advantages of Hilt dependency injection Android is how naturally it supports testing. In tests, you can replace real implementations with fakes using @TestInstallIn or by replacing entire modules with test-specific versions.
kotlin
@TestInstallIn(
components = [SingletonComponent::class],
replaces = [RepositoryModule::class]
)
@Module
abstract class FakeRepositoryModule {
@Binds
abstract fun bindUserRepository(
fakeImpl: FakeUserRepository
): UserRepository
}
This means you can test your ViewModel with a fake repository that returns controlled, predictable data — without needing a real network connection or a real database. Clean, fast, reliable tests that run without an emulator.
For a complete reference on Hilt dependency injection Android testing patterns, the official Hilt testing documentation covers every approach in detail.
If you’re also building your app’s architecture layers alongside this, our Android app architecture layers guide explains exactly where each Hilt-injected class belongs in the Data, Domain, and UI layers. And for teams structuring ViewModels with Use Cases, our Clean Architecture Android beginners guide shows how Hilt dependency injection Android wires the entire three-layer architecture together at runtime.
Final Conclusion
Hilt dependency injection Android takes a concept that used to require significant expertise — manual dependency management with Dagger — and makes it genuinely accessible for any Android developer who understands basic architecture patterns.
Setting it up follows a clear sequence: add dependencies, set up the Application class, annotate your Android components, provide dependencies through modules, bind interfaces to implementations, and let Hilt handle the rest. Once configured, your codebase becomes significantly cleaner — no manual object creation, no long dependency-passing chains, just clear declarations of what each class needs.
In 2025, Hilt dependency injection Android is not optional for serious Android development. It’s as fundamental as Jetpack Compose or Coroutines. Learn it once, understand the setup sequence, and you’ll use it confidently in every project going forward.



Post Comment