[Перевод] Dagger 2 для начинающих Android разработчиков. Dagger 2. Продвинутый уровень. Часть 2
Данная статья является седьмой частью серии статей, предназначенных, по словам автора, для тех, кто не может разобраться с внедрением зависимостей и фреймворком Dagger 2, либо только собирается это сделать. Оригинал написан 30 декабря 2017 года. Перевод вольный.
Это седьмая статья цикла «Dagger 2 для начинающих Android разработчиков.». Если вы не читали предыдущие, то вам сюда.
Серия статей
- Dagger 2 для начинающих Android разработчиков. Введение.
- Dagger 2 для начинающих Android разработчиков. Внедрение зависимостей. Часть 1 .
- Dagger 2 для начинающих Android разработчиков. Внедрение зависимостей. Часть 2.
- Dagger 2 для начинающих Android разработчиков. Dagger 2. Часть 1.
- Dagger 2 для начинающих Android разработчиков. Dagger 2. Часть 2.
- Dagger 2 для начинающих Android разработчиков. Dagger 2. Продвинутый уровень.
Часть 1. - Dagger 2 для начинающих Android разработчиков. Dagger 2. Продвинутый уровень.
Часть 2 (вы здесь).
Ранее в цикле статей
Мы рассмотрели пример проекта и попытались избавиться от сильных связей с помощью внедрения зависимостей, используя Dagger 2 и аннотации.
Также изучили три новые аннотации. @Scope
для создания объектов в единственном экземпляре (singleton). @Named
для разделения методов, предоставляющих объекты одинакового типа. @Qualifier
как альтернативу @Named
.
Создание нескольких Component
В предыдущей статье мы создали зависимости уровня приложения. Но что, если требуются зависимости только для уровня Activity
? Activity
создается и уничтожается в своем жизненном цикле, а что происходит с зависимостями? Зависимости, созданные внутри Activity
уничтожаются вместе с Activity
.
Лучшее решение заключается в создании отдельных модулей и компонентов для объектов, жизненные циклы которых различаются.
Для объяснения этого я не хочу добавлять новые объекты в ранее рассмотренный проект. Вместо этого рассмотрим нашу MainActivity
как отдельный объект и создадим для него свой модуль и компонент.
Взгляните на ветку Dagger2Part2
.
Шаг 1. Создание области уровня Activity
Для предстоящих изменений я создал отдельный пакет с названием MainActivityFeature
.
Создадим новую область (Scope) для MainActivity
.
@Scope
public @interface MainActivityScope {}
Шаг 2. Создание компонента для MainActivity
Далее создадим отдельный компонент (Component) для MainActivity
и пометим его только что созданной аннотацией.
@Component(dependencies = RandomUserComponent.class)
@MainActivityScope
public interface MainActivityComponent {
RandomUserAdapter getRandomUserAdapter();
RandomUsersApi getRandomUserService();
}
Необходимо позволить MainActivityComponent
ссылаться на RandomUserComponent
, для чего используется атрибут dependencies
. Другими словами, этот атрибут говорит Dagger 2 обращаться к RandomUserComponent
, если требуются дополнительные зависимости.
В данном примере на уровне Activity
нам потребуются адаптер для API и объект для создания вызовов к RandomUsersAPI
. Следовательно, реализуем методы getRandomUserAdapter()
и getRandomUserService()
.
Шаг 3. Создание модуля для MainActivity
Теперь создадим модуль, который предоставит адаптер.
@Module
public class MainActivityModule {
private final MainActivity mainActivity;
public MainActivityModule(MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
@Provides
@MainActivityScope
public RandomUserAdapter randomUserAdapter(Picasso picasso){
return new RandomUserAdapter(mainActivity, picasso);
}
}
Заметка: Внедрение MainActivity
через адаптер не обязательно, я сделал это для примера. Если нужен контекст для Picasso
, то можно использовать holder.imageView.getContext()
.
Обратите внимание на аннотацию @MainActivityScope
, которая добавлена к методу randomUserAdapter()
чтобы ограничить область использования зависимости уровнем Activity
.
Также необходимо сопоставить этот модуль с соответствующим компонентом. Воспользуемся атрибутом modules
.
@Component(modules = MainActivityModule.class, dependencies = RandomUserComponent.class)
@MainActivityScope
public interface MainActivityComponent {
RandomUserAdapter getRandomUserAdapter();
RandomUsersApi getRandomUserService();
}
Шаг 4. Создание класса Application
public class RandomUserApplication extends Application {
// добавьте имя этого класса в манифест
private RandomUserComponent randomUserApplicationComponent;
public static RandomUserApplication get(Activity activity){
return (RandomUserApplication) activity.getApplication();
}
@Override
public void onCreate() {
super.onCreate();
Timber.plant(new Timber.DebugTree());
randomUserApplicationComponent = DaggerRandomUserComponent.builder()
.contextModule(new ContextModule(this))
.build();
}
public RandomUserComponent getRandomUserApplicationComponent(){
return randomUserApplicationComponent;
}
Этот класс, наследуемый от Application
, содержит все зависимости уровня приложения — RandomUserApplicationComponent
.
Шаг 5. Доработка MainActivity
Если вы соберете проект, то Dagger 2 сгенерирует для вас класс DaggerMainActivityComponent
. Для использования зависимостей уровня Activity
нам понадобится получить некоторые зависимости уровня приложения.
public class MainActivity extends AppCompatActivity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
....
MainActivityComponent mainActivityComponent = DaggerMainActivityComponent.builder()
.mainActivityModule(new MainActivityModule(this))
.randomUserComponent(RandomUserApplication.get(this).getRandomUserApplicationComponent())
.build();
randomUsersApi = mainActivityComponent.getRandomUserService();
mAdapter = mainActivityComponent.getRandomUserAdapter();
....
}
}
Заметка: взгляните на метод afterActivityLevelComponent()
в ветке с проектом.
Шаг 6. Поздравьте себя
Мы создали достаточно поддерживаемый код. Сделали зависимости уровня Activity
. Поздравьте себя.
А что, если в компоненте 50 зависимостей?
Если существует действительно много зависимостей, нужно ли нам постоянно писать выражения вроде того, что ниже? randomUserApi = mainActivityComponent.getRandomUserService();
mAdapter = mainActivityComponent.getRandomUserAdapter();
…
Вы можете решить, что для вас это не важно, но и для этой проблемы есть решение.
Использование аннотации @Inject
Вместо того, чтобы указывать Dagger 2 что вам необходимы RandomUserService
и RandomUserAdapter
, пусть Dagger 2 обрабатывает поле, которые мы пометим аннотацией @Inject
.
Изменив классы так как ниже, мы сможем начать использовать аннотацию @Inject
в кратчайшие сроки. Полный пример вы можете просмотреть в следующей ветке.
Доработка MainActivityComponent
Удалим методы getRandomUserService()
и getRandomUserAdapter()
и добавим метод для внедрения MainActivity
.
@Component(modules = MainActivityModule.class, dependencies = RandomUserComponent.class)
@MainActivityScope
public interface MainActivityComponent {
void injectMainActivity(MainActivity mainActivity);
}
Доработка MainActivity
public class MainActivity extends AppCompatActivity {
....
@Inject
RandomUsersApi randomUsersApi;
@Inject
RandomUserAdapter mAdapter;
....
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
.....
MainActivityComponent mainActivityComponent = DaggerMainActivityComponent.builder()
.mainActivityModule(new MainActivityModule(this))
.randomUserComponent(RandomUserApplication.get(this).getRandomUserApplicationComponent())
.build();
mainActivityComponent.injectMainActivity(this);
....
}
}
Как это работает? Когда Dagger 2 находит метод без возвращаемого значения (void
) он понимает, что должно быть что-то, что ему нужно в классе, то есть он будет инициализировать в классе поля, помеченные аннотацией @Inject
.
То что нужно! Теперь код можно запускать.
Резюме
Мы рассмотрели пример внедрения зависимостей на уровне Activity
. Также увидели пример использования аннотации @Inject
.
В заключение
Спасибо, что потратили свое время на чтение и поддержку этой серии статей. Я надеюсь, вы получили некоторое представление о зависимостях и Dagger 2. Причина, по которой я написал эту серию статей заключается в том, что я улучшал свои знания Dagger 2 чтением большого количества статей в разных блогах, но получил ещё больше знаний, пока писал эти статьи для вас. Поэтому призываю всех читателей делиться своими знаниями любыми возможными путями. Я не эксперт в Dagger 2, я считаю себя лишь учеником.
Теперь вы можете продолжить изучать Dagger 2 дальше, эта серия статей должна была сформировать у вас достаточно понимание того, как он работает.
Ссылки на другие ресурсы (на английском)
- https://blog.mindorks.com/introduction-to-dagger-2-using-dependency-injection-in-android-part-1–223289c2a01b
- https://blog.mindorks.com/introduction-to-dagger-2-using-dependency-injection-in-android-part-2-b55857911bcd
- https://blog.mindorks.com/the-new-dagger-2-android-injector-cbe7d55afa6a
- https://blog.mindorks.com/android-dagger2-critical-things-to-know-before-you-implement-275663aecc3e
- https://blog.mindorks.com/a-complete-guide-to-learn-dagger-2-b4c7a570d99c
- http://www.vogella.com/tutorials/Dagger/article.html
- https://medium.com/@iammert/new-android-injector-with-dagger-2-part-1–8baa60152abe