- Using kapt
- Using in Gradle
- Annotation processor arguments
- Gradle build cache support
- Improving the speed of builds that use kapt
- Running kapt tasks in parallel
- Caching for annotation processors’ classloaders
- Compile avoidance for kapt
- Incremental annotation processing
- Java compiler options
- Non-existent type correction
- Using in Maven
- Using in CLI
- Generating Kotlin sources
- AP/Javac options encoding
- Keeping Java compiler’s annotation processors
- Урок 1. Введение
- Зачем нужен Dagger
- Пример
- Теория
- Практика
- Подключение
- Объекты
- Модули
- Компонент
- Get метод
- Inject метод
Using kapt
kapt is in maintenance mode. We are keeping it up-to-date with recent Kotlin and Java releases but have no plans to implement new features. Please use the Kotlin Symbol Processing API (KSP) for annotation processing. See the list of libraries supported by KSP.
Annotation processors (see JSR 269) are supported in Kotlin with the kapt compiler plugin.
In a nutshell, you can use libraries such as Dagger or Data Binding in your Kotlin projects.
Please read below about how to apply the kapt plugin to your Gradle/Maven build.
Using in Gradle
Apply the kotlin-kapt Gradle plugin:
Alternatively, you can use the apply plugin syntax:
Then add the respective dependencies using the kapt configuration in your dependencies block:
If you previously used the Android support for annotation processors, replace usages of the annotationProcessor configuration with kapt . If your project contains Java classes, kapt will also take care of them.
If you use annotation processors for your androidTest or test sources, the respective kapt configurations are named kaptAndroidTest and kaptTest . Note that kaptAndroidTest and kaptTest extends kapt , so you can just provide the kapt dependency and it will be available both for production sources and tests.
Annotation processor arguments
Use arguments <> block to pass arguments to annotation processors:
Gradle build cache support
The kapt annotation processing tasks are cached in Gradle by default. However, annotation processors run arbitrary code that may not necessarily transform the task inputs into the outputs, might access and modify the files that are not tracked by Gradle etc. If the annotation processors used in the build cannot be properly cached, it is possible to disable caching for kapt entirely by adding the following lines to the build script, in order to avoid false-positive cache hits for the kapt tasks:
Improving the speed of builds that use kapt
Running kapt tasks in parallel
To improve the speed of builds that use kapt, you can enable the Gradle worker API for kapt tasks. Using the worker API lets Gradle run independent annotation processing tasks from a single project in parallel, which in some cases significantly decreases the execution time. However, running kapt with Gradle worker API enabled can result in increased memory consumption due to parallel execution.
To use the Gradle worker API for parallel execution of kapt tasks, add this line to your gradle.properties file:
When you use the custom JDK home feature in the Kotlin Gradle plugin, kapt task workers use only process isolation mode. Note that the kapt.workers.isolation property is ignored.
Caching for annotation processors’ classloaders
Caching for annotation processors’ classloaders in kapt is Experimental. It may be dropped or changed at any time. Use it only for evaluation purposes. We would appreciate your feedback on it in YouTrack.
Caching for annotation processors’ classloaders helps kapt perform faster if you run many Gradle tasks consecutively.
To enable this feature, use the following properties in your gradle.properties file:
If you run into any problems with caching for annotation processors, disable caching for them:
Compile avoidance for kapt
To improve the times of incremental builds with kapt, it can use the Gradle compile avoidance. With compile avoidance enabled, Gradle can skip annotation processing when rebuilding a project. Particularly, annotation processing is skipped when:
The project’s source files are unchanged.
The changes in dependencies are ABI compatible. For example, the only changes are in method bodies.
However, compile avoidance can’t be used for annotation processors discovered in the compile classpath since any changes in them require running the annotation processing tasks.
To run kapt with compile avoidance:
Add the annotation processor dependencies to the kapt* configurations manually as described above.
Turn off the discovery of annotation processors in the compile classpath by adding this line to your gradle.properties file:
Incremental annotation processing
kapt supports incremental annotation processing that is enabled by default. Currently, annotation processing can be incremental only if all annotation processors being used are incremental.
To disable incremental annotation processing, add this line to your gradle.properties file:
Note that incremental annotation processing requires incremental compilation to be enabled as well.
Java compiler options
kapt uses Java compiler to run annotation processors.
Here is how you can pass arbitrary options to javac:
Non-existent type correction
Some annotation processors (such as AutoFactory ) rely on precise types in declaration signatures. By default, kapt replaces every unknown type (including types for the generated classes) to NonExistentClass , but you can change this behavior. Add the option to the build.gradle file to enable error type inferring in stubs:
Using in Maven
Add an execution of the kapt goal from kotlin-maven-plugin before compile :
Please note that kapt is still not supported for IntelliJ IDEA’s own build system. Launch the build from the “Maven Projects” toolbar whenever you want to re-run the annotation processing.
Using in CLI
kapt compiler plugin is available in the binary distribution of the Kotlin compiler.
You can attach the plugin by providing the path to its JAR file using the Xplugin kotlinc option:
Here is a list of the available options:
sources ( required): An output path for the generated files.
classes ( required): An output path for the generated class files and resources.
stubs ( required): An output path for the stub files. In other words, some temporary directory.
incrementalData : An output path for the binary stubs.
apclasspath ( repeatable): A path to the annotation processor JAR. Pass as many apclasspath options as many JARs you have.
apoptions : A base64-encoded list of the annotation processor options. See AP/javac options encoding for more information.
javacArguments : A base64-encoded list of the options passed to javac. See AP/javac options encoding for more information.
processors : A comma-specified list of annotation processor qualified class names. If specified, kapt does not try to find annotation processors in apclasspath .
verbose : Enable verbose output.
stubs – only generate stubs needed for annotation processing;
apt – only run annotation processing;
stubsAndApt – generate stubs and run annotation processing.
correctErrorTypes : See below. Disabled by default.
The plugin option format is: -P plugin:
: = . Options can be repeated.
Generating Kotlin sources
kapt can generate Kotlin sources. Just write the generated Kotlin source files to the directory specified by processingEnv.options[«kapt.kotlin.generated»] , and these files will be compiled together with the main sources.
Note that kapt does not support multiple rounds for the generated Kotlin files.
AP/Javac options encoding
apoptions and javacArguments CLI options accept an encoded map of options.
Here is how you can encode options by yourself:
Keeping Java compiler’s annotation processors
By default, kapt runs all annotation processors and disables annotation processing by javac. However, you may need some of javac’s annotation processors working (for example, Lombok).
In the Gradle build file, use the option keepJavacAnnotationProcessors :
If you use Maven, you need to specify concrete plugin settings. See this example of settings for the Lombok compiler plugin.
Источник
Урок 1. Введение
В этом уроке я подробно расскажу о Dagger и его возможностях. Мы разберем, что такое Component и Module, подключим Dagger к проекту, и сделаем несколько простых примеров
Зачем нужен Dagger
Если вы хотите снизить зависимость объектов друг от друга и упростить написание тестов для вашего кода, то вам подойдет паттерн Dependency Injection. А Dagger — это библиотека, которая поможет в реализации этого паттерна. В этом курсе я опишу использование библиотеки Dagger версии 2 (далее по тексту даггер).
Плюсы даггера в сравнении с другими библиотеками:
— генерирует код несложный для понимания и отладки
— проверяет зависимости на этапе компиляции
— не создает проблем при использовании proguard
Сразу скажу, что тема нетривиальная и у вас могут возникать вопросы типа «а что будет, если сделать так?». Рассмотреть все случаи в рамках этого курса я не смогу. Поэтому очень рекомендую вам создавать примеры и на них проверять, как все это работает в том или ином случае. Это поможет вам лучше понять теорию.
Пример
Чтобы понять, зачем нам может понадобиться Dependency Injection и даггер, давайте рассмотрим небольшой пример. В нем мы смоделируем ситуацию, когда создание одного объекта может потребовать создание еще нескольких.
Пусть в нашем приложении есть MainActivity и, в соответствии с паттерном MVP, для него есть презентер. Презентеру для работы нужны будут некие UserController и DataController. Т.е. нам надо будет создать два этих объекта перед тем, как создать презентер. Но для создания двух этих объектов нам, в свою очередь, нужны объекты ApiService и SharedPreferences. А для создания ApiService нужны RestAdapter, RestAdapter.Builder, OkHttpClient и Cache.
В обычной реализации это может выглядеть так:
В MainActivity мы создаем один объект, затем используем его при создании другого, и так далее по цепочке, чтобы в итоге получить презентер. Нам сейчас не важно, какие именно объекты создаются. Главное — это сколько кода может потребоваться написать в MainActivity, чтобы получить результат.
Выглядит это все не очень красиво, а главное — неправильно. Activity ничего не должно знать о кэшах, сервисах, контроллерах и прочем. Activity знает только презентер и должно получать его в готовом виде.
Как вариант — можно весь этот создающий код поместить в сам презентер. Т.е. Activity только создает презентер, а он уже пусть внутри себя создает все остальные объекты. Так сделать можно, но это тоже неправильно, потому что нарушает принцип Dependency Injection: презентер не должен создавать эти объекты, он их должен получать готовыми. Да и написать нормальные тесты для такого презентера будет затруднительно.
Нам нужен какой-то механизм, который умеет сам создавать все необходимые объекты, как мы это делали в коде Activity. Т.е. по цепочке, создает один объект, использует его в конструкторе другого объекта и так далее, пока не получится готовый презентер, который будет торжественно вручен Activity.
Даггер как раз является таким механизмом. При его использовании код в Activity будет выглядеть так:
Разумеется, код создания объектов никуда не исчез. Но он разделен на части и вынесен из Activity в специальные отдельные классы, к которым даггер имеет доступ. В итоге мы просто вызываем метод getMainActivityPresenter, чтобы получить презентер. А даггер под капотом уже сам создаст этот объект и всю необходимую для него иерархию объектов.
Теория
Теперь давайте смотреть, как работает даггер изнутри.
Возьмем все тот же пример с Activity и Presenter. Т.е. когда Activity для своих нужд создает объект Presenter.
Обычая схема создания будет выглядеть так:
Т.е. Activity создает Presenter самостоятельно
При использовании даггера схема будет выглядеть так:
Activity -> Component -> Module -> Presenter
Activity обращается к компоненту (appComponent в примере выше), компонент с помощью модулей создает Presenter (и все остальные необходимые для этого объекты) и возвращает его в Activity.
Модули и компоненты — это два ключевых понятия даггера.
Модули — это просто классы, в которые мы выносим (из Activity) код создания объектов. Обычно каждый модуль включает в себя создание объектов близких по смыслу.
Модуль UserModule будет содержать в себе код создания объектов, связанных с пользователями, т.е. UserController.
Модуль NetworkModule — объекты OkHttpClient и ApiService.
Модуль StorageModule — объекты DataController и SharedPreferences
Компонент — это посредник между Activity и модулями. Когда Activity нужен какой-либо объект, она сообщает об этом компоненту. Компонент знает, какой модуль умеет создавать такой объект, просит модуль создать объект, и передает его в Activity. При этом компонент может использовать другие модули, чтобы создать всю иерархию объектов, необходимую для создания искомого объекта.
Процесс работы даггера можно сравнить с обедом в McDonalds. Т.е. по аналогии со схемой даггера:
Activity -> Component -> Module -> Presenter
схема McDonalds выглядит так:
Клиент -> Кассир -> Производственная линия -> Заказ (Бигмак/Картошка/Кола)
Рассмотрим подробнее шаги этих схем:
McDonalds | Даггер |
Клиент определился, что его заказ будет состоять из бигмака, картошки и колы, и он говорит об этом кассиру | Activity сообщает компоненту, что ему понадобится Presenter |
Кассир ходит по производственной линии и собирает заказ: берет бигмак, наливает колу, насыпает картошку | Компонент использует модули, чтобы создать все необходимые объекты, которые понадобятся для создания Presenter |
Кассир комплектует заказ в пакет или на поднос и выдает его клиенту | Компонент в итоге получает от модулей требуемый объект Presenter и отдает его Activity |
Практика
Теперь на простом примере посмотрим, как создавать модули и компоненты, и как с их помощью Activity будет получать требуемые объекты.
В этом курсе все примеры будут на Kotlin.
Подключение
Для начала подключите плагин kapt. Это делается в самом начале файла build.gradle модуля
Подключить можно так:
Добавьте в раздел dependencies файла build.gradle модуля строки:
Объекты
В качестве объектов, которые мы будем запрашивать от даггера, используем пару классов: DatabaseHelper и NetworkUtils.
Их реализация нам сейчас не важна, оставляем их пустыми.
Предположим, что эти объекты будут нужны нам в MainActivity.
Презентер пока не используем, чтобы не усложнять пример.
Чтобы получить эти объекты с помощью даггера, нам нужно создать модули и компонент.
Модули
Создаем модули, которые будут уметь предоставлять требуемые объекты. Именно в модулях мы и пишем весь код по созданию объектов. Это обычные классы, но с парой аннотаций.
Модуль для DatabaseHelper:
Модуль для NetworkUtils:
Аннотацией @Module мы сообщаем даггеру, что этот класс является модулем. А аннотация @Provides указывает, что метод является поставщиком объекта.
Таким образом мы предоставляем даггеру информацию о том, как создавать объекты DatabaseHelper и NetworkUtils. Нам больше не надо будет самим создавать их. Теперь, где бы они нам не понадобились, мы сможем просто попросить эти объекты у даггера, и он создаст их и предоставит нам.
Технически можно было вполне обойтись и одним модулем. Но логичнее будет разделить объекты на модули по их смыслу и области применения.
Компонент
Модули готовы, теперь создаем компонент. Для этого нам необходимо создать интерфейс:
Данный интерфейс описывает пустой компонент, который пока ничего не умеет.
При компиляции проекта, даггер найдет этот интерфейс по аннотации @Component и сгенерирует класс DaggerAppComponent (Dagger + имя интерфейса), который станет реализацией этого интерфейса. Этот класс и будет потом создавать объекты и возвращать их нам.
После того как даггер создал нам класс компонента, нам необходимо создать экземпляр этого класса. Это можно сделать, например, в Application классе (не забудьте добавить его в манифест):
У класса компонента есть метод create, которым мы создаем его экземпляр.
Можно, кстати, сделать чуть короче:
На этом месте ваша среда разработки, возможно, будет ругаться на класс DaggerAppComponent. Так может происходить, потому что класса DaggerAppComponent пока не существует. Мы описали интерфейс компонента AppComponent, но нам надо скомпилировать проект, чтобы даггер создал этот класс-компонент.
Скомпилируйте проект. В Android Studio это можно сделать через меню Build -> Make Project (CTRL+F9). После того как процесс завершится, класс DaggerAppComponent будет создан в недрах папки build\generated\. Студия теперь знает этот класс и должна предлагать добавить его в import, чтобы в коде не было никаких ошибок.
Чтобы добраться до компонента из MainActivity, мы можем сделать так:
После этого MainActivity сможет получать от компонента все необходимые объекты.
Нам остается рассказать компоненту, какие именно объекты мы хотим от него получать. Для этого мы будем наполнять его интерфейс методами. И вот тут у нас есть два вида методов.
Первый — это обычный и понятный get метод:
Т.е. мы просто описываем метод, который должен вернуть нужный нам объект. Когда даггер будет создавать класс-реализацию (DaggerAppComponent) этого интерфейса, он создаст в нем и реализацию метода getDatabaseHelper. Этот метод будет ходить в модуль StorageModule, создавать объект DatabaseHelper и возвращать его нам.
Второй вид методов посложнее для понимания. Это inject метод.
В этом случае мы не просим компонент создать и вернуть нам конкретный объект. Вместо этого мы просим компонент проверить какие объекты нужны в MainActivity. Компонент создаст эти объекты и передаст их сразу в MainActivity. Такая процедура называется инджект.
Рассмотрим оба способа подробнее.
Get метод
Нам от компонента нужны объекты DatabaseHelper и NetworkUtils. Для этого нам надо просто добавить в интерфейс компонента методы, которые будут возвращать эти объекты:
Создаем два метода. Они могут быть с любым именем, главное — это их возвращаемые типы (NetworkUtils и DatabaseHelper). Они дают понять компоненту, какие именно объекты мы хотим от него получать. При компиляции, даггер проверит, в каком модуле какой объект можно достать, и нагенерит в реализации двух этих методов соответствующий код создания этих объектов.
Список modules — это модули. Здесь нам надо указать модули, в которых компонент сможет найти код создания объектов. В StorageModule он найдет код для создания DatabaseHelper, а в NetworkModule — для NetworkUtils.
В MainActivity мы просто вызываем эти методы компонента, чтобы получить готовые объекты:
Если этот код у вас крэшит с NullPointerException, убедитесь, что добавили App класс в манифест.
Inject метод
У нас в MainActivity сейчас всего два объекта, которые мы получаем от компонента. Но если их будет штук 20, то придется в интерфейсе компонента описать 20 get-методов и в коде MainActivity написать 20 вызовов этих методов. У даггера есть более удобное решение для таких случаев. Мы можем научить компонент не возвращать объекты, а самому наполнять Activity требуемыми объектами. Т.е. мы даем компоненту экземпляр MainActivity, а он смотрит, какие объекты нужны, создает их и сам помещает в соответствующие поля.
Перепишем интерфейс компонента
Вместо пары get-методов мы описываем один inject-метод. Имя может быть любым, главное — это тип его единственного параметра. Мы указываем здесь MainActivity. Тем самым, мы говорим компоненту, что когда мы будем вызывать этот метод и передавать туда экземпляр MainActivity, мы ожидаем, что компонент наполнит этот экземпляр требуемыми объектами.
Аннотациями @Inject мы помечаем поля, которые компонент должен заполнить (инджектить). При вызове метода injectMainActivity компонент создаст объекты DatabaseHelper и NetworkUtils и поместит их в соответствующие поля MainActivity.
Этот механизм можно посмотреть в коде класса (DaggerAppComponent) компонента, который был сгенерирован даггером. Метод injectMainActivity, если поубирать все лишнее, выглядит примерно так:
Разумеется, get-методы и inject-методы могут быть использованы вместе в одном компоненте. Я описывал их отдельно друг от друга только для простоты понимания.
Где и как размещать все эти классы и интерфейсы зависит от вашей архитектуры и является отдельной темой для дискуссии.
Источник