Dependency Injection with Hilt

Advanced April 2025 Mohsen Mashkour
Dependency Injection with Hilt

Introduction

Dependency Injection (DI) is a design pattern that implements Inversion of Control (IoC) for managing dependencies between classes. It helps you write more modular, testable, and maintainable code by decoupling components from their dependencies. Hilt is Google's recommended dependency injection library for Android, built on top of Dagger and specifically designed for Android applications.

This comprehensive guide will take you from basic dependency injection concepts to advanced Hilt features like custom scopes, qualifiers, and multi-module architecture. Whether you're building a simple app or a complex enterprise application, understanding and implementing proper dependency injection will significantly improve your code quality and development experience.

Why Use Dependency Injection?

Before diving into Hilt implementation, let's understand the benefits of dependency injection:

  • Loose Coupling: Classes don't create their own dependencies
  • Testability: Easy to mock dependencies for unit testing
  • Reusability: Dependencies can be shared across multiple classes
  • Maintainability: Changes to dependencies don't require changes to dependent classes
  • Lifecycle Management: Automatic management of object lifecycles
  • Configuration: Centralized configuration of dependencies
  • Performance: Efficient object creation and reuse
  • Scalability: Easy to add new dependencies and features

Setting Up Hilt

Let's start by setting up Hilt in your Android project.

Dependencies
// Project-level build.gradle.kts
buildscript {
    dependencies {
        classpath("com.google.dagger:hilt-android-gradle-plugin:2.48")
    }
}

// App-level build.gradle.kts
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("kotlin-kapt")
    id("dagger.hilt.android.plugin")
}

dependencies {
    implementation("com.google.dagger:hilt-android:2.48")
    kapt("com.google.dagger:hilt-android-compiler:2.48")
    
    // For ViewModel injection
    implementation("androidx.hilt:hilt-navigation-compose:1.1.0")
    
    // For testing
    testImplementation("com.google.dagger:hilt-android-testing:2.48")
    kaptTest("com.google.dagger:hilt-android-compiler:2.48")
    androidTestImplementation("com.google.dagger:hilt-android-testing:2.48")
    kaptAndroidTest("com.google.dagger:hilt-android-compiler:2.48")
}
Application Class
@HiltAndroidApp
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // Application initialization
    }
}
AndroidManifest.xml

    
    
        
        
        
    

Basic Dependency Injection

Let's start with basic dependency injection concepts and implementations.

Simple Dependencies
// Data class for user
data class User(
    val id: Int,
    val name: String,
    val email: String
)

// Service interface
interface UserService {
    suspend fun getUsers(): List
    suspend fun getUserById(id: Int): User?
}

// Service implementation
class UserServiceImpl : UserService {
    override suspend fun getUsers(): List {
        // Implementation
        return listOf(
            User(1, "John Doe", "john@example.com"),
            User(2, "Jane Smith", "jane@example.com")
        )
    }
    
    override suspend fun getUserById(id: Int): User? {
        // Implementation
        return User(id, "User $id", "user$id@example.com")
    }
}

// Hilt module to provide dependencies
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    
    @Provides
    @Singleton
    fun provideUserService(): UserService {
        return UserServiceImpl()
    }
    
    @Provides
    @Singleton
    fun provideDatabase(): AppDatabase {
        return Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            "app_database"
        ).build()
    }
    
    @Provides
    @Singleton
    fun provideUserDao(database: AppDatabase): UserDao {
        return database.userDao()
    }
}
Activity Injection
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    
    @Inject
    lateinit var userService: UserService
    
    @Inject
    lateinit var userDao: UserDao
    
    private val viewModel: MainViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        // Dependencies are automatically injected
        loadUsers()
    }
    
    private fun loadUsers() {
        lifecycleScope.launch {
            val users = userService.getUsers()
            // Update UI with users
        }
    }
}

// ViewModel with Hilt
@HiltViewModel
class MainViewModel @Inject constructor(
    private val userService: UserService,
    private val userDao: UserDao
) : ViewModel() {
    
    private val _users = MutableStateFlow>(emptyList())
    val users: StateFlow> = _users.asStateFlow()
    
    init {
        loadUsers()
    }
    
    private fun loadUsers() {
        viewModelScope.launch {
            try {
                val users = userService.getUsers()
                _users.value = users
            } catch (e: Exception) {
                // Handle error
            }
        }
    }
}

Network Dependencies

Setting up network dependencies with Retrofit and OkHttp.

Network Module
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    
    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .addInterceptor(HttpLoggingInterceptor().apply {
                level = if (BuildConfig.DEBUG) {
                    HttpLoggingInterceptor.Level.BODY
                } else {
                    HttpLoggingInterceptor.Level.NONE
                }
            })
            .addInterceptor(AuthInterceptor())
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            .build()
    }
    
    @Provides
    @Singleton
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
    
    @Provides
    @Singleton
    fun provideApiService(retrofit: Retrofit): ApiService {
        return retrofit.create(ApiService::class.java)
    }
    
    @Provides
    @Singleton
    fun provideUserRepository(
        apiService: ApiService,
        userDao: UserDao
    ): UserRepository {
        return UserRepositoryImpl(apiService, userDao)
    }
}

// API Service interface
interface ApiService {
    @GET("users")
    suspend fun getUsers(): List
    
    @GET("users/{id}")
    suspend fun getUserById(@Path("id") id: Int): User
    
    @POST("users")
    suspend fun createUser(@Body user: CreateUserRequest): User
}

// Repository interface
interface UserRepository {
    suspend fun getUsers(): List
    suspend fun getUserById(id: Int): User?
    suspend fun createUser(user: CreateUserRequest): User
}

// Repository implementation
class UserRepositoryImpl @Inject constructor(
    private val apiService: ApiService,
    private val userDao: UserDao
) : UserRepository {
    
    override suspend fun getUsers(): List {
        return try {
            val users = apiService.getUsers()
            userDao.insertUsers(users)
            users
        } catch (e: Exception) {
            userDao.getAllUsers()
        }
    }
    
    override suspend fun getUserById(id: Int): User? {
        return try {
            val user = apiService.getUserById(id)
            userDao.insertUser(user)
            user
        } catch (e: Exception) {
            userDao.getUserById(id)
        }
    }
    
    override suspend fun createUser(user: CreateUserRequest): User {
        val createdUser = apiService.createUser(user)
        userDao.insertUser(createdUser)
        return createdUser
    }
}

Scopes and Lifecycles

Understanding Hilt scopes and how they manage object lifecycles.

Hilt Scopes
@Module
@InstallIn(SingletonComponent::class)
object SingletonModule {
    
    @Provides
    @Singleton // Lives for the entire application lifecycle
    fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences {
        return context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
    }
    
    @Provides
    @Singleton
    fun provideTokenManager(sharedPreferences: SharedPreferences): TokenManager {
        return TokenManagerImpl(sharedPreferences)
    }
}

@Module
@InstallIn(ActivityComponent::class)
object ActivityModule {
    
    @Provides
    @ActivityScoped // Lives for the activity lifecycle
    fun provideActivityScopedService(): ActivityScopedService {
        return ActivityScopedServiceImpl()
    }
}

@Module
@InstallIn(FragmentComponent::class)
object FragmentModule {
    
    @Provides
    @FragmentScoped // Lives for the fragment lifecycle
    fun provideFragmentScopedService(): FragmentScopedService {
        return FragmentScopedServiceImpl()
    }
}

@Module
@InstallIn(ViewComponent::class)
object ViewModule {
    
    @Provides
    @ViewScoped // Lives for the view lifecycle
    fun provideViewScopedService(): ViewScopedService {
        return ViewScopedServiceImpl()
    }
}
Custom Scopes
@Scope
@Retention(RetentionPolicy.BINARY)
annotation class UserScope

@Module
@InstallIn(SingletonComponent::class)
object UserModule {
    
    @Provides
    @UserScope
    fun provideUserManager(
        tokenManager: TokenManager,
        apiService: ApiService
    ): UserManager {
        return UserManagerImpl(tokenManager, apiService)
    }
    
    @Provides
    @UserScope
    fun provideUserPreferences(
        @ApplicationContext context: Context,
        userId: String
    ): UserPreferences {
        return context.getSharedPreferences("user_$userId", Context.MODE_PRIVATE)
    }
}

Qualifiers and Named Dependencies

Using qualifiers to provide different implementations of the same type.

Qualifiers
@Qualifier
@Retention(RetentionPolicy.BINARY)
annotation class ApiUrl

@Qualifier
@Retention(RetentionPolicy.BINARY)
annotation class CacheUrl

@Module
@InstallIn(SingletonComponent::class)
object UrlModule {
    
    @Provides
    @ApiUrl
    fun provideApiUrl(): String {
        return "https://api.example.com/"
    }
    
    @Provides
    @CacheUrl
    fun provideCacheUrl(): String {
        return "https://cache.example.com/"
    }
}

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    
    @Provides
    @Singleton
    fun provideApiRetrofit(
        @ApiUrl baseUrl: String,
        okHttpClient: OkHttpClient
    ): Retrofit {
        return Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
    
    @Provides
    @Singleton
    fun provideCacheRetrofit(
        @CacheUrl baseUrl: String,
        okHttpClient: OkHttpClient
    ): Retrofit {
        return Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
}
Named Dependencies
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
    
    @Provides
    @Named("main_database")
    fun provideMainDatabase(@ApplicationContext context: Context): AppDatabase {
        return Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            "main_database"
        ).build()
    }
    
    @Provides
    @Named("cache_database")
    fun provideCacheDatabase(@ApplicationContext context: Context): AppDatabase {
        return Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            "cache_database"
        ).build()
    }
    
    @Provides
    @Named("main_user_dao")
    fun provideMainUserDao(@Named("main_database") database: AppDatabase): UserDao {
        return database.userDao()
    }
    
    @Provides
    @Named("cache_user_dao")
    fun provideCacheUserDao(@Named("cache_database") database: AppDatabase): UserDao {
        return database.userDao()
    }
}

// Using named dependencies
@HiltViewModel
class MainViewModel @Inject constructor(
    @Named("main_user_dao") private val userDao: UserDao,
    @Named("cache_user_dao") private val cacheDao: UserDao
) : ViewModel() {
    // Implementation
}

Context Injection

Injecting Android Context and other Android-specific dependencies.

Context Types
@Module
@InstallIn(SingletonComponent::class)
object ContextModule {
    
    @Provides
    @Singleton
    fun provideApplicationContext(@ApplicationContext context: Context): Context {
        return context
    }
    
    @Provides
    @Singleton
    fun provideResources(@ApplicationContext context: Context): Resources {
        return context.resources
    }
    
    @Provides
    @Singleton
    fun providePackageManager(@ApplicationContext context: Context): PackageManager {
        return context.packageManager
    }
    
    @Provides
    @Singleton
    fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences {
        return context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
    }
    
    @Provides
    @Singleton
    fun provideFileDir(@ApplicationContext context: Context): File {
        return context.filesDir
    }
    
    @Provides
    @Singleton
    fun provideCacheDir(@ApplicationContext context: Context): File {
        return context.cacheDir
    }
}

// Activity context injection
@Module
@InstallIn(ActivityComponent::class)
object ActivityContextModule {
    
    @Provides
    fun provideActivityContext(@ActivityContext context: Context): Context {
        return context
    }
    
    @Provides
    fun provideActivityResources(@ActivityContext context: Context): Resources {
        return context.resources
    }
}

Multi-Module Architecture

Organizing Hilt modules in a multi-module Android project.

Module Structure
app/
├── build.gradle.kts
├── src/main/java/com/example/app/
│   ├── MainActivity.kt
│   ├── MyApplication.kt
│   └── di/
│       └── AppModule.kt
│
data/
├── build.gradle.kts
├── src/main/java/com/example/data/
│   ├── repository/
│   ├── local/
│   ├── remote/
│   └── di/
│       └── DataModule.kt
│
domain/
├── build.gradle.kts
├── src/main/java/com/example/domain/
│   ├── repository/
│   ├── usecase/
│   └── di/
│       └── DomainModule.kt
│
ui/
├── build.gradle.kts
├── src/main/java/com/example/ui/
│   ├── main/
│   ├── detail/
│   └── di/
│       └── UiModule.kt
Module Dependencies
// app/build.gradle.kts
dependencies {
    implementation(project(":data"))
    implementation(project(":domain"))
    implementation(project(":ui"))
}

// data/build.gradle.kts
dependencies {
    implementation(project(":domain"))
    implementation("com.google.dagger:hilt-android:2.48")
    kapt("com.google.dagger:hilt-android-compiler:2.48")
}

// domain/build.gradle.kts
dependencies {
    implementation("com.google.dagger:hilt-android:2.48")
    kapt("com.google.dagger:hilt-android-compiler:2.48")
}

// ui/build.gradle.kts
dependencies {
    implementation(project(":domain"))
    implementation("com.google.dagger:hilt-android:2.48")
    kapt("com.google.dagger:hilt-android-compiler:2.48")
}
Module Implementation
// Domain Module
@Module
@InstallIn(SingletonComponent::class)
object DomainModule {
    
    @Provides
    @Singleton
    fun provideGetUsersUseCase(
        userRepository: UserRepository
    ): GetUsersUseCase {
        return GetUsersUseCase(userRepository)
    }
    
    @Provides
    @Singleton
    fun provideGetUserByIdUseCase(
        userRepository: UserRepository
    ): GetUserByIdUseCase {
        return GetUserByIdUseCase(userRepository)
    }
}

// Data Module
@Module
@InstallIn(SingletonComponent::class)
object DataModule {
    
    @Provides
    @Singleton
    fun provideUserRepository(
        apiService: ApiService,
        userDao: UserDao
    ): UserRepository {
        return UserRepositoryImpl(apiService, userDao)
    }
    
    @Provides
    @Singleton
    fun provideApiService(retrofit: Retrofit): ApiService {
        return retrofit.create(ApiService::class.java)
    }
    
    @Provides
    @Singleton
    fun provideUserDao(database: AppDatabase): UserDao {
        return database.userDao()
    }
}

// UI Module
@Module
@InstallIn(ActivityComponent::class)
object UiModule {
    
    @Provides
    @ActivityScoped
    fun provideMainViewModel(
        getUsersUseCase: GetUsersUseCase,
        getUserByIdUseCase: GetUserByIdUseCase
    ): MainViewModel {
        return MainViewModel(getUsersUseCase, getUserByIdUseCase)
    }
}

Testing with Hilt

Testing applications that use Hilt dependency injection.

Unit Testing
@HiltAndroidTest
class MainActivityTest {
    
    @get:Rule
    val hiltRule = HiltAndroidRule(this)
    
    @Before
    fun setup() {
        hiltRule.inject()
    }
    
    @Test
    fun testUserListDisplayed() {
        // Test implementation
    }
}

// Custom test runner
class CustomTestRunner : AndroidJUnitRunner() {
    override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
        return super.newApplication(cl, HiltTestApplication::class.java.name, context)
    }
}

// Test module
@Module
@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [NetworkModule::class]
)
object TestNetworkModule {
    
    @Provides
    @Singleton
    fun provideApiService(): ApiService {
        return MockApiService()
    }
    
    @Provides
    @Singleton
    fun provideUserRepository(
        apiService: ApiService,
        userDao: UserDao
    ): UserRepository {
        return TestUserRepository(apiService, userDao)
    }
}

// Mock implementations
class MockApiService : ApiService {
    override suspend fun getUsers(): List {
        return listOf(
            User(1, "Test User", "test@example.com"),
            User(2, "Test User 2", "test2@example.com")
        )
    }
    
    override suspend fun getUserById(id: Int): User {
        return User(id, "Test User $id", "test$id@example.com")
    }
    
    override suspend fun createUser(user: CreateUserRequest): User {
        return User(999, user.name, user.email)
    }
}

class TestUserRepository @Inject constructor(
    private val apiService: ApiService,
    private val userDao: UserDao
) : UserRepository {
    
    override suspend fun getUsers(): List {
        return apiService.getUsers()
    }
    
    override suspend fun getUserById(id: Int): User? {
        return apiService.getUserById(id)
    }
    
    override suspend fun createUser(user: CreateUserRequest): User {
        return apiService.createUser(user)
    }
}
Repository Testing
@RunWith(MockitoJUnitRunner::class)
class UserRepositoryTest {
    
    @Mock
    private lateinit var apiService: ApiService
    
    @Mock
    private lateinit var userDao: UserDao
    
    private lateinit var userRepository: UserRepository
    
    @Before
    fun setup() {
        userRepository = UserRepositoryImpl(apiService, userDao)
    }
    
    @Test
    fun `getUsers returns users from API when successful`() = runTest {
        // Given
        val users = listOf(
            User(1, "John Doe", "john@example.com"),
            User(2, "Jane Smith", "jane@example.com")
        )
        whenever(apiService.getUsers()).thenReturn(users)
        
        // When
        val result = userRepository.getUsers()
        
        // Then
        assertThat(result).isEqualTo(users)
        verify(userDao).insertUsers(users)
    }
    
    @Test
    fun `getUsers returns cached users when API fails`() = runTest {
        // Given
        val cachedUsers = listOf(
            User(1, "Cached User", "cached@example.com")
        )
        whenever(apiService.getUsers()).thenThrow(IOException("Network error"))
        whenever(userDao.getAllUsers()).thenReturn(cachedUsers)
        
        // When
        val result = userRepository.getUsers()
        
        // Then
        assertThat(result).isEqualTo(cachedUsers)
    }
}

Advanced Features

Advanced Hilt features for complex dependency injection scenarios.

Component Dependencies
@Component(dependencies = [SingletonComponent::class])
@ActivityScoped
interface UserComponent {
    fun inject(activity: UserActivity)
    fun userManager(): UserManager
}

@Module
@InstallIn(UserComponent::class)
object UserComponentModule {
    
    @Provides
    @ActivityScoped
    fun provideUserManager(
        @ApplicationContext context: Context,
        apiService: ApiService
    ): UserManager {
        return UserManagerImpl(context, apiService)
    }
}

@AndroidEntryPoint
class UserActivity : AppCompatActivity() {
    
    @Inject
    lateinit var userManager: UserManager
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user)
        
        // userManager is injected
    }
}
Entry Points
@EntryPoint
@InstallIn(SingletonComponent::class)
interface UserRepositoryEntryPoint {
    fun userRepository(): UserRepository
}

@EntryPoint
@InstallIn(ActivityComponent::class)
interface UserManagerEntryPoint {
    fun userManager(): UserManager
}

// Using entry points
class CustomView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    
    private val userRepository: UserRepository by lazy {
        val entryPoint = EntryPointAccessors.fromApplication(
            context.applicationContext,
            UserRepositoryEntryPoint::class.java
        )
        entryPoint.userRepository()
    }
    
    private val userManager: UserManager by lazy {
        val entryPoint = EntryPointAccessors.fromActivity(
            context as Activity,
            UserManagerEntryPoint::class.java
        )
        entryPoint.userManager()
    }
}
Assisted Injection
// For dependencies that need runtime parameters
class UserDetailViewModel @AssistedInject constructor(
    private val userRepository: UserRepository,
    @Assisted private val userId: Int
) : ViewModel() {
    
    private val _user = MutableStateFlow(null)
    val user: StateFlow = _user.asStateFlow()
    
    init {
        loadUser()
    }
    
    private fun loadUser() {
        viewModelScope.launch {
            _user.value = userRepository.getUserById(userId)
        }
    }
    
    @AssistedFactory
    interface Factory {
        fun create(userId: Int): UserDetailViewModel
    }
}

// Using assisted injection
@AndroidEntryPoint
class UserDetailActivity : AppCompatActivity() {
    
    private val viewModel: UserDetailViewModel by viewModels {
        UserDetailViewModel.Factory(userId)
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user_detail)
        
        // ViewModel is created with userId parameter
    }
}

Best Practices

Module Organization
  • Group related dependencies: Keep related providers in the same module
  • Use appropriate scopes: Choose the right scope for each dependency
  • Minimize module size: Keep modules focused and manageable
  • Document complex dependencies: Add comments for complex injection logic
Performance
  • Use singletons appropriately: Don't overuse singleton scope
  • Lazy initialization: Use lazy injection when appropriate
  • Memory management: Be aware of object lifecycles
  • Compile-time validation: Let Hilt validate dependencies at compile time
Testing
  • Mock external dependencies: Replace network and database dependencies
  • Use test modules: Create separate modules for testing
  • Test injection: Verify that dependencies are injected correctly
  • Isolate tests: Ensure tests don't depend on each other

Common Pitfalls

Avoiding Common Mistakes
  • Don't forget @AndroidEntryPoint: Always annotate Android components
  • Don't mix scopes: Be careful with scope mismatches
  • Don't create circular dependencies: Avoid dependency cycles
  • Don't ignore compile errors: Fix Hilt compilation errors immediately
Debugging Tips
  • Check component hierarchy: Verify module installation
  • Use Hilt debug mode: Enable debug logging
  • Verify annotations: Ensure all required annotations are present
  • Check build configuration: Verify Hilt plugin is applied

Practice Exercises

Try these exercises to reinforce your Hilt knowledge:

Exercise 1: Weather App
// Create a weather app with:
// - Weather API service
// - Local weather cache
// - Location service
// - Weather repository
// - Multiple ViewModels
// - Custom scopes for user preferences
Exercise 2: E-commerce App
// Build an e-commerce app with:
// - Product API service
// - Shopping cart management
// - User authentication
// - Payment processing
// - Order management
// - Multi-module architecture
Exercise 3: Social Media App
// Create a social media app with:
// - User management
// - Post creation and sharing
// - Real-time messaging
// - Image upload service
// - Push notifications
// - Complex dependency relationships

Next Steps

Now that you have a solid foundation in Hilt, explore these advanced topics:

  • Koin: Alternative DI library for Kotlin
  • Dagger: The foundation library that Hilt is built on
  • Service Locator Pattern: Alternative to dependency injection
  • Modular Architecture: Advanced multi-module patterns
  • Dynamic Feature Modules: DI with dynamic modules
  • Custom Scopes: Creating application-specific scopes

Resources

Summary

Hilt is a powerful dependency injection library that simplifies the management of dependencies in Android applications. Its compile-time validation, seamless integration with Android components, and comprehensive testing support make it the preferred choice for dependency injection in modern Android development.

You've learned about Hilt setup, basic and advanced dependency injection, scopes, qualifiers, multi-module architecture, testing, and best practices. Remember to always consider maintainability, testability, and performance when designing your dependency injection architecture.

Practice regularly with different project structures, experiment with advanced features, and stay updated with the latest Hilt patterns and best practices. The more you work with Hilt, the more you'll appreciate its power and flexibility for building scalable, maintainable Android applications.