Настройка Dependency Injection (Dagger 2) в Android-приложении
Dagger 2 генерирует DI-код во время компиляции — никакой рефлексии, никаких сюрпризов в рантайме. За это приходится платить: graphy компонентов, скоупы, сабкомпоненты и квалификаторы требуют понимания архитектуры до начала работы. Хаотичная настройка 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 — от недели. Стоимость рассчитывается индивидуально.







