Integrating Hilt Dependency Injection in Android Compose Applications

Introduction
Hilt is the recommended dependency injection library for Android, built on top of Dagger to simplify DI implementation. This guide will walk you through integrating Hilt in your Android Compose application, using practical examples from a fitness tracking app.
Setup
1. Add Dependencies
First, add the required dependencies in your project's build.gradle.kts
file:
plugins {
id("com.google.dagger.hilt.android")
id("kotlin-kapt")
}
dependencies {
implementation("com.google.dagger:hilt-android:2.48")
kapt("com.google.dagger:hilt-compiler:2.48")
implementation("androidx.hilt:hilt-navigation-compose:1.1.0")
}
2. Create Application Class
Create a custom Application class and annotate it with @HiltAndroidApp
:
@HiltAndroidApp
class MyApplication : Application()
Update your AndroidManifest.xml
to use the custom Application class:
<application
android:name=".MyApplication"
... >
Dependency Injection Implementation
1. Creating Modules
Modules are classes that tell Hilt how to provide instances of different types. Here's an example of a network module:
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideApiService(): ApiService {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
@Provides
@Singleton
fun provideRepository(apiService: ApiService, app: Application): MyRepository {
return MyRepositoryImpl(apiService, app)
}
}
2. Implementing ViewModels
To use Hilt in ViewModels, annotate them with @HiltViewModel
and inject dependencies:
@HiltViewModel
class HomeViewModel @Inject constructor(
private val repository: MyRepository
) : ViewModel() {
private val _uiState = MutableStateFlow(HomeUiState())
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()
init {
loadData()
}
private fun loadData() {
viewModelScope.launch {
try {
val data = repository.getData()
_uiState.value = _uiState.value.copy(
data = data,
isLoading = false
)
} catch (e: Exception) {
_uiState.value = _uiState.value.copy(
error = e.message,
isLoading = false
)
}
}
}
}
3. Using in Composables
In your Composable functions, use hiltViewModel()
to get an instance of your ViewModel:
@Composable
fun HomeScreen(
viewModel: HomeViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsState()
when {
uiState.isLoading -> LoadingIndicator()
uiState.error != null -> ErrorMessage(uiState.error)
else -> Content(uiState.data)
}
}
Best Practices
Scope Management: Use appropriate scope annotations:
@Singleton
for application-wide instances@ActivityScoped
for activity-level instances@ViewModelScoped
for ViewModel-level instances
Testing: Hilt makes testing easier by allowing you to swap implementations:
@Module
@TestInstallIn(
components = [SingletonComponent::class],
replaces = [NetworkModule::class]
)
object TestNetworkModule {
@Provides
@Singleton
fun provideTestApiService(): ApiService {
return FakeApiService()
}
}
- Interface Abstraction: Always depend on interfaces rather than concrete implementations:
interface MyRepository {
suspend fun getData(): List<Data>
}
class MyRepositoryImpl @Inject constructor(
private val apiService: ApiService
) : MyRepository {
override suspend fun getData(): List<Data> {
return apiService.getData()
}
}
Common Pitfalls
Circular Dependencies: Avoid circular dependencies between classes. Use
@Lazy
if necessary.Scope Mismatch: Ensure that dependencies have compatible scopes. A
@Singleton
component cannot depend on a@ActivityScoped
component.Missing Bindings: Always provide all necessary dependencies in your modules.
Conclusion
Hilt simplifies dependency injection in Android applications by reducing the boilerplate code and providing a standard way to implement DI. When used with Jetpack Compose, it creates a clean and maintainable architecture that's easy to test and scale.
Remember to:
Keep modules focused and organized
Use appropriate scopes
Follow interface-based design
Write tests using Hilt's testing utilities
By following these guidelines, you'll have a robust dependency injection setup in your Android Compose application.
Subscribe to my newsletter
Read articles from Angel Saikia directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by