Skip to Content

Dependency Injection với Hilt

1. Giới thiệu

Hilt là DI library được Google xây dựng trên Dagger, đơn giản hóa DI trong Android.

2. Setup

// build.gradle.kts (project) plugins { id("com.google.dagger.hilt.android") version "2.51.1" apply false } // build.gradle.kts (app) plugins { id("com.google.dagger.hilt.android") id("com.google.devtools.ksp") } dependencies { implementation("com.google.dagger:hilt-android:2.51.1") ksp("com.google.dagger:hilt-compiler:2.51.1") // For Compose implementation("androidx.hilt:hilt-navigation-compose:1.2.0") }

3. Application Class

@HiltAndroidApp class MyApplication : Application()
<!-- AndroidManifest.xml --> <application android:name=".MyApplication" ...>

4. Activity

@AndroidEntryPoint class MainActivity : ComponentActivity() { @Inject lateinit var analytics: AnalyticsService override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // analytics is ready to use } }

5. ViewModel

@HiltViewModel class UserViewModel @Inject constructor( private val repository: UserRepository, private val savedStateHandle: SavedStateHandle ) : ViewModel() { // ... } // Usage in Compose @Composable fun UserScreen( viewModel: UserViewModel = hiltViewModel() ) { // ... }

6. Modules

Object Module

@Module @InstallIn(SingletonComponent::class) object NetworkModule { @Provides @Singleton fun provideOkHttpClient(): OkHttpClient { return OkHttpClient.Builder() .addInterceptor(HttpLoggingInterceptor()) .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) } }

Interface Bindings

@Module @InstallIn(SingletonComponent::class) abstract class RepositoryModule { @Binds @Singleton abstract fun bindUserRepository( impl: UserRepositoryImpl ): UserRepository }

7. Scopes

@Singleton // Application scope @ActivityScoped // Activity scope @ViewModelScoped // ViewModel scope @FragmentScoped // Fragment scope
@Module @InstallIn(SingletonComponent::class) object DatabaseModule { @Provides @Singleton // One instance for entire app fun provideDatabase(@ApplicationContext context: Context): AppDatabase { return Room.databaseBuilder( context, AppDatabase::class.java, "app_db" ).build() } }

8. Qualifiers

@Qualifier annotation class IoDispatcher @Qualifier annotation class MainDispatcher @Module @InstallIn(SingletonComponent::class) object DispatcherModule { @Provides @IoDispatcher fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO @Provides @MainDispatcher fun provideMainDispatcher(): CoroutineDispatcher = Dispatchers.Main } // Usage class UserRepository @Inject constructor( @IoDispatcher private val ioDispatcher: CoroutineDispatcher ) { suspend fun getUsers() = withContext(ioDispatcher) { // ... } }

9. Application Context

class AnalyticsService @Inject constructor( @ApplicationContext private val context: Context ) { // Use context safely }

10. Entry Points

Inject vào classes không được Hilt quản lý:

@EntryPoint @InstallIn(SingletonComponent::class) interface AnalyticsEntryPoint { fun analytics(): AnalyticsService } // Usage class MyContentProvider : ContentProvider() { private lateinit var analytics: AnalyticsService override fun onCreate(): Boolean { val entryPoint = EntryPointAccessors.fromApplication( context!!, AnalyticsEntryPoint::class.java ) analytics = entryPoint.analytics() return true } }

11. Testing

@HiltAndroidTest class UserViewModelTest { @get:Rule val hiltRule = HiltAndroidRule(this) @Inject lateinit var repository: UserRepository @Before fun setup() { hiltRule.inject() } @Test fun testViewModel() { // repository is injected } } // Replace module for testing @Module @TestInstallIn( components = [SingletonComponent::class], replaces = [RepositoryModule::class] ) object FakeRepositoryModule { @Provides @Singleton fun provideFakeRepository(): UserRepository = FakeUserRepository() }

12. Complete Example

// ApiService interface ApiService { @GET("users") suspend fun getUsers(): List<UserDto> } // Repository interface UserRepository { suspend fun getUsers(): Result<List<User>> } class UserRepositoryImpl @Inject constructor( private val api: ApiService, @IoDispatcher private val dispatcher: CoroutineDispatcher ) : UserRepository { override suspend fun getUsers() = withContext(dispatcher) { try { Result.success(api.getUsers().map { it.toDomain() }) } catch (e: Exception) { Result.failure(e) } } } // Module @Module @InstallIn(SingletonComponent::class) abstract class RepositoryModule { @Binds @Singleton abstract fun bindRepository(impl: UserRepositoryImpl): UserRepository } // ViewModel @HiltViewModel class UserViewModel @Inject constructor( private val repository: UserRepository ) : ViewModel() { // ... } // Composable @Composable fun UserScreen( viewModel: UserViewModel = hiltViewModel() ) { // ... }

📝 Tóm tắt

AnnotationMô tả
@HiltAndroidAppApplication class
@AndroidEntryPointActivity/Fragment
@HiltViewModelViewModel
@InjectConstructor injection
@ModuleProvide dependencies
@ProvidesProvide external classes
@BindsBind interface to impl
@SingletonApplication scope
Last updated on