Dagger — це фреймворк впровадження залежностей для Java та Kotlin, що генерує DI-код на етапі компіляції через обробку анотацій. Hilt — надбудова над Dagger для Android, що спрощує налаштування компонентів і життєвого циклу. За даними Google, 2025, Hilt використовується більш ніж у 70% Android-застосунків із Google Play Top-100, підтримуючи Activity, Fragment, ViewModel та Service через попередньо визначені компоненти. Обидва фреймворки забезпечують compile-time перевірку графа залежностей, виключаючи runtime-помилки впровадження.
Головне
Dagger — це фреймворк впровадження залежностей із генерацією коду на етапі компіляції. Розроблений у Square і згодом переданий Google, Dagger використовує процесор анотацій Java APT для аналізу графа залежностей і генерації фабричних класів. На відміну від runtime-DI (Guice, Koin), Dagger не використовує рефлексію — весь код створюється під час компіляції, що забезпечує максимальну продуктивність під час виконання та виявлення помилок на етапі збірки.
Hilt — це бібліотека від Google, побудована поверх Dagger та оптимізована для Android. Hilt надає попередньо визначені компоненти, що відповідають життєвому циклу Android-компонентів: @SingletonComponent для Application, @ActivityComponent для Activity, @FragmentComponent для Fragment, @ViewModelComponent для ViewModel. Це усуває рутинне налаштування Component та Module, яке потрібне в чистому Dagger. Hilt також генерує граф залежностей для кожного Android-компонента автоматично через @AndroidEntryPoint.
За даними Google I/O 2024, Hilt є рекомендованим рішенням для DI в Android-застосунках, написаних на Kotlin. Jetpack-бібліотеки (Navigation, Room, WorkManager) мають вбудовану інтеграцію з Hilt через @HiltViewModel та @HiltWorker. У проєктах, що не використовують Android (чисті Java/Kotlin бібліотеки, серверні застосунки), застосовується чистий Dagger без Hilt-надбудови.
Без DI-фреймворку розробник створює об'єкти вручну через конструктори або фабрики, передаючи залежності по ланцюжку. Кожна нова вимога — зміна сигнатури всіх конструкторів у ланцюжку. Dagger автоматизує цей процес: достатньо оголосити, який тип потрібен (@Inject constructor), і Dagger створить граф залежностей, розв'язавши всі вкладені типи. При зміні залежностей Dagger оновлює згенерований код автоматично — помилитися в ланцюжку неможливо.
Впровадження залежностей — це патерн, за якого об'єкт отримує свої залежності ззовні, а не створює їх самостійно. DI реалізує принцип інверсії управління (IoC): клас не відповідає за створення своїх залежностей, а декларує їх через конструктор, метод або поле. Впровадження через конструктор вважається найбільш бажаним, оскільки воно гарантує, що об'єкт створюється у валідному стані.
| Тип впровадження | Синтаксис Dagger | Коли використовувати |
|---|---|---|
| Constructor injection | @Inject constructor | Основний спосіб — для всіх власних класів |
| Field injection | @Inject lateinit var | Тільки для Android-компонентів (Activity, Fragment) |
| Method injection | @Inject fun bind() | Для post-construct ініціалізації |
Основні переваги DI включають тестованість (залежності підміняються через mock-об'єкти), слабку зв'язаність (класи залежать від інтерфейсів, а не реалізацій) та явне управління життєвим циклом об'єктів через scopes. Dagger автоматично гарантує, що об'єкт створюється один раз у межах свого скоупу та знищується при виході зі скоупу.
Component — центральний елемент графа залежностей Dagger. Це інтерфейс, анотований @Component, який описує міст між Module та цілями впровадження. Dagger генерує реалізацію Component (наприклад, DaggerAppComponent) на етапі компіляції. Component визначає, які типи доступні для впровадження, через абстрактні методи, що повертають потрібні типи, або через методи inject, що приймають об'єкт для field injection.
// Module: надає залежності, які Dagger не може створити сам
@Module
class NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.build()
}
@Provides
@Singleton
fun provideApiService(client: OkHttpClient): ApiService {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
}
// Component: пов'язує Module та Injection-цілі
@Component(modules = [NetworkModule::class])
interface AppComponent {
fun inject(activity: MainActivity)
fun getApiService(): ApiService
}
@Module — це клас, що містить методи з @Provides, які повертають екземпляри залежностей. Module використовується для типів, які Dagger не може створити автоматично: сторонні бібліотеки (OkHttp, Retrofit), об'єкти з параметрами конструктора, інтерфейси з вибором реалізації. @Binds — альтернатива @Provides для випадків, коли метод повертає інтерфейс і приймає єдину реалізацію: Dagger генерує прямий cast без виклику методу.
@Scope визначає час життя об'єкта в графі залежностей. @Singleton — об'єкт створюється один раз на весь застосунок. @ActivityScoped — об'єкт живе, поки живе Activity. @FragmentScoped — поки живе Fragment. Без скоупу Dagger створює новий екземпляр при кожному впровадженні. @Reusable — скоуп для об'єктів, які не обов'язково мають бути синглтонами, але їх створення дороге — Dagger може кешувати екземпляр, але не гарантує цього.
Hilt спрощує налаштування Dagger для Android завдяки попередньо визначеним компонентам і автоматичній генерації базового графа. Анотація @HiltAndroidApp на Application-класі запускає генерацію Hilt-компонента. Без цієї анотації Hilt не працює — вона обов'язкова для будь-якого Android-застосунку, що використовує Hilt. @HiltAndroidApp створює батьківський компонент SingletonComponent, від якого успадковуються всі інші компоненти застосунку.
@HiltAndroidApp
class MyApplication : Application()
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject lateinit var apiService: ApiService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// apiService вже впроваджено до виклику onCreate
}
}
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
@Provides
@Singleton
fun provideDatabase(@ApplicationContext ctx: Context): AppDatabase {
return Room.databaseBuilder(ctx, AppDatabase::class.java, "app.db").build()
}
}
@AndroidEntryPoint — анотація для Activity, Fragment, Service, BroadcastReceiver та View. Вона генерує Hilt-компонент для кожного типу: @AndroidEntryPoint на Activity створює ActivityComponent, який успадковується від SingletonComponent. Дочірній компонент автоматично отримує всі залежності батька. Field injection з @Inject lateinit var доступний лише в класах, анотованих @AndroidEntryPoint — у звичайних класах використовується constructor injection.
@InstallIn вказує, в який Hilt-компонент встановлюється модуль. NetworkModule з @InstallIn(SingletonComponent::class) доступний у всьому застосунку. Module з @InstallIn(ActivityComponent::class) — тільки в Activity. Це ізолює графи залежностей: Activity-специфічні модулі не видно в Fragment та ViewModel, запобігаючи випадковому використанню неприпустимих залежностей. @ApplicationContext — вбудований кваліфікатор Hilt для отримання Context застосунку.
Коли потрібно впровадити дві різні реалізації одного інтерфейсу, використовуються кваліфікатори. Hilt підтримує @Named для рядкових ідентифікаторів та кастомні анотації з @Qualifier. Наприклад, @Named("baseUrl") та @Named("imageBaseUrl") для різних рядкових конфігурацій. Кастомні кваліфікатори кращі за @Named через перевірку на етапі компіляції — неправильне ім'я рядка не буде виявлено до рантайму.
@HiltViewModel — анотація, що замінює ручну фабрику ViewModelProvider.Factory. Клас, анотований @HiltViewModel з @Inject constructor, автоматично отримує всі залежності через Dagger. Hilt генерує ViewModelFactory, використовувану Jetpack ViewModelProvider. Без Hilt розробнику доводиться писати фабрику вручну, передаючи кожен параметр з Activity або фрагмента.
@HiltViewModel
class MainViewModel
@Inject constructor(
private val apiService: ApiService,
private val database: AppDatabase
) : ViewModel() {
private val _users = MutableStateFlow<List<User>>(emptyList())
val users: StateFlow<List<User>> = _users.asStateFlow()
fun loadUsers() {
viewModelScope.launch {
_users.value = apiService.getUsers()
}
}
}
// В Activity — Hilt автоматично створює ViewModel
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
}
ViewModelScoped — скоуп Hilt для залежностей, які живуть, поки живе ViewModel. Якщо два ViewModel одного типу впроваджують одну @ViewModelScoped-залежність, кожен отримує свій екземпляр. Це відрізняє @ViewModelScoped від @ActivityScoped, де одна Activity отримує один екземпляр для всіх фрагментів. Для ViewModel-специфічних залежностей (наприклад, SavedStateHandle) використовується @HiltViewModel з @Inject constructor(savedStateHandle: SavedStateHandle).
Hilt підтримує assisted injection через бібліотеку Hilt Extensions. Assisted injection дозволяє передавати параметри в конструктор під час впровадження, коли частина залежностей відома лише в рантаймі (наприклад, ID користувача з інтенту). Для assisted injection використовується @AssistedInject у комбінації з @Assisted-параметрами. Hilt генерує AssistedFactory, яку можна впровадити стандартним способом.
Чистий Dagger вимагає ручного створення Component, визначення скоупів та налаштування ін'єкції в кожен Android-компонент. Розробник створює AppComponent, ActivityComponent, FragmentComponent, керує їхніми зв'язками через @Subcomponent. Цей підхід дає максимальний контроль, але потребує значного boilerplate-коду. Dagger використовується у великих проєктах, де потрібна нестандартна архітектура DI, або в не-Android Java/Kotlin-проєктах.
Hilt автоматизує boilerplate: один @HiltAndroidApp, один @AndroidEntryPoint для кожного компонента, попередньо визначені скоупи. Hilt рекомендується Google для всіх нових Android-проєктів. Міграція з Dagger на Hilt включає заміну Component на @InstallIn, заміну @Subcomponent на попередньо визначені Hilt-компоненти та заміну ручного ViewModelProvider.Factory на @HiltViewModel. Більшість @Module-класів переносяться з додаванням @InstallIn без зміни @Provides методів.
| Характеристика | Dagger | Hilt |
|---|---|---|
| Налаштування | Ручне: Component, Subcomponent, Builder | Авто: @HiltAndroidApp, @AndroidEntryPoint |
| Android-компоненти | Немає попередньо визначених | 12+ вбудованих компонентів |
| ViewModel | Ручна фабрика | @HiltViewModel + @Inject constructor |
| Multimodule | Через @Component(dependencies) | Через @InstallIn + агрегацію |
| Складність | Висока — потрібен досвід | Низька — інтуїтивно зрозумілий |
| Гнучкість | Максимальна | Стандартна (покриває 95% сценаріїв) |
Обмеження Hilt: бібліотека підтримує тільки Android (не підходить для чисто серверних Java-проєктів), накладає певну структуру компонентів (складно перевизначити) та додає залежність від android.hilt:hilt-navigation-compose для Jetpack Compose-проєктів. Для Compose-застосунків Hilt надає @HiltViewModel, доступний у Composable через hiltViewModel() — без ручного надання ViewModel з Activity.
Часті запитання
Dagger — базовий compile-time DI фреймворк із ручним налаштуванням Component та Module. Hilt — надбудова для Android, що автоматизує створення компонентів та інтеграцію з життєвим циклом Activity, Fragment, ViewModel, Service та BroadcastReceiver.
@HiltAndroidApp вмикає генерацію Hilt-компонента для Application. Без цієї анотації Hilt не може створити базовий компонент SingletonComponent, від якого успадковуються всі ActivityComponent, FragmentComponent та ViewModelComponent. Анотація обов'язкова для будь-якого Hilt-проєкту.
Hilt Navigation надає @HiltViewModel для ViewModel у NavBackStackEntry та hiltNavGraphViewModels() для скоупингу ViewModel у межах навігаційного графа. Бібліотека android.hilt:hilt-navigation-fragment автоматично створює ViewModel для кожного NavBackStackEntry.
Використовуйте @ApplicationContext для контексту застосунку або @ActivityContext для контексту Activity. Hilt надає ці кваліфікатори вбудованими в бібліотеку android.hilt:hilt-android. @ActivityContext доступний тільки в модулях, встановлених у ActivityComponent.
@Binds — ефективна альтернатива @Provides, коли метод приймає рівно один параметр і повертає його тип як інтерфейс. @Binds генерує прямий cast без виклику методу, що зменшує кількість згенерованого коду та підвищує продуктивність при впровадженні.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також