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

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

Dagger 2 advanced part 2 image

Это седьмая статья цикла «Dagger 2 для начинающих Android разработчиков.». Если вы не читали предыдущие, то вам сюда.

Серия статей



Ранее в цикле статей


Мы рассмотрели пример проекта и попытались избавиться от сильных связей с помощью внедрения зависимостей, используя 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 дальше, эта серия статей должна была сформировать у вас достаточно понимание того, как он работает.

Ссылки на другие ресурсы (на английском)


  • +11
  • 17.5k
  • 5
Share post

Comments 5

    0
    Статья морально устарела
      0
      Хотелось бы услышать аргументацию.
        0
        Например, для создание компонентов для activity/fragment стоит использовать Dagger Android, что сильно уменьшит количество кода
      0

      В использование Dagger Android тоже есть спорные моменты. Например, каким образом самому контролировать жизненный цикл компонента (сабкрмпонента) фрагмента или активити, ведь Dagger Android по умолчанию его удаляет при повороте экрана (что не совсем удобно, если нужно реализовать кейс с сохранением компонента при повороте — например, сохранить при повороте презентер и все его зависимости).

        0
        Наконец-то, вроде бы я начал понимать, что происходит. Изучал DI и Dagger 2 с нуля, ничего не знал об этом, потратил уйму времени. За это время прочитал весь цикл этих статей, мало что понял, написал уйму кода в своем проекте, столкнулся с кучей ошибок, разобрался с ними, вернулся к циклу статей, вновь всё перечитал и только тогда понял что зачем и куда надо.
        В целом информация очень полезная и хорошо, что разбита на такие разделы. Так как остальные статьи довольно сумбурны и навалено в них в кучу.
        Предложил бы добавить в конце ссылки, например, на хорошие видео о DI и Dagger, в частности, где рассказывается про Dagger Android. Чтобы читающие понимали, что технология не стоит на месте и знали куда дальше им копать (я про русскоязычные источники).

        Only users with full accounts can post comments. Log in, please.