Dependency Injection với Hilt
1. Giới thiệu
Phần tiêu đề “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
Phần tiêu đề “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
Phần tiêu đề “3. Application Class”@HiltAndroidAppclass MyApplication : Application()<application android:name=".MyApplication" ...>4. Activity
Phần tiêu đề “4. Activity”@AndroidEntryPointclass MainActivity : ComponentActivity() {
@Inject lateinit var analytics: AnalyticsService
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // analytics is ready to use }}5. ViewModel
Phần tiêu đề “5. ViewModel”@HiltViewModelclass UserViewModel @Inject constructor( private val repository: UserRepository, private val savedStateHandle: SavedStateHandle) : ViewModel() { // ...}
// Usage in Compose@Composablefun UserScreen( viewModel: UserViewModel = hiltViewModel()) { // ...}6. Modules
Phần tiêu đề “6. Modules”Object Module
Phần tiêu đề “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
Phần tiêu đề “Interface Bindings”@Module@InstallIn(SingletonComponent::class)abstract class RepositoryModule {
@Binds @Singleton abstract fun bindUserRepository( impl: UserRepositoryImpl ): UserRepository}7. Scopes
Phần tiêu đề “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
Phần tiêu đề “8. Qualifiers”@Qualifierannotation class IoDispatcher
@Qualifierannotation class MainDispatcher
@Module@InstallIn(SingletonComponent::class)object DispatcherModule {
@Provides @IoDispatcher fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
@Provides @MainDispatcher fun provideMainDispatcher(): CoroutineDispatcher = Dispatchers.Main}
// Usageclass UserRepository @Inject constructor( @IoDispatcher private val ioDispatcher: CoroutineDispatcher) { suspend fun getUsers() = withContext(ioDispatcher) { // ... }}9. Application Context
Phần tiêu đề “9. Application Context”class AnalyticsService @Inject constructor( @ApplicationContext private val context: Context) { // Use context safely}10. Entry Points
Phần tiêu đề “10. Entry Points”Inject vào classes không được Hilt quản lý:
@EntryPoint@InstallIn(SingletonComponent::class)interface AnalyticsEntryPoint { fun analytics(): AnalyticsService}
// Usageclass 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
Phần tiêu đề “11. Testing”@HiltAndroidTestclass 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
Phần tiêu đề “12. Complete Example”// ApiServiceinterface ApiService { @GET("users") suspend fun getUsers(): List<UserDto>}
// Repositoryinterface 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@HiltViewModelclass UserViewModel @Inject constructor( private val repository: UserRepository) : ViewModel() { // ...}
// Composable@Composablefun UserScreen( viewModel: UserViewModel = hiltViewModel()) { // ...}📝 Tóm tắt
Phần tiêu đề “📝 Tóm tắt”| Annotation | Mô tả |
|---|---|
@HiltAndroidApp |
Application class |
@AndroidEntryPoint |
Activity/Fragment |
@HiltViewModel |
ViewModel |
@Inject |
Constructor injection |
@Module |
Provide dependencies |
@Provides |
Provide external classes |
@Binds |
Bind interface to impl |
@Singleton |
Application scope |