Привет, Android разработчики!
Хотел бы рассказать вам про мою библиотеку (Anvil Utils) - кодогенератор позволяющий слегка уменьшить количество болилерплейта в многомодульных проектах использующих Dagger 2 и Anvil.
В чём собственно проблема?
Представьте: у вас есть многомодульное приложение.
Почти наверняка у вас есть набор фичей, каждая из которых состоит из api
и implementation
модулей.
Представим, что один из модулей предоставляет фабрику какой-то сущности (например, Decompose компонента).
Если у вас в проекте используется Dagger 2, то реализацию этой фабрики логичнее всего отдать ему, используя Assisted Injection.
Однако, вам всё равно необходимо каким-то образом предоставить биндинг этой Assisted Factory к вашему публичному интерфейсу из модуля api
.
Попробуем посмотреть на варианты как это можно сделать.
Голый Dagger 2
Предположим, у вас есть подобный публичный API вашего feature модуля.
interface MyClass {
interface Factory {
fun create(param1: String, param2: Int): MyClass
}
}
Обычно чтобы реализовать подобный API и сделать его доступным в вашем графе зависимостей, нужно:
Создаём
@AssistedFactory
вimplementation
модуле, наследуем её от публичного интерфейса фабрикиMyClass.Factory
Пишем Dagger модуль который занимается биндингом сгенерированной Dagger 2 фабрики к публичному интерфейсу фабрики из модуля
api
.
Вот пример:
class DefaultMyClass @AssistedInject constructor(
@Assisted param1: String,
@Assisted param2: Int
) : MyClass {
@AssistedFactory
interface Factory: MyClass.Factory {
override fun create(param1: String, param2: Int): DefaultMyClass
}
}
@Module
interface MyClassFactoryBindingModule {
@Binds
fun bindFactory(impl: DefaultMyClass.Factory): MyClass.Factory
}
Также, вам придётся поддерживать руками набор параметров во всех трёх сущностях:
Параметры конструктора
Аргументы публичного интерфейса фабрики
Аргументы assisted фабрики Dagger
Достаточно много боилерплейта для такой простой задачи, не так ли?
![](https://habrastorage.org/getpro/habr/upload_files/51b/f16/12d/51bf1612d1f447cf93ddb5a9df035748.png)
Давайте упростим!
Используем Anvil
Anvil это плагин компилятора Kotlin который помогает значительно уменьшить количество боилерплейта необходимого для использования Dagger 2 в вашем приложении.
Также, будучи правильно сконфигурированным, позволяет увеличить скорость сборки проекта путём избавления от необходимости запуска процессора аннотаций Dagger 2 в ваших фича-модулях.
Я не буду погружаться в подробности того что такое Anvil и почему вам стоит подумать о том чтоб использовать его в вашем проекте. Возможно, напишу о нём статью попозже.
Пока что, если интересует, можете прочитать документацию: https://github.com/square/anvil
Благодаря Anvil, мы можем избавиться от нашего MyClassFactoryBindingModule
и использовать вместо него аннотацию @ContributesBinding
:
class DefaultMyClass @AssistedInject constructor(
@Assisted param1: String,
@Assisted param2: Int
) : MyClass {
@ContributesBinding(AppGraph::class, MyClass.Factory::class)
@AssistedFactory
interface Factory: MyClass.Factory {
override fun create(param1: String, param2: Int): DefaultMyClass
}
}
Anvil сгенерирует для нас соответствующий модуль который создаст биндинг DefaultMyClass.Factory
к MyClass.Factory
.
Этот подход уменьшает количество боилерплейта, но он всё ещё тут!
Нам необходимо вручную следить за соответствием публичного интерфейса фабрики, интерфейса Assisted фабрики, и параметров конструктора.
Может есть вариант как упростить это ещё сильнее?
Встречайте @ContributesAssistedFactory !
Благодаря моей библиотеке Anvil Utils, вы можете попрощаться с необходимостью вручную создавать и поддерживать Assisted фабрики. Аннотация @ContributesAssistedFactory
всё сделает за вас!
Она автоматически сгенерирует assisted фабрику и забиндит её в соответствующий скоуп при помощи @ContributesBinding
.
Теперь мы можем полностью удалить интерфейс assisted фабрики и аннотировать наш DefaultMyClass
аннотацией @ContributesAssistedFactory
:
@ContributesAssistedFactory(AppGraph::class, MyClass.Factory::class)
class DefaultMyClass @AssistedInject constructor(
@Assisted param1: String,
@Assisted param2: Int
) : MyClass
И всё! Anvil Utils сгенерирует всё что нужно сама.
Пример из реального проекта
Для того чтобы продемонстрировать что польза не иллюзорна, я создал пулл реквест в прекрасный open source проект - приложение-компаньон для Flipper - тамагочи для хакеров.
Можете ознакомиться с ним по этой ссылке, если интересно: https://github.com/flipperdevices/Flipper-Android-App/pull/798
![](https://habrastorage.org/getpro/habr/upload_files/e8b/a48/276/e8ba4827677abfd000f553f0c2d80f57.png)
В заключение
Если вы используете в своём проекте Anvil и у вас есть идеи что ещё можно упростить при помощи кодогенерации - открывайте пулл реквесты, возможно я реализую 🙂