Dagger / Hilt: какво е това, DI и приложение

Автор: IT Sectr Публикувано: 2026-05-03 Време за четене: 9 мин

Dagger — е framework за инжектиране на зависимости за Java и Kotlin, който генерира DI код на етапа на компилация чрез обработка на анотации. Hilt — е надстройка над Dagger за Android, която опростява конфигурирането на компоненти и жизнения цикъл. Според данни на Google, 2025, Hilt се използва в повече от 70% от Android приложенията от Google Play Top-100, поддържайки Activity, Fragment, ViewModel и Service чрез предефинирани компоненти. И двата framework-а осигуряват проверка на графа на зависимостите по време на компилация, елиминирайки грешки при инжектиране по време на изпълнение.

Основни точки

  • Dagger — DI framework за време на компилация с генериране на код чрез анотации @Module, @Provides, @Component
  • Hilt — надстройка за Android, опростяваща Dagger чрез @HiltAndroidApp, @AndroidEntryPoint, @HiltViewModel
  • Component — граф на зависимостите, свързващ Module с Inject целите чрез прокси методи
  • Scope — @Singleton, @ViewModelScoped, @ActivityScoped управляват жизнения цикъл на инжектираните обекти
  • Hilt поддържа мултимодулни проекти чрез @InstallIn за изолирани графи на зависимости

Какво е Dagger / Hilt?

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()За пост-конструкт инициализация

Предимства на compile-time DI

Основните предимства на DI включват тестваемост (зависимостите могат да бъдат заменени с mock обекти), слаба свързаност (класовете зависят от интерфейси, а не от имплементации) и изрично управление на жизнения цикъл на обектите чрез scope-ове. Dagger автоматично гарантира, че обектът се създава веднъж в рамките на своя scope и се унищожава при излизане от scope-а.

Архитектура на Dagger: Component, Module, Provides

Component — централният елемент на графа на зависимостите на Dagger. Това е интерфейс, анотиран с @Component, който описва моста между Module и целите на инжектиране. Dagger генерира имплементация на Component (напр. DaggerAppComponent) на етапа на компилация. Component определя кои типове са достъпни за инжектиране, чрез абстрактни методи, връщащи необходимите типове, или чрез inject методи, приемащи обект за field injection.

kotlin
// Модул: предоставя зависимости, които 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 за Android: @HiltAndroidApp и @AndroidEntryPoint

Hilt опростява конфигурирането на Dagger за Android благодарение на предефинирани компоненти и автоматично генериране на основния граф. Анотацията @HiltAndroidApp върху Application класа активира генерирането на Hilt компонента. Без тази анотация Hilt не работи — тя е задължителна за всяко Android приложение, използващо Hilt. @HiltAndroidApp създава родителския компонент SingletonComponent, от който наследяват всички останали компоненти на приложението.

kotlin
@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 за получаване на контекста на приложението.

Qualifier: @Named и персонализирани квалификатори

Когато се изисква инжектиране на две различни имплементации на един и същ интерфейс, се използват квалификатори. Hilt поддържа @Named за текстови идентификатори и персонализирани анотации с @Qualifier. Например @Named("baseUrl") и @Named("imageBaseUrl") за различни текстови конфигурации. Персонализираните квалификатори са за предпочитане пред @Named поради проверка на етапа на компилация — неправилно текстово име няма да бъде открито до runtime.

Hilt ViewModel: @HiltViewModel и @Inject constructor

@HiltViewModel — анотация, която замества ръчната фабрика ViewModelProvider.Factory. Клас, анотиран с @HiltViewModel с @Inject constructor, автоматично получава всички зависимости чрез Dagger. Hilt генерира ViewModelFactory, използвана от Jetpack ViewModelProvider. Без Hilt разработчикът би трябвало да пише фабриката ръчно, предавайки всеки параметър от Activity или фрагмент.

kotlin
@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 vs Hilt: сравнение и миграция

Чист 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 методите.

ХарактеристикаDaggerHilt
КонфигурацияРъчна: 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 и Hilt?

Dagger — основен compile-time DI framework с ръчно конфигуриране на Component и Module. Hilt — надстройка за Android, автоматизираща създаването на компоненти и интеграцията с жизнения цикъл на Activity, Fragment, ViewModel, Service и BroadcastReceiver.

За какво служи @HiltAndroidApp?

@HiltAndroidApp активира генерирането на Hilt компонента за Application. Без тази анотация Hilt не може да създаде основния компонент SingletonComponent, от който наследяват всички ActivityComponent, FragmentComponent и ViewModelComponent. Анотацията е задължителна за всеки Hilt проект.

Как Hilt работи с Jetpack Navigation?

Hilt Navigation предоставя @HiltViewModel за ViewModel в NavBackStackEntry и hiltNavGraphViewModels() за скоупиране на ViewModel в рамките на навигационния граф. Библиотеката android.hilt:hilt-navigation-fragment автоматично създава ViewModel за всеки NavBackStackEntry.

Как да инжектирам Context в Hilt?

Използвайте @ApplicationContext за контекста на приложението или @ActivityContext за контекста на Activity. Hilt предоставя тези квалификатори вградени в библиотеката android.hilt:hilt-android. @ActivityContext е достъпен само в модули, инсталирани в ActivityComponent.

Какво е @Binds и кога се използва?

@Binds — ефективна алтернатива на @Provides, когато методът приема точно един параметър и връща неговия тип като интерфейс. @Binds генерира директно преобразуване (cast) без извикване на метода, което намалява количеството генериран код и повишава производителността при инжектиране.

Резюме

  • Dagger — compile-time DI framework с анотации @Module, @Provides, @Component и генериране на код чрез APT
  • Hilt — Android надстройка над Dagger с @HiltAndroidApp, @AndroidEntryPoint, @InstallIn и предефинирани компоненти
  • Component управлява графа на зависимостите, Module предоставя класове на трети страни, Provides — фабрики за обекти
  • Scope (@Singleton, @ViewModelScoped, @ActivityScoped) определя жизнения цикъл на обекта в Dagger графа
  • @HiltViewModel автоматизира създаването на ViewModel, елиминирайки ръчните фабрики ViewModelProvider.Factory
  • @InstallIn изолира модулите по компоненти, предотвратявайки изтичане на зависимости между слоевете на приложението
  • Hilt се препоръчва от Google за всички нови Android проекти, Dagger — за не-Android и персонализирани DI архитектури

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също