Skip to main content

Dagger

Полезные статьи#

Часть первая Часть вторая

Краткое содержание#

Основные элементы(аннотации) Dagger 2:#

  1. @Inject – базовая аннотация, с помощью которой “запрашивается зависимость”
  2. @Module – классы, чьи методы “предоставляют зависимости”
  3. @Provide – методы внутри @Module, “говорящие Dagger, как мы хотим сконструировать и предоставить зависимость“
  4. @Component – мост между @Inject и @Module
  5. @Scope – предоставляют возможность создания глобальных и “локальных синглтонов”
  6. @Qualifier – если необходимы разные объекты одного типа

Основная идея#

  1. Модуль провайдит зависимости. То есть именно в модулях мы прописываем, какие объекты хотим предоставлять.
  2. Компонент являет собой граф зависимостей. Он объединяет модули и предоставляет зависимости нуждающимся классам (MainActivity)

@Component по сути является мостом между @Module и @Inject. Или другими словами, Компонент представляет собой готовый граф зависимостей

Пример:

@Component(modules = {AppModule.class, UtilsModule.class, ReceiversModule.class})

Данной аннотацией мы говорим Даггеру, что AppComponent содержит три модуля — AppModule, UtilsModule, ReceiversModule. Зависимости, которые провайдит каждый из этих модулей, доступны для всех остальных модулей, объединенных под эгидой компонента AppComponent. Для большей наглядности взглянем на рисунок. картинка Также внутри интерфейса необходимо добавить метод

void inject(MainActivity mainActivity)

Этим методом мы сообщаем Даггеру, в какой класс/классы мы хотим делать инъекции. После этого в данном классе мы можем использовать те зависимости, которые провайдят модули AppModule, UtilsModule, ReceiversModule. Для этого нужно просто добавить в класс соответствующие поля и пометить их аннотацией @Inject, а также сделать их доступность как минимум пакетной (если поле задано как private, то Даггер не сможет подставить в это поле нужную реализацию). Далее в методе onCreate мы добавляем строчку

App.getComponent().inject(this);

для того чтобы получить требуемые зависимости от компонента

Скоупы и сабмодули#

Рассмотрим пример. Есть приложение. В приложении есть несколько модулей, один из которых модуль чата. Модуль чата включает в себя три экрана: экран одиночного чата, группового чата и настройки. Вспоминая Clean architecture, выделяем три горизонтальных уровня:

Уровень всего приложения. Здесь находятся объекты, которые необходимы на протяжении всего жизненного цикла приложения, то есть "глобальные синглтоны". Пускай это будут объекты: Context (глобальный контекст), RxUtilsAbs (класс-утилита), NetworkUtils (класс-утилита) и IDataRepository (класс, отвечающий за запросы к серверу). Уровень чата. Объекты, которые нужны для всех трех экранов Чата: IChatInteractor (класс, реализующий конкретные бизнес-кейсы Чата) и IChatStateController (класс, отвечающий за состояние Чата). Уровень каждого экрана чата. У каждого экрана будет свой Presenter, устойчивый к переориентации, то есть чей жизненный цикл будет отличаться от жизненного цикла фрагмента/активити.

Схематично жизненные циклы будут выглядеть следующим образом: картинка А вот теперь в дело вступает Dagger 2, у которого есть замечательный механизм Scopes. Данный механизм берет на себя создание и хранение единственного экземпляра необходимого класса до тех пор, пока соответствующий scope существует По текущей архитектуре у нас получаются три группы объектов, у которых своя "длина жизни". Таким образом, нам необходима три scope-аннотации:

  1. @Singleton — для глобальных синглтонов.
  2. @ChatScope — для объектов Чата.
  3. @ChatScreenScope — для объектов конкретного экрана Чата.

При этом отметим, что @ChatScope объекты должны иметь доступ к @Singleton объектам, а @ChatScreenScope — к @Singleton и @ChatScope объектам. Схематично: картинка Далее напрашивается создание и соответствующих Компонент Даггера:

AppComponent, который предоставляет "глобальные синглтоны". ChatComponent, предоставляющий "локальные синглтоны" для всех экранов Чата. SCComponent, предоставляющий "локальные синглтоны" для конкретного экрана Чата (SingleChatFragment, то есть экрана Одиночного чата).

И снова визуализируем вышеописанное: картинка

Для того что-бы связать между собой компоненты использеум механизм субкомпонентов

  1. Необходимо прописывать в интерфейсе родителя метод получения Сабкомпонента (упрощенное название Subcomponent)
  2. Для Сабкомпонента доступны все объекты родителя
  3. Родитель может быть только один

Схема зависимостей компонентов

картинка

По схеме видим, что для дочернего компонента доступны все объекты родителя, и так по всему дереву зависимостей компонент. Например, для SCComponent доступен NetworkUtils.