Dependency Injection (Hilt) in Android App
Hilt—Google's official DI library for Android, built on top of Dagger 2. Removes 80% of Dagger boilerplate, providing ready-made components for standard Android classes. For most new projects—reasonable default choice.
Basic Setup
// build.gradle.kts (project)
plugins {
id("com.google.dagger.hilt.android") version "2.51" apply false
}
// build.gradle.kts (app)
plugins {
id("com.google.dagger.hilt.android")
id("com.google.devtools.ksp")
}
dependencies {
implementation("com.google.dagger:hilt-android:2.51")
ksp("com.google.dagger:hilt-android-compiler:2.51")
}
@HiltAndroidApp on Application—entry point, without it Hilt doesn't initialize:
@HiltAndroidApp
class App : Application()
Injection in Android Classes
Hilt knows about Activity, Fragment, Service, BroadcastReceiver, ViewModel, and WorkManager. Each has its own scope:
@AndroidEntryPoint
class ProfileFragment : Fragment() {
@Inject lateinit var userRepository: UserRepository
private val viewModel: ProfileViewModel by viewModels()
}
@HiltViewModel
class ProfileViewModel @Inject constructor(
private val userRepository: UserRepository,
private val analyticsService: AnalyticsService
) : ViewModel()
@AndroidEntryPoint generates Dagger subcomponent for this class. @HiltViewModel integrates ViewModel with Hilt without manual ViewModelFactory.
Modules and Bindings
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY
else HttpLoggingInterceptor.Level.NONE
})
.build()
@Provides
@Singleton
fun provideApiService(client: OkHttpClient): ApiService =
Retrofit.Builder()
.baseUrl(BuildConfig.API_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds
abstract fun bindUserRepository(impl: UserRepositoryImpl): UserRepository
}
@InstallIn(SingletonComponent::class) binds module to Hilt's AppComponent. For Activity-level dependencies—ActivityComponent::class, for Fragment—FragmentComponent::class.
Qualifiers and Multiple Bindings
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthenticatedClient
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class UnauthenticatedClient
@Module
@InstallIn(SingletonComponent::class)
object HttpModule {
@Provides @Singleton @AuthenticatedClient
fun provideAuthenticatedClient(authInterceptor: AuthInterceptor): OkHttpClient =
OkHttpClient.Builder().addInterceptor(authInterceptor).build()
@Provides @Singleton @UnauthenticatedClient
fun provideUnauthenticatedClient(): OkHttpClient = OkHttpClient.Builder().build()
}
Without qualifiers Hilt can't distinguish two OkHttpClient—compilation error [Dagger/DuplicateBindings].
Testing with Hilt
@HiltAndroidTest
@RunWith(AndroidJUnit4::class)
class ProfileFragmentTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@BindValue
@JvmField
val fakeRepository: UserRepository = FakeUserRepository()
@Test
fun displaysUserName() {
// test
}
}
@BindValue replaces real binding with fake directly in test, without creating test module. For unit tests of ViewModels, Hilt is not needed—dependencies passed to constructor directly.
Common Mistake: @Inject in non-Android Classes Without @AndroidEntryPoint
@Inject in Fragment without @AndroidEntryPoint gives NullPointerException when accessing injected field—field remains null. Hilt doesn't inject into classes without its annotation. Typical crash when copying Fragment from old code to new project.
Setting up Hilt from scratch in new project—1–2 days. Migration from manual DI or Dagger 2—3–7 days depending on project size. Cost is calculated individually.







