Изучение разработки под Android

Иллюстрированные наручники Dagger 2

Dagger 2 имеет возможность множественных привязок с официальной ссылкой, как показано ниже. Мне потребовалось довольно много времени, чтобы понять и создать рабочий пример.



Поэтому мы решили упростить иллюстрацию и сделать все примеры в github (примечание: код написан на Java, чтобы легче было соответствовать исходному справочному документу. Если вы хотите увидеть версию кода Kotlin, обратитесь к здесь ).

Dagger 2 Multibindings - это просто функция, которая упаковывает зависимости от разных модулей в SET или MAP зависимостей. Затем он может быть введен целиком в целевой объект.

Установить мульти-привязки

Мы используем @IntoSet для привязки значения к Set Collection

@Module
class MyModuleA {
  @Provides 
  @IntoSet
  static String provideOneString(DepA depA, DepB depB) {
    return "ABC";
  }
}

Точно так же мы используем @ElementsIntoSet для привязки набора к Коллекции наборов.

@Module
class MyModuleB {
    @Provides
    @ElementsIntoSet
    static Set<String> provideSomeStrings(DepA depA, DepB depB) {
        return new HashSet<String>(Arrays.asList("DEF", "GHI"));
    }
}

Чтобы подтвердить, у нас есть компонент, как показано ниже

@Component(modules = {MyModuleA.class, MyModuleB.class})
interface MyComponent {
    Set<String> strings();
}

И мы могли проверить, что они были привязаны к Set ‹String›, используя приведенный ниже тест.

@Test
public void testMyComponent() {
    Set expectedContains = 
        new HashSet<String>(Arrays.asList("ABC", "DEF", "GHI"));

    MyComponent myComponent = DaggerMyComponent.create();
    assertEquals(3, myComponent.strings().size());
    assertTrue(myComponent.strings().containsAll(expectedContains));
}

Это проиллюстрировано ниже.

Помимо получения Set<String>, вы также можете получить Provider<Set<String>> или Lazy<Set<String>>. Посмотрите это руководство, если вы не совсем понимаете, что это значит.

Если у нас есть два разных набора строк, мы могли бы использовать Qualifier. Пример кода ниже, где используются QualifySetOne и QualifySetTwo.

@Module
class MyModuleB {
    @Provides
    @ElementsIntoSet
    @QualifySetOne
    static Set<String> provideSomeStrings(DepA depA, DepB depB) {
        return new HashSet<String>(Arrays.asList("DEF", "GHI"));
    }
}
@Module
class MyModuleD {
    @Provides
    @ElementsIntoSet
    @QualifySetTwo
    static Set<String> provideSomeStrings(DepA depA, DepB depB) {
        return new HashSet<String>(Arrays.asList("456", "789"));
    }
}

Чтобы получить более подробную информацию о квалификаторе, ознакомьтесь с этим руководством.

Привязки карт

Помимо набора, вы также можете упаковать свои зависимости в Map. Ключ карты должен быть известен во время компиляции.

Простая карта

Мы можем использовать @IntoMap для упаковки в Map.

Для карт с ключами, которые являются строками, Class<?> или примитивами в рамке, используйте одну из стандартных аннотаций в dagger.multibindings. Два примера ключа - это строка (с использованием @StringKey) и имя класса (с использованием @ClassKey).

@Module
class MyModule {
  @Provides @IntoMap
  @StringKey("foo")
  static Long provideFooValue() {
    return 100L;
  }
  @Provides @IntoMap
  @StringKey("boo")
  static Long provideBooValue() {
    return 200L;
  }
  @Provides @IntoMap
  @ClassKey(Thing.class)
  static String provideThingValue() {
    return "value for Thing";
  }
}

У нас есть наш компонент как

@Component(modules = MyModule.class)
interface MyComponent {
    Map<String, Long> longsByString();
    Map<Class<?>, String> stringsByClass();
}

Тогда мы могли бы их проверить

@Test
public void testMyComponent() {
    MyComponent myComponent = DaggerMyComponent.create();

    assertEquals(2, myComponent.longsByString().size());
    assertEquals(100L, 
      myComponent.longsByString().get("foo").longValue());
    assertEquals(200L, 
      myComponent.longsByString().get("boo").longValue());

    assertEquals(1, myComponent.stringsByClass().size());
    assertEquals("value for Thing", 
      myComponent.stringsByClass().get(Thing.class));
}

Это можно проиллюстрировать дополнительно, как показано ниже.

Помимо получения точной зависимости, можно получить Provider или Lazy фабрики зависимости, просто указав Provider (или Lazy), как это было задумано, как показано в приведенном ниже коде. Посмотрите это руководство, если вы не совсем понимаете, что это значит.

@Component(modules = MyModule.class)
interface MyComponent {
    Map<String, Long> longsByString();
    Map<Class<?>, String> stringsByClass();
    Map<String, Provider<Long>> providerLongsByString();
}

Ключ перечисления или подкласса

Мы также могли бы создать Enum Key или Subclass Key, используя @MapKey, как показано ниже.

enum MyEnum {
  ABC, DEF;
}

@MapKey
@interface MyEnumKey {
  MyEnum value();
}

@MapKey
@interface MyNumberClassKey {
  Class<? extends Number> value();
}

@Module
class MyModule {
  @Provides @IntoMap
  @MyEnumKey(MyEnum.ABC)
  static String provideABCValue() {
    return "value for ABC";
  }

  @Provides @IntoMap
  @MyNumberClassKey(BigDecimal.class)
  static String provideBigDecimalValue() {
    return "value for BigDecimal";
  }
}

Примечание. BigDecimal является подклассом Number .

Это можно легко проиллюстрировать ниже.

@MapKey аннотация возможна для любого типа, кроме массива.

Сложные ключи карты

Простые ключи карты предназначены для одноэлементной карты ключей. Если у вас есть несколько элементов в качестве ключа (т. Е. Комбинация элементов для формирования ключа), вам нужно установить @MapKey’s unwrapValue на false

Ниже приведен пример

@MapKey(unwrapValue = false)
@interface MyKey {
  String name();
  Class<?> implementingClass();
  int[] thresholds();
}

@Module
class MyModule {
  @Provides @IntoMap
  @MyKey(name = "abc", 
         implementingClass = Abc.class, 
         thresholds = {1, 5, 10})
  static String provideAbc1510Value() { return "foo"; }
}

@Component(modules = MyModule.class)
interface MyComponent {
  Map<MyKey, String> myKeyStringMap();
}

Это можно проиллюстрировать ниже

Чтобы обеспечить эквивалент Key, нам нужно будет использовать AutoAnnotation, который является частью функции AutoValue, которая автоматически выполняет сравнение JavaObject Content Equivalent (вместо Object Equivalent)

public class AutoValueUtil {
    @AutoAnnotation
    static MyKey createMyKey(
        String name, Class<?> implementingClass, int[] thresholds) {
        return new AutoAnnotation_AutoValueUtil_createMyKey(
               name, implementingClass, thresholds);
    }
}

Чтобы узнать больше об AutoAnnotation, обратитесь к этому Stackoverflow.

Подключив AutoAnnotation, вы теперь можете легко проверить введение ключевого значения из карты, как показано ниже.

@Test
public void testMyComponent() {
    MyComponent myComponent = DaggerMyComponent.create();
    assertEquals("foo",
          myComponent.myKeyStringMap().get(
            createMyKey("abc", Abc.class, new int[] {1, 5, 10})));
}

Карты, ключи которых неизвестны во время компиляции

Dagger 2 Multibinds into Map может работать только с регистром, для которого ключ известен во время компиляции.

Однако для ключа, который будет известен только во время выполнения, мы могли бы обойтись, создав сначала Map.Entry<Key,Value> в набор (используя @IntoSet), а затем вставив его в другой поставщик, который будет разложен на Map<Key,Value>.

Для более ясной иллюстрации ниже приведены некоторые случайно сгенерированные ключи времени выполнения,

@Module
class MyModule {
    @Provides @IntoSet
    static Map.Entry<String, String> entryOne() {
        String key = randomStringGenerator();
        String value = "Random Value 1";
        return new SimpleImmutableEntry(key, value);
    }

    @Provides @IntoSet
    static Map.Entry<String, String> entryTwo() {
        String key = randomStringGenerator();
        String value = "Random Value 2";
        return new SimpleImmutableEntry(key, value);
    }
}

Это сгенерирует Set<Map.Entry<String, String>> entries, который мы вставим в другого провайдера, разложив Set на Map, как показано ниже.

@Module(includes = MyModule.class)
class MyMapModule {
    @Provides
    static Map<String, String> 
        randomKeyValueMap(Set<Map.Entry<String, String>> entries) {
             Map<String, String> randomKeyValueMap = 
                 new LinkedHashMap<>(entries.size());
        for (Map.Entry<String, String> entry : entries) {
            randomKeyValueMap.put(entry.getKey(), entry.getValue());
        }
        return randomKeyValueMap;
    }
}

Поскольку вышеупомянутое решение является обходным путем, невозможно автоматически получить привязку Map<Key, Provider<Value>> (или Lazy<Value>), если только начальный набор не равен Set<Map.Entry<Key, Provider<Value>> (или Lazy<Value>). Посмотрите это руководство, если вы не совсем понимаете, что это значит.

Множественные привязки унаследованных подкомпонентов

Одно из значений Multibindings - это подкомпонент (дочерний элемент), хотя он может получить доступ к зависимостям родительского Multibind, он (дочерний элемент) также может связывать дополнительные зависимости в Set или Map.

Пример кода, как показано ниже

@Module
class ParentModule {
    @Provides @IntoSet
    static String string1() {
        return "parent string 1";
    }

    @Provides @IntoSet
    static String string2() {
        return "parent string 2";
    }
}
@Module
class ChildModule {
    @Provides @IntoSet
    static String string3() {
        return "child string 3";
    }

    @Provides @IntoSet
    static String string4() {
        return "child string 4";
    }
}
@Component(modules = ParentModule.class)
interface ParentComponent {
    Set<String> strings();
    ChildComponent childComponent();
}
@Subcomponent(modules = ChildModule.class)
interface ChildComponent {
    Set<String> strings();
}

Для тестирования и проверки ниже показано, что родительский элемент содержит всего 2 элемента в наборе, а дочерний элемент имеет 4 элемента в наборе.

@Test
public void testMultibindings() {
    Set expectedParentSet = new HashSet<String>
         (Arrays.asList("parent string 1", "parent string 2"));
    ParentComponent parentComponent = 
          DaggerParentComponent.create();
    assertEquals(2, parentComponent.strings().size());
    assertTrue(parentComponent.strings()
          .containsAll(expectedParentSet));
    Set expectedChildSet = new HashSet<String>(Arrays.asList(
          "parent string 1", "parent string 2", 
          "child string 3", "child string 4"));

    ChildComponent childComponent = 
          parentComponent.childComponent();

    assertEquals(4, childComponent.strings().size());
    assertTrue(childComponent.strings()
         .containsAll(expectedChildSet));
}

Объявление мульти-привязок (для пустого набора / карты)

В этом случае у родительского компонента нет никаких элементов, которые можно было бы вставить в Set / Map, но есть дочерний компонент, который нужно вставить в него. Родитель может использовать аннотацию @Multibinds, а класс должен быть abstract.

@Module
abstract class MyModule {
    @Multibinds abstract Set<String> aSet();
    @Multibinds abstract Map<String, String> aMap();
}

При желании для набора можно было установить

@Module
class MyEmptySetModule {
  @Provides @ElementsIntoSet
  static Set<String> emptySet() {
    return Collections.emptySet();
  }
}

Чтобы получить коды, вы можете получить к нему доступ из папки unit test в приведенном ниже git



Спасибо за прочтение. Вы можете ознакомиться с другими моими темами здесь.

Подпишитесь на меня в medium, Twitter, Facebook или Reddit, чтобы получить советы и узнать о мобильной разработке и т. Д., Связанные темы. . ~ Эли ~