Dependency Injection (Dagger 2) в Android-додатку
Dagger 2 генерує DI-код під час компіляції — без рефлексії, без сюрпризів в runtime. За це доводиться платити: граф компонентів, скоупи, сабкомпоненти та кваліфікатори вимагають розуміння архітектури перед початком роботи. Хаотичне налаштування Dagger в середині проекту — один з найболючіших рефакторингів в Android-розробці.
Структура компонентів
Стандартна схема для великого додатку: AppComponent (Singleton) → ActivityComponent (PerActivity) → FragmentComponent (PerFragment). Кожен рівень — сабкомпонент або залежний компонент.
@Singleton
@Component(modules = [AppModule::class, NetworkModule::class, DatabaseModule::class])
interface AppComponent {
fun inject(app: App)
fun activityComponentBuilder(): ActivityComponent.Builder
}
@Module
class NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(AuthInterceptor())
.connectTimeout(30, TimeUnit.SECONDS)
.build()
}
@Provides
@Singleton
fun provideRetrofit(client: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(BuildConfig.API_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
Скоупи та їх межі
Найчастіша помилка — неправильні скоупи. Якщо UserRepository оголошений @Singleton та AuthToken всередину нього зберігається в пам'яті, то після logout без пересоздання компонента старий токен залишається в живому об'єкті. Це приводить до запитів з чужим токеном — продакшн-баг, який відтворюється лише при конкретному сценарії використання.
Рішення: @Singleton-компоненти не повинні містити змінюване стан, залежне від сесії користувача. Сесійні залежності виносяться в @UserScope:
@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class UserScope
@UserScope
@Subcomponent(modules = [UserModule::class])
interface UserComponent {
@Subcomponent.Factory
interface Factory {
fun create(@BindsInstance userId: String): UserComponent
}
fun inject(profileFragment: ProfileFragment)
}
UserComponent створюється після логіну, знищується після logout. Усі залежності, прив'язані до користувача, живуть рівно стільки, скільки потрібно.
Multibindings та плагінна архітектура
@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(LoginViewModel::class)
abstract fun bindLoginViewModel(vm: LoginViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(ProfileViewModel::class)
abstract fun bindProfileViewModel(vm: ProfileViewModel): ViewModel
}
@IntoMap з @ViewModelKey — паттерн для інжекції ViewModels через ViewModelProvider.Factory. Dagger створює Map<Class<out ViewModel>, Provider<ViewModel>>, фабрика вибирає потрібний клас. Без цього паттерну кожну ViewModel доводиться окремо оголошувати в компоненті.
Kapt та KSP
Dagger 2 традиційно працює з kapt. Починаючи з Dagger 2.50 доступна експериментальна підтримка KSP, яка прискорює інкрементальні збірки. Для нових проектів має сенс використовувати KSP:
// build.gradle.kts
plugins {
id("com.google.devtools.ksp")
}
dependencies {
implementation("com.google.dagger:dagger:2.51")
ksp("com.google.dagger:dagger-compiler:2.51")
}
На проекті з ~200 Dagger-анотаціями перехід з kapt на KSP скоротив час clean build з 4.5 до 2.8 хвилин.
Тестування
Dagger та тести — окрема історія. Стандартний підхід: тестові модулі, які замінюють продакшн-залежності на фейки:
@Component(modules = [TestNetworkModule::class, DatabaseModule::class])
interface TestAppComponent : AppComponent
@Module
class TestNetworkModule {
@Provides
@Singleton
fun provideApiService(): ApiService = FakeApiService()
}
В Espresso-тестах DaggerTestAppComponent підставляється замість основного в App.appComponent перед запуском тесту. Без цієї заміни інтеграційні тести йдуть на реальний сервер.
Коли вибирати Dagger 2, а не Hilt
Hilt — обгортка над Dagger з попередньо заданою структурою компонентів. Якщо потрібні нестандартні скоупи, мультимодульний граф з independent компонентами або Dagger вже в проекті — Dagger 2 дає повний контроль. Hilt швидше стартує, але обмежує в складних архітектурах.
Налаштування Dagger 2 з нуля — 3–5 днів: аналіз архітектури, проектування графа компонентів, налаштування модулів, інтеграція з ViewModels. Рефакторинг існуючого коду на Dagger — від тижня. Вартість розраховується індивідуально.







