# 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:

```kotlin
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`:

```kotlin
@HiltAndroidApp
class MyApplication : Application()
```

Update your `AndroidManifest.xml` to use the custom Application class:

```xml
<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:

```kotlin
@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:

```kotlin
@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:

```kotlin
@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

1. **Scope Management**: Use appropriate scope annotations:
    
    * `@Singleton` for application-wide instances
        
    * `@ActivityScoped` for activity-level instances
        
    * `@ViewModelScoped` for ViewModel-level instances
        
2. **Testing**: Hilt makes testing easier by allowing you to swap implementations:
    

```kotlin
@Module
@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [NetworkModule::class]
)
object TestNetworkModule {
    @Provides
    @Singleton
    fun provideTestApiService(): ApiService {
        return FakeApiService()
    }
}
```

3. **Interface Abstraction**: Always depend on interfaces rather than concrete implementations:
    

```kotlin
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

1. **Circular Dependencies**: Avoid circular dependencies between classes. Use `@Lazy` if necessary.
    
2. **Scope Mismatch**: Ensure that dependencies have compatible scopes. A `@Singleton` component cannot depend on a `@ActivityScoped` component.
    
3. **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.
