Skip to main content

Using Dependency Injection with Hilt

1. Setting Up Hilt Dependencies

Dependency Version Description
com.google.dagger:hilt-android 2.51.1 Main Hilt library for Android
com.google.dagger:hilt-android-compiler 2.51.1 Annotation processor for Hilt
androidx.hilt:hilt-navigation-compose 1.2.0 Hilt integration with Jetpack Compose Navigation

Example: Gradle Setup

plugins {
    id 'com.google.dagger.hilt.android'
}

dependencies {
    implementation 'com.google.dagger:hilt-android:2.51.1'
    annotationProcessor 'com.google.dagger:hilt-android-compiler:2.51.1'
}
Note: Ensure android { ... } block in your app-level build.gradle includes apply plugin: 'com.google.dagger.hilt.android'

2. Annotating Application Class

When using Hilt in an Android application, the Application class must be annotated with @HiltAndroidApp to enable Hilt's dependency injection capabilities.

Annotation Description Usage
@HiltAndroidApp Marks the Application class to enable Hilt's dependency injection. Apply to the Application class or a subclass of it.

Example: Annotating Application Class

@HiltAndroidApp
class MyApplication : Application() {
    // Hilt will now be available for dependency injection
}
Note: The Application class must be declared in the AndroidManifest.xml file to be recognized by the Android system.

3. Injecting into Activities and Fragments

Component Injection Point Annotations Notes
Activity onCreate() @AndroidEntryPoint Required for Hilt to inject dependencies into Activities
Fragment onCreate() @AndroidEntryPoint Required for Hilt to inject dependencies into Fragments

Example: Injecting a Repository into an Activity

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var userRepository: UserRepository

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // userRepository is now injected
        val user = userRepository.getUser(1)
    }
}

Example: Injecting a ViewModel into a Fragment

@AndroidEntryPoint
class UserFragment : Fragment() {
    @Inject
    lateinit var userViewModel: UserViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // userViewModel is now injected
    }
}
Note: Ensure that the @AndroidEntryPoint annotation is applied to all Activities and Fragments where dependencies are injected.

4. Creating Modules

Example: Basic Hilt Module

@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
    @Provides
    @Singleton
    fun provideDatabase(context: Context): AppDatabase {
        return Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            "app_database"
        ).build()
    }
}
Annotation Description Usage
@Module Marks a class as a Hilt module Must be applied to classes providing dependencies
@InstallIn Specifies where the module is installed Accepts component types like SingletonComponent
@Provides Denotes a method that provides a dependency Used inside modules to define dependencies
Note: Hilt modules must be annotated with @Module and installed in a specific component using @InstallIn.

Example: Providing Different Scopes

@Module
@InstallIn(ActivityComponent::class)
object ActivityModule {
    @Provides
    @ActivityScoped
    fun provideViewModel(): MyViewModel {
        return MyViewModel()
    }
}

Module Creation Guidelines

  • Use @Module to mark classes that provide dependencies
  • Install modules using @InstallIn with appropriate component types
  • Use @Provides to define methods that return dependencies
  • Apply scope annotations like @Singleton or @ActivityScoped for proper lifecycle management

5. Providing Dependencies

Dependency Type Annotation Description Example
Singleton @Singleton Provides a single instance for the application lifecycle @Singleton<br>class NetworkClient @Inject constructor() { }
Per-Activity @ActivityScoped Provides a single instance per Activity lifecycle @ActivityScoped<br>class ViewModel @Inject constructor() { }
Per-View @ViewScoped Provides a single instance per View lifecycle @ViewScoped<br>class FragmentViewModel @Inject constructor() { }
Qualifier @Named Allows multiple implementations of the same type @Named("production")<br>class ApiClient @Inject constructor() { }
Custom Scope @MyCustomScope Defines a custom scope for dependency injection @Scope<br>@Retention(AnnotationRetention.RUNTIME)<br>annotation class MyCustomScope

Example: Providing a Singleton Dependency

@Singleton<br>class DatabaseManager @Inject constructor() {<br>    fun saveData(data: String) { /* ... */ }<br>}

Example: Using Qualifiers for Multiple Implementations

@Named("production")<br>class ProductionApiClient @Inject constructor() : ApiClient {<br>    override fun fetch(): String = "Production Data"<br>}<br><br>@Named("staging")<br>class StagingApiClient @Inject constructor() : ApiClient {<br>    override fun fetch(): String = "Staging Data"<br>}
Note: Hilt automatically manages the lifecycle of dependencies based on their scope annotations.

6. Using Constructor Injection

Component Description Usage
@Inject Annotation to mark constructor parameters for injection @Inject constructor(val repository: UserRepository)
Hilt ViewModel Inject dependencies into ViewModels @HiltViewModel<br>class UserViewModel @Inject constructor(<br>  private val repository: UserRepository<br>) : ViewModel()
Activity/Fragment Inject dependencies into Activities or Fragments @AndroidEntryPoint<br>class MainActivity : AppCompatActivity() {<br>  @Inject<br>  lateinit var userManager: UserManager<br>}

Example: Constructor Injection in a Repository

@Repository<br>class UserRepository @Inject constructor(<br>&nbsp;&nbsp;private val apiService: ApiService,<br>&nbsp;&nbsp;private val database: AppDatabase<br>) {<br>&nbsp;&nbsp;fun fetchUser(id: String) = apiService.fetchUser(id)<br>}

Example: Constructor Injection in a ViewModel

@HiltViewModel<br>class UserViewModel @Inject constructor(<br>&nbsp;&nbsp;private val repository: UserRepository,<br>&nbsp;&nbsp;private val dispatcher: CoroutineDispatcher = Dispatchers.IO<br>) : ViewModel() {<br>&nbsp;&nbsp;fun loadUser(id: String) = viewModelScope.launch(dispatcher) {<br>&nbsp;&nbsp;&nbsp;&nbsp;val user = repository.fetchUser(id)<br>&nbsp;&nbsp;&nbsp;&nbsp;// Update UI<br>&nbsp;&nbsp;}<br>}

7. Defining Scopes

Scope Description Usage
@Singleton Defines a scope that ensures only one instance of the class is created throughout the application's lifecycle. @Singleton is applied to a class or module to ensure it's a singleton across the app.
@ActivityScoped Ensures that a dependency is created once per Activity instance. Used with @ActivityScoped to scope dependencies to the lifecycle of an Activity.
@FragmentScoped Ensures that a dependency is created once per Fragment instance. Applied to dependencies to scope them to the lifecycle of a Fragment.
@ViewModelScoped Ensures that a dependency is created once per ViewModel instance. Used to scope dependencies to the lifecycle of a ViewModel in Android.

Example: Using @Singleton Scope

@Singleton
class DatabaseManager @Inject constructor(
    private val database: RoomDatabase
) {
    fun saveData(data: String) {
        // Save data logic here
    }
}

Example: Using @ActivityScoped

@ActivityScoped
class NetworkManager @Inject constructor(
    private val apiService: ApiService
) {
    fun fetchData(): Flow<Data> {
        return apiService.fetchData()
    }
}

8. Injecting ViewModel

Component Description Usage
@HiltViewModel Annotation to mark a ViewModel for Hilt injection @HiltViewModel
HiltViewModel Base class for Hilt-managed ViewModels Extend HiltViewModel

Example: Injecting ViewModel with Hilt

import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.ViewModel

@HiltViewModel
class MyViewModel @ViewModelInject constructor(
    private val repository: MyRepository
) : HiltViewModel() {
    // ViewModel logic here
}
Note: The @ViewModelInject constructor is used to inject dependencies into the ViewModel.

9. Using Qualifiers

In Hilt, qualifiers are used to distinguish between multiple implementations of the same type. This is particularly useful when you have more than one binding for a specific type, such as multiple implementations of a network interface or different database instances.

Qualifier Description Usage Example
@Named Simple string-based qualifier @Named("production")
@Qualifier Custom annotation for more complex qualifiers @Qualifier with @Retention

Example: Using @Named Qualifier

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    @Provides
    @Named("production")
    fun provideProductionApi(): ApiInterface = ProductionApi()

    @Provides
    @Named("staging")
    fun provideStagingApi(): ApiInterface = StagingApi()
}

@Singleton
class Repository @Inject constructor(
    @Named("production") private val api: ApiInterface
)

Example: Using Custom @Qualifier

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class Production

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    @Provides
    @Production
    fun provideProductionApi(): ApiInterface = ProductionApi()
}

@Singleton
class Repository @Inject constructor(
    @Production private val api: ApiInterface
)

10. Testing with Hilt

Example: Providing Mock Dependencies in Tests

@HiltAndroidTest
class UserRepositoryTest {

    @Inject
    @Mock
    lateinit var mockUserDao: UserDao

    @Inject
    @Mock
    lateinit var mockNetworkManager: NetworkManager

    @Test
    fun `test fetch user data`() {
        // Given
        val expectedUser = User("1", "John Doe")
        whenever(mockUserDao.fetchUserById("1")).thenReturn(expectedUser)

        // When
        val result = userRepository.fetchUser("1")

        // Then
        assertEquals(expectedUser, result)
    }
}
Feature Description Notes
@HiltAndroidTest Annotation to enable Hilt in Android tests Must be applied to test classes
@Mock Creates a mock instance for injection Used with Dagger's @Mock annotation
@Inject Marker for Hilt to inject dependencies Can be applied to fields or constructors
Note: Hilt simplifies testing by allowing you to inject mock dependencies directly into your test classes.

Testing with Hilt Summary

  • Use @HiltAndroidTest to enable Hilt in Android tests
  • Use @Mock to provide mock implementations for dependencies
  • Inject mocks using @Inject in test classes
  • Hilt supports both unit and integration testing

11. Implementing Entry Points

Component Description Usage
@EntryPoint Marker annotation to define entry points in Hilt modules Used to access dependencies from outside of Hilt's scope
@InstallIn Defines the scope in which a component is installed Used with @EntryPoint to specify where the entry point is available

Example: Defining an Entry Point

@Module
@InstallIn(ActivityComponent::class)
@EntryPoint
interface MyEntryPoint {
    fun provideSomeService(): SomeService
}

Example: Using Entry Points in an Activity

class MyActivity : AppCompatActivity() {
    private val entryPoint by lazy {
        EntryPointAccessors.fromActivity(this, MyEntryPoint::class.java)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val service = entryPoint.provideSomeService()
        // Use the service
    }
}
Note: Entry points are useful for accessing dependencies in components that are not managed by Hilt, such as Activities or Fragments.

Was this article helpful?