Bỏ qua để đến nội dung

Dependency Injection với Hilt

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

// 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")
}
@HiltAndroidApp
class MyApplication : Application()
AndroidManifest.xml
<application
android:name=".MyApplication"
...>
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@Inject
lateinit var analytics: AnalyticsService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// analytics is ready to use
}
}
@HiltViewModel
class UserViewModel @Inject constructor(
private val repository: UserRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// ...
}
// Usage in Compose
@Composable
fun UserScreen(
viewModel: UserViewModel = hiltViewModel()
) {
// ...
}
@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)
}
}
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindUserRepository(
impl: UserRepositoryImpl
): UserRepository
}
@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()
}
}
@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) {
// ...
}
}
class AnalyticsService @Inject constructor(
@ApplicationContext private val context: Context
) {
// Use context safely
}

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
}
}
@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()
}
// 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()
) {
// ...
}

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