[Перевод] Dagger 2 для начинающих Android разработчиков. Dagger 2. Продвинутый уровень. Часть 2

Данная статья является седьмой частью серии статей, предназначенных, по словам автора, для тех, кто не может разобраться с внедрением зависимостей и фреймворком Dagger 2, либо только собирается это сделать. Оригинал написан 30 декабря 2017 года. Перевод вольный.

Dagger 2 advanced part 2 image

Это седьмая статья цикла «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().components connection image

Шаг 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();
}


Linked all Components and Modules image

Шаг 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.

То что нужно! Теперь код можно запускать.

GIF
image


Резюме


Мы рассмотрели пример внедрения зависимостей на уровне 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

© Habrahabr.ru