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

image

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

Серия статей



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


В предыдущей статье мы обсуждали как ручное использование внедрения зависимостей (DI) усложняет работу и увеличивает количество шаблонного кода. После рассмотрели как Dagger 2 избавляет нас от этой боли и генерирует шаблонный код за нас. Также пробежались по обработчикам аннотаций и базовым аннотациям Dagger 2. Затем применили эти аннотации на примере и внедрили зависимости с помощью Dagger 2.

Анатомия DaggerBattleComponent


Для лучшего понимания Dagger 2 рассмотрим класс DaggerBattleComponent. Установите курсор на DaggerBattleComponent и нажмите Ctrl+B (или Ctrl+ЛКМ, Command+ЛКМ). Вы увидите следующее:

@Generated(
  value = "dagger.internal.codegen.ComponentProcessor",
  comments = "https://google.github.io/dagger"
)
public final class DaggerBattleComponent implements BattleComponent {
  private DaggerBattleComponent(Builder builder) {}

  public static Builder builder() {
    return new Builder();
  }

  public static BattleComponent create() {
    return new Builder().build();
  }
  
  // реализованный метод интерфейса
  @Override
  public War getWar() {
    return new War(new Starks(), new Boltons());
  }

  public static final class Builder {
    private Builder() {}

    public BattleComponent build() {
      return new DaggerBattleComponent(this);
    }
  }
}

Вот что генерирует Dagger 2 за нас для решения проблемы сильных связей (hard dependency). Если посмотреть на интерфейс, который реализует класс, то вы увидите, что это BattleComponent? — интерфейс, который мы ранее создали и описали в нем метод getWar() для предоставления экземпляра класса War.

Эта зависимость предоставляется с использованием шаблона builder. О данном шаблоне подробнее модно прочитать здесь и здесь.

Изучим кое-что новое


Я надеюсь, что вы четко понимаете, зачем нужен метод getWar(). Сейчас я хочу добавить ещё пару зависимостей: Starks и Boltons. Добавим методы в интерфейс:

@Component
interface BattleComponent {
    War getWar();
    // добавляем методы
    Starks getStarks();
    Boltons getBoltons();
}

После внесения изменений заново соберите проект. Теперь проверим класс DaggerBattleComponent. Если вы всё сделали верно, то увидите следующее.

@Generated(
  value = "dagger.internal.codegen.ComponentProcessor",
  comments = "https://google.github.io/dagger"
)
public final class DaggerBattleComponent implements BattleComponent {
  private DaggerBattleComponent(Builder builder) {}

  public static Builder builder() {
    return new Builder();
  }

  public static BattleComponent create() {
    return new Builder().build();
  }

  @Override
  public War getWar() {
    return new War(getStarks(), getBoltons());
  }

  @Override
  public Starks getStarks() {
    return new Starks();
  }

  @Override
  public Boltons getBoltons() {
    return new Boltons();
  }

  public static final class Builder {
    private Builder() {}

    public BattleComponent build() {
      return new DaggerBattleComponent(this);
    }
  }
}

Как видно, Dagger 2 реализовал методы getStarks() и getBoltons().

Мы указали Dagger 2 получить эти зависимости с помощью аннотации @Inject в классе Boltons. Давайте кое-что сломаем. Уберите аннотацию @Inject из класса Boltons. Соберите проект заново.

Ничего не произошло? Да, вы не получили никакой ошибки, но попробуйте запустить проект. Вы должны получить следующую ошибку:

image

Если прочитаете текст ошибки, то он явно говорит о том, что методы getWar() и getBoltons() не будут работать, если нет пометок аннотациями @Inject или @Provides.

Как ранее упоминалось, Dagger 2 позволяет легко отслеживать ошибки. Можете немного поиграть с этим классом.

Аннотации @Module и @Provides


Копнем глубже и разберемся с парой полезных аннотаций — @Module и @Provides. Их стоит использовать, если размер вашего проекта увеличивается.

@Module


Если кратко, то эта аннотация отмечает модули и классы. Поговорим об Android. У нас может быть модуль ContextModule и этот модуль будет предоставлять зависимости ApplicationContext и Context для других классов. Для этого мы должны пометить класс ContextModule аннотацией @Module.

@Provides


Если кратко, то данная аннотация нужна для пометки методов, которые предоставляют зависимости, внутри модулей. В ранее описанном примере мы пометили класс ContextModule аннотацией @Module, но также нам необходимо пометить методы, которые предоставляют зависимости ApplicationContext и Context аннотацией @Provides.

Посмотрите небольшой пример (ссылка на ветку).

Пример


Возьмем два сервиса, предоставляемых Браавосом — деньги (Cash) и солдат (Soldiers) (Я не уверен, что они предоставляют такие услуги, но рассмотрим это только для примера). Создадим два класса:

public class Cash {
    public Cash(){
        // что-то происходит
    }
}

public class Soldiers {
    public Soldiers(){
      // что-то происходит
    }
}

Теперь создадим модуль и назовем его BraavosModule. Он будет снабжать нас двумя зависимостями — Cash и Soldiers.

@Module // Модуль
public class BraavosModule {
    Cash cash;
    Soldiers soldiers;

    public BraavosModule(Cash cash, Soldiers soldiers){
        this.cash=cash;
        this.soldiers=soldiers;
    }

    @Provides // Предоставляет зависимость Cash
    Cash provideCash(){
        return cash;
    }

    @Provides // Предоставляет зависимость Soldiers
    Soldiers provideSoldiers(){
        return soldiers;
    }
}

Как мы видели ранее, необходимо пометить все модули аннотацией @Module, а методы, предоставляющие зависимости — аннотацией @Provides.

Вернемся к классу BattleOfBastards и укажем компоненту реализовывать методы provideCash() и provideSoldiers().

@Component(modules = BraavosModule.class)
interface BattleComponent {
    War getWar();
    Cash getCash();
    Soldiers getSoldiers();
}

public class BattleOfBastards {

    public static void main(String[] args){
        Cash cash = new Cash();
        Soldiers soldiers = new Soldiers();
        BattleComponent component = DaggerBattleComponent
                .builder()
                .braavosModule(new BraavosModule(cash, soldiers))
                .build();
        War war = component.getWar();
        war.prepare();
        war.report();
        // используем деньги и солдат
        component.getCash();
        component.getSoldiers();
    }
}

Обратите внимание на то, что модуль добавлен в объявление аннотации @Component. Это говорит о том, что компонент будет содержать внутри себя данный модуль.

@Component(modules = BraavosModule.class)

После всех изменений соберите проект заново. Вы увидите ошибку в методе .create() класса DaggerBattleComponent. Она возникла в связи с тем, что при добавлении модуля необходимо передать эту зависимость Dagger 2. Выглядит это так:

BattleComponent component = DaggerBattleComponent.builder().braavosModule(new BraavosModule(cash, soldiers)).build();

После включения всех модулей вы можете начать использовать их методы через Component.

component.getCash(); component.getSoldiers();

Если вы хотите убедиться, то наведите курсор на DaggerBattleComponent и нажмите Ctrl+B (или Ctrl+ЛКМ, Command+ЛКМ). Вы увидите, что модуль BraavosModule включен в класс для предоставления зависимостей Cash и Soldiers.

Резюме


Мы проанализировали генерируемые Dagger 2 классы и заметили, что Dagger 2 использует шаблон builder для предоставления зависимостей. Также рассмотрели простой пример использования аннотаций @Module и @Provides.

Что дальше?


В следующей статье мы рассмотрим пример Android приложения с использованием Dagger 2.
Следующая статья выйдет 29 декабря или позже. Спасибо за терпение.

Комментарии (0)