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
| 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 |
Last updated on