Зачем нужен Dagger https://startandroid.ru/ru/courses/architecture-components/16-course/dagger2/424-urok-1.html
Модули и компоненты - это два ключевых понятия даггера.
При использовании даггера схема будет выглядеть так: Activity -> Component -> Module -> Presenter Activity обращается к компоненту, компонент с помощью модулей создает Presenter и возвращает его в Activity.
Модули - это просто классы, куда мы помещаем код создания объектов. И обычно каждый модуль включает в себя объекты близкие по смыслу. Например: Модуль ItemModule будет содержать в себе код создания объектов, связанных с пользователями, т.е. что-нибудь типа Item и ItemController. Модуль NetworkModule - объекты OkHttpClient и ApiService. Модуль StorageModule - объекты DataController и SharedPreferences
Компонент - это посредник между Activity и модулем. Когда Activity нужен какой-либо объект, она сообщает об этом компоненту. Компонент знает, какой модуль умеет создавать такой объект, просит модуль создать объект и передает его в Activity. При этом компонент может использовать другие модули, чтобы создать всю иерархию объектов, необходимую для создания искомого объекта.
Рассмотрим подробнее шаги этих схем:
- Activity сообщает компоненту, что ему понадобится Presenter
- Компонент использует модули, чтобы создать все необходимые объекты, которые понадобятся для создания Presenter
- Компонент в итоге получает от модулей требуемый объект Presenter и отдает его Activity
Пример: D:\p\dagger-simple
implementation 'com.google.dagger:dagger:2.13' annotationProcessor 'com.google.dagger:dagger-compiler:2.13'
Если вдруг у вас что-то не работает, то воспользуйтесь этим готовым рабочим проектом https://github.com/startandroid/Dagger2_FirstProject
В качестве объектов, которые мы будем запрашивать от даггера, используем пару классов: DatabaseHelper и NetworkUtils. public class DatabaseHelper { }
public class NetworkUtils { } Их реализация нам сейчас не важна, оставляем их пустыми.
Предположим, что эти объекты будут нужны нам в MainActivity.
Чтобы получить их с помощью даггера, нам нужно создать модули и компонент.
Создаем модули, которые будут уметь предоставлять требуемые объекты. Именно в модулях мы и пишем весь код по созданию объектов. Это обычные классы, но с парой аннотаций: @Module и @Provides NetworkModule и StorageModule
Аннотацией @Module мы сообщаем даггеру, что этот класс является модулем. А аннотация @Provides указывает, что метод является поставщиком объекта и компонент может использовать его, чтобы получить объект. Технически можно было вполне обойтись и одним модулем. Но логичнее будет разделить объекты на модули по их смыслу и области применения. Модули готовы, теперь создаем компонент. Для этого нам необходимо создать интерфейс AppComponent Данный интерфейс описывает пустой компонент, который пока ничего не будет уметь. При компиляции проекта, даггер найдет этот интерфейс по аннотации @Component и сгенерирует класс DaggerAppComponent (имя класса = слово Dagger + имя интерфейса), которые реализует этот интерфейс. Это и будет класс компонента.
Все что от нас требуется - наполнить интерфейс методами. Этим мы дадим понять компоненту, какие объекты он должен уметь нам возвращать. А при сборе проекта даггер уже сам их реализует в сгенерированном классе компонента.
Компонент может возвращать нам объекты двумя способами. Первый - это обычные get-методы. Т.е. мы просто вызываем метод, который вернет нам объект. Второй способ интереснее, это inject-методы. В этом случае мы передаем компоненту экземпляр Activity, и компонент сам заполняет там все необходимые поля, создавая необходимые объекты.
Рассмотрим оба способа на примерах.
Get методы Дополним интерфейс, чтобы компонент научился создавать для нас объекты. @Component(modules = {StorageModule.class, NetworkModule.class}) public interface AppComponent { NetworkUtils getNetworkUtils(); DatabaseHelper getDatabaseHelper(); } Список modules - это модули, в которых компонент сможет искать код создания объектов.
Методы могут быть с любым именем, главное - это их возвращаемые типы (NetworkUtils и DatabaseHelper). Они дают понять компоненту, какие именно объекты мы захотим от него получить. При компиляции, даггер проверит, в каком модуле какой объект можно достать и нагенерит в реализации двух этих методов соответствующий код создания этих объектов. А в MainActivity мы просто вызовем эти методы компонента, чтобы получить готовые объекты.
Осталось где-то описать создание экземпляра компонента. Используем для этого Application класс. Не забудьте добавить его в манифест
Скомпилируйте проект. В Android Studio это можно сделать через меню Build -> Make Project (CTRL+F9). После того, как процесс завершится, класс DaggerAppComponent будет создан в недрах папки build\generated. Студия теперь знает этот класс и должна предлагать добавить его в import, чтобы в коде не было никаких ошибок.
Теперь в MainActivity мы можем использовать этот компонент, чтобы получить готовые объекты DatabaseHelper и NetworkUtils: public class MainActivity extends AppCompatActivity {
DatabaseHelper databaseHelper;
NetworkUtils networkUtils;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
databaseHelper = App.getComponent().getDatabaseHelper();
networkUtils = App.getComponent().getNetworkUtils();
}
} При запуске приложения объекты будут созданы даггером. ПРОФИТ
Inject-методы У нас в MainActivity сейчас всего два объекта, которые мы получаем от компонента. Но если будет штук 20, то придется в интерфейсе компонента описать 20 get-методов и в коде MainActivity написать 20 вызовов этих методов. У даггера есть более удобное решение для таких случаев. Мы можем научить компонент не просто возвращать объекты, а самому наполнять Activity требуемыми объектами. Т.е. мы даем компоненту экземпляр MainActivity, а он смотрит, какие объекты нужны, создает их и сам помещает в соответствующие поля.
Перепишем интерфейс компонента @Component(modules = {StorageModule.class, NetworkModule.class}) public interface AppComponent { void injectsMainActivity(MainActivity mainActivity); } Вместо пары get-методов мы описываем один inject-метод. Имя может быть любым, главное - это тип его единственного параметра. Мы указываем здесь MainActivity. Тем самым, мы говорим компоненту, что когда мы будем вызывать этот метод и передавать туда экземпляр MainActivity, мы ожидаем, что компонент наполнит этот экземпляр требуемыми объектами.
При компиляции проекта, даггер увидит этот метод в интерфейсе, просмотрит класс MainActivity на наличие (помеченных специальными аннотациями) полей и определит, какие объекты ему нужно будет создавать. В итоге, в классе компонента даггер реализует метод injectsMainActivity так, чтобы он получал объекты из своих модулей и подставлял их в соответствующие переменные переданного ему экземпляра MainActivity.
Перепишем MainActivity public class MainActivity extends AppCompatActivity {
@Inject
DatabaseHelper databaseHelper;
@Inject
NetworkUtils networkUtils;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
App.getComponent().injectsMainActivity(this);
}
}
Аннотациями @Inject мы помечаем поля, которые компонент должен заполнить. При вызове метода injectsMainActivity компонент вытащит из модулей объекты DatabaseHelper и NetworkUtils и поместит их в поля MainActivity
Граф зависимостей Совокупность всех объектов, которые умеет создавать компонент, называется граф объектов компонента, или граф зависимостей компонента. Т.е. в примере выше этот граф состоит всего из двух объектов: DatabaseHelper и NetworkUtils. Компонент знает как создать эти объекты и может их предоставить.
В некоторых случаях при создании одного объекта, компоненту может потребоваться другой объект. Мы говорили об этом в самом начале этого урока. Когда для создания презентера нам понадобилось создать еще с десяток объектов.
Рассмотрим пример модуля
@Module
public class NetworkModule {
@Provides NetworkUtils provideNetworkUtils(HttpClient httpClient) {
return new NetworkUtils(httpClient);
}
@Provides HttpClient provideHttpClient() {
return new HttpClient();
}
}
Когда мы от компонента попросим объект NetworkUtils, компонент придет в этот модуль и вызовет метод provideNetworkUtils.
Но на вход этому методу требуется объект HttpClient.
Компонент ищет, какой из его модулей умеет создавать такой объект и находит его в этом же модуле.
Он вызывает метод provideHttpClient, получает объект HttpClient и использует его при вызове provideNetworkUtils.
Т.е. если ваш объект требует для создания другие объекты, то вам необходимо в модулях описать создание всех этих объектов.
В этом случае компонент создаст всю цепочку и получит искомый объект.
Бывают случаи, когда не все можно так просто создать в модулях и нужны какие-то объекты извне даггера.
Выявление ошибок К плюсам даггера относят то, что если у вас есть какая-то ошибка в построении зависимостей, то вы узнаете об этом не в Runtime, а на этапе компиляции.
Дальше платно :) Но есть проект https://github.com/startandroid/Dagger2_ScopeProject