Изучение разработки под 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, чтобы получить советы и узнать о мобильной разработке и т. Д., Связанные темы. . ~ Эли ~