Dagger — е framework за инжектиране на зависимости за Java и Kotlin, който генерира DI код на етапа на компилация чрез обработка на анотации. Hilt — е надстройка над Dagger за Android, която опростява конфигурирането на компоненти и жизнения цикъл. Според данни на Google, 2025, Hilt се използва в повече от 70% от Android приложенията от Google Play Top-100, поддържайки Activity, Fragment, ViewModel и Service чрез предефинирани компоненти. И двата framework-а осигуряват проверка на графа на зависимостите по време на компилация, елиминирайки грешки при инжектиране по време на изпълнение.
Основни точки
Dagger — е framework за инжектиране на зависимости с генериране на код на етапа на компилация (compile-time DI). Разработен в 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 framework, разработчикът създава обекти ръчно чрез конструктори или фабрики, предавайки зависимости по веригата. Всяко ново изискване — промяна на подписа на всички конструктори във веригата. Dagger автоматизира този процес: достатъчно е да декларирате какъв тип е необходим (@Inject constructor), и Dagger ще създаде графа на зависимостите, разрешавайки всички вложени типове. При промяна на зависимостите, Dagger автоматично актуализира генерирания код — невъзможно е да се допусне грешка във веригата.
Инжектиране на зависимости (Dependency Injection) — е модел, при който обектът получава своите зависимости отвън, вместо да ги създава сам. DI реализира принципа на инверсия на контрола (IoC): класът не е отговорен за създаването на собствените си зависимости, а ги декларира чрез конструктор, метод или поле. Инжектирането чрез конструктор (constructor injection) се счита за най-предпочитано, тъй като гарантира, че обектът се създава в валидно състояние.
| Тип инжектиране | Синтаксис на Dagger | Кога да се използва |
|---|---|---|
| Constructor injection | @Inject constructor | Основен начин — за всички собствени класове |
| Field injection | @Inject lateinit var | Само за Android компоненти (Activity, Fragment) |
| Method injection | @Inject fun bind() | За пост-конструкт инициализация |
Основните предимства на DI включват тестваемост (зависимостите могат да бъдат заменени с mock обекти), слаба свързаност (класовете зависят от интерфейси, а не от имплементации) и изрично управление на жизнения цикъл на обектите чрез scope-ове. Dagger автоматично гарантира, че обектът се създава веднъж в рамките на своя scope и се унищожава при излизане от scope-а.
Component — централният елемент на графа на зависимостите на Dagger. Това е интерфейс, анотиран с @Component, който описва моста между Module и целите на инжектиране. Dagger генерира имплементация на Component (напр. DaggerAppComponent) на етапа на компилация. Component определя кои типове са достъпни за инжектиране, чрез абстрактни методи, връщащи необходимите типове, или чрез inject методи, приемащи обект за field injection.
// Модул: предоставя зависимости, които 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)
}
}
// Компонент: свързва Module и целите за инжектиране
@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. Без scope, Dagger създава нова инстанция при всяко инжектиране. @Reusable — scope за обекти, които не трябва да бъдат сингълън, но създаването им е скъпо — 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 за получаване на контекста на приложението.
Когато се изисква инжектиране на две различни имплементации на един и същ интерфейс, се използват квалификатори. Hilt поддържа @Named за текстови идентификатори и персонализирани анотации с @Qualifier. Например @Named("baseUrl") и @Named("imageBaseUrl") за различни текстови конфигурации. Персонализираните квалификатори са за предпочитане пред @Named поради проверка на етапа на компилация — неправилно текстово име няма да бъде открито до runtime.
@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 — scope на Hilt за зависимости, които живеят, докато живее ViewModel. Ако два ViewModel от един и същ тип инжектират една и съща @ViewModelScoped зависимост, всеки получава своя собствена инстанция. Това различава @ViewModelScoped от @ActivityScoped, където едно Activity получава една инстанция за всички фрагменти. За зависимости, специфични за ViewModel (напр. SavedStateHandle), използвайте @HiltViewModel с @Inject constructor(savedStateHandle: SavedStateHandle).
Hilt поддържа assisted injection чрез библиотеката Hilt Extensions. Assisted injection позволява предаване на параметри на конструктора по време на инжектиране, когато част от зависимостите са известни само по време на изпълнение (напр. ID на потребител от intent). За assisted injection се използва @AssistedInject в комбинация с @Assisted параметри. Hilt генерира AssistedFactory, която може да бъде инжектирана по стандартен начин.
Чист Dagger изисква ръчно създаване на Component, дефиниране на scope-ове и конфигуриране на инжектиране във всеки Android компонент. Разработчикът създава AppComponent, ActivityComponent, FragmentComponent и управлява връзките им чрез @Subcomponent. Този подход предоставя максимален контрол, но изисква значително количество boilerplate код. Dagger се използва в големи проекти, където се изисква персонализирана DI архитектура, или в не-Android Java/Kotlin проекти.
Hilt автоматизира boilerplate: един @HiltAndroidApp, един @AndroidEntryPoint за всеки компонент, предефинирани scope-ове. 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 |
| Мултимодул | Чрез @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 framework с ръчно конфигуриране на 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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също