Урок по треку Архитектура мобильных приложений 6 мин

Внедрение зависимостей на iOS, Android и Flutter

DI и Service Locator, скоупы и время жизни, контейнеры на iOS, Android и Flutter, тестируемость.

DI спрашивают на всех мобильных собесах: что это, чем отличается от Service Locator, как устроены скоупы и какой инструмент вы используете.

Принцип

Объект получает зависимости снаружи, а не создаёт их сам:

// плохо
final class ProfileViewModel {
    private let repo = ProfileRepository(api: APIClient.shared)
}

// хорошо
final class ProfileViewModel {
    private let repo: ProfileRepositoryProtocol
    init(repo: ProfileRepositoryProtocol) { self.repo = repo }
}

Способы: через конструктор (предпочтительно: зависимости явные и обязательные), через свойство (когда объект создаёт система, например Activity), через параметр метода.

Главная выгода: тестируемость. В тесте view model получает фейковый репозиторий, и проверка идёт без сети и базы.

Composition root

Граф объектов собирается в одном месте у входа в приложение (или модуля): создаются клиент, репозитории, фабрики экранов. Остальной код получает готовые зависимости.

Service Locator

val api = ServiceLocator.get<Api>()

Объект сам достаёт зависимости из глобального реестра. Минусы: из сигнатуры не видно, что нужно классу, а отсутствие регистрации обнаруживается крэшем во время выполнения. Если контейнер DI доступен из любого места, он превращается в локатор.

Скоупы

Время жизни зависимостей следует за временем жизни данных:

СкоупПримеры
Приложениесетевой клиент, конфигурация, аналитика
Сессия пользователяпрофиль, корзина, кеш заказов, токены
Потоксостояние оформления заказа
Экранview model

Классическая ошибка: пользовательский репозиторий с кешем зарегистрирован синглтоном. После выхода и входа под другим аккаунтом мелькают чужие данные. Скоуп сессии уничтожается при выходе целиком.

Инструменты

Android:

  • Hilt (поверх Dagger): граф строится кодогенерацией при компиляции, ошибки обнаруживаются при сборке. Компоненты: SingletonComponent, ActivityRetainedComponent, ViewModelComponent, ActivityComponent и другие.
  • Koin: DSL на Kotlin, граф собирается при запуске, проще вход, ошибки ловят тестом проверки графа. Работает в Kotlin Multiplatform.

iOS: стандартного фреймворка нет. Чаще ручной DI через инициализаторы и фабрики, иногда контейнеры (Swinject, Factory, Needle с проверкой при компиляции). В SwiftUI сервисы удобно передавать через Environment.

Flutter: Provider и Riverpod (зависимости через дерево и провайдеры с проверкой типов), get_it (сервис-локатор), injectable для генерации регистрации.

React Native: чаще контекст React и модули, реже контейнеры.

Как выбирать

  • Большой проект и много команд: проверка графа при компиляции (Hilt, Needle).
  • Небольшой проект: Koin, ручной DI, Riverpod.
  • В любом случае: зависимости через конструктор, интерфейсы на границах, контейнер только в composition root.

Частые ошибки

  • Вызов контейнера из любых классов.
  • Синглтоны для пользовательских данных.
  • Зависимость от конкретных классов вместо интерфейсов там, где нужна подмена.
  • Огромный модуль DI, который знает обо всём приложении.

Что спросят на собесе

  • Что такое DI и зачем он нужен?
  • Чем DI отличается от Service Locator?
  • Какие скоупы бывают и как сделать скоуп сессии?
  • Hilt или Koin, ручной DI или контейнер на iOS?
  • Как DI помогает тестам?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.