Dependency Injection with Hilt/Dagger
📖 Concept
Dependency Injection (DI) is mandatory knowledge for senior Android developers. Hilt (built on Dagger) is Google's recommended DI framework for Android.
Why DI matters:
- Testability — Replace real implementations with fakes/mocks in tests
- Decoupling — Classes depend on interfaces, not concrete implementations
- Lifecycle management — Hilt scopes instances to Android lifecycle (Activity, ViewModel, etc.)
- Code reuse — Same interface, different implementations per build variant
Hilt component hierarchy:
SingletonComponent (Application scope)
├── ActivityRetainedComponent (survives config change)
│ ├── ViewModelComponent (ViewModel scope)
│ └── ActivityComponent (Activity scope)
│ ├── FragmentComponent
│ └── ViewComponent
└── ServiceComponent (Service scope)
Key Hilt annotations:
@HiltAndroidApp— Application class, generates the root component@AndroidEntryPoint— Activity/Fragment/Service, enables injection@HiltViewModel— ViewModel, enables constructor injection@Inject— Marks a constructor or field for injection@Module/@Provides— Provides instances that can't be constructor-injected@Binds— Binds an interface to its implementation (more efficient than @Provides)@Singleton/@ActivityScoped— Scoping annotations
Under the hood:
Dagger generates code at compile time (no reflection). For each @Inject constructor, Dagger creates a Factory class. For each @Module, it creates a provider. The component wires everything together.
💻 Code Example
1// Application setup2@HiltAndroidApp3class MyApplication : Application()45// Module — providing dependencies6@Module7@InstallIn(SingletonComponent::class)8abstract class DataModule {9 @Binds10 @Singleton11 abstract fun bindTaskRepository(impl: TaskRepositoryImpl): TaskRepository12}1314@Module15@InstallIn(SingletonComponent::class)16object NetworkModule {17 @Provides18 @Singleton19 fun provideOkHttpClient(): OkHttpClient {20 return OkHttpClient.Builder()21 .addInterceptor(AuthInterceptor())22 .addInterceptor(HttpLoggingInterceptor().apply {23 level = HttpLoggingInterceptor.Level.BODY24 })25 .connectTimeout(30, TimeUnit.SECONDS)26 .build()27 }2829 @Provides30 @Singleton31 fun provideRetrofit(client: OkHttpClient): Retrofit {32 return Retrofit.Builder()33 .baseUrl("https://api.example.com/")34 .client(client)35 .addConverterFactory(MoshiConverterFactory.create())36 .build()37 }3839 @Provides40 @Singleton41 fun provideTaskApi(retrofit: Retrofit): TaskApi {42 return retrofit.create(TaskApi::class.java)43 }44}4546// Repository with constructor injection47class TaskRepositoryImpl @Inject constructor(48 private val taskApi: TaskApi,49 private val taskDao: TaskDao,50 @IoDispatcher private val ioDispatcher: CoroutineDispatcher51) : TaskRepository {52 override fun getTasks(): Flow<List<Task>> = taskDao.getAll()53}5455// ViewModel with Hilt56@HiltViewModel57class TaskViewModel @Inject constructor(58 private val getTasksUseCase: GetTasksUseCase,59 private val savedStateHandle: SavedStateHandle60) : ViewModel() {61 // savedStateHandle is provided automatically by Hilt62 private val filter = savedStateHandle.getStateFlow("filter", TaskFilter.ALL)63}6465// Qualifier for multiple bindings of same type66@Qualifier67@Retention(AnnotationRetention.BINARY)68annotation class IoDispatcher6970@Qualifier71@Retention(AnnotationRetention.BINARY)72annotation class MainDispatcher7374@Module75@InstallIn(SingletonComponent::class)76object DispatcherModule {77 @Provides @IoDispatcher78 fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO7980 @Provides @MainDispatcher81 fun provideMainDispatcher(): CoroutineDispatcher = Dispatchers.Main82}
🏋️ Practice Exercise
Practice:
- Set up Hilt in a new project with proper Application, Activity, and ViewModel injection
- Create a @Module with both @Provides and @Binds — when do you use each?
- Implement a Qualifier for test vs production API URLs
- Explain the Hilt component hierarchy and scoping rules
- Write a unit test that replaces a Hilt module with a test module using @TestInstallIn
⚠️ Common Mistakes
Using @Provides when @Binds suffices — @Binds is more efficient (no method body, directly maps interface to impl)
Not scoping correctly — @Singleton in a ViewModelComponent module means the instance outlives the ViewModel
Injecting Activity context where Application context is needed — causes memory leaks
Circular dependencies — A depends on B, B depends on A. Use @Lazy or restructure
Forgetting @AndroidEntryPoint on Activities/Fragments — injection silently fails
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Dependency Injection with Hilt/Dagger. Login to unlock this feature.