Будущих учащихся на курсе "Android Developer. Professional" приглашаем посетить открытый урок на тему "Пишем Gradle Plugin"
А также делимся переводом полезной статьи.
Простой способ использовать внедрение зависимостей в приложениях для Android
Hilt — это новая библиотека для внедрения зависимостей, построенная на основе Dagger. Она позволяет использовать возможности Dagger в приложениях для Android упрощенным способом. В этом руководстве описаны основные функциональные возможности библиотеки и приведено несколько фрагментов кода, которые помогут вам начать использовать Hilt в своих проектах.
Настройка Hilt
Чтобы настроить Hilt в своем приложении, сначала выполните указания из руководства: Установка Gradle Build.
После установки всех необходимых элементов и подключаемых модулей, чтобы использовать Hilt, задайте своему классу Application аннотацию @HiltAndroidApp.
Больше ничего делать не нужно, а также не нужно вызывать Hilt напрямую.
Определение и внедрение зависимостей
При написании кода, в котором используется внедрение зависимостей, есть два основных компонента, которые следует учитывать:
Классы, имеющие зависимости, которые вы собираетесь внедрить.
Классы, которые могут быть внедрены как зависимости.
Они не являются взаимоисключающими: во многих случаях класс одновременно является внедряемым и имеет зависимости.
Как сделать зависимость внедряемой
Чтобы в Hilt сделать объект внедряемым, необходимо указать для Hilt способ создания экземпляра этого объекта. Такие инструкции называются привязками.
Есть три способа определения привязки в Hilt.
Добавить к конструктору аннотацию
@Inject
Использовать
@Binds
в модулеИспользовать
@Provides
в модуле
⮕ Добавление к конструктору аннотации @Inject
У любого класса может быть конструктор с аннотацией @Inject
, что позволяет использовать его в качестве зависимости в любом месте проекта.
⮕ Использование модуля
Два других способа преобразования объектов во внедряемые в Hilt связаны с использованием модулей.
Модуль Hilt можно считать набором «рецептов», указывающих Hilt, как создать экземпляр того или иного объекта, у которого нет конструктора, — например, интерфейса или системной службы.
Кроме того, в тестах любой модуль можно заменить другим модулем. Например, это позволяет легко заменять реализации интерфейсов мок-объектами.
Модули устанавливаются в компонент Hilt, который указывается с помощью аннотации @InstallIn
. Я дам более подробное объяснение ниже.
Вариант 1: использовать @Binds, чтобы создать привязку для интерфейса
Если вы хотите использовать в своем коде OatMilk, когда требуется Milk, создайте абстрактный метод внутри модуля и задайте ему аннотацию @Binds
. Обращаю внимание, чтобы этот вариант работал, сам OatMilk должен быть внедряемым. Для этого его конструктору необходимо задать аннотацию @Inject
.
Вариант 2: создать функцию-фабрику с помощью @Provides
Когда экземпляр нельзя сконструировать напрямую, можно создать поставщик. Поставщик — это функция-фабрика, которая возвращает экземпляр объекта.
В качестве примера можно привести системную службу, скажем ConnectivityManager, которую необходимо получить из контекста.
Объект Context является внедряемым по умолчанию, если задать ему аннотацию @ApplicationContext
либо @ActivityContext
.
Внедрение зависимости
После того как вы сделали нужные зависимости внедряемыми, их можно внедрять с помощью Hilt двумя способами.
Как параметры конструктора
Как поля
⮕ Как параметры конструктора
Если пометить конструктор аннотацией @Inject
, Hilt внедрит все параметры в соответствии с привязками, которые вы определили для этих типов.
⮕ Как поля
Если класс является точкой входа, указанной с использованием аннотации @AndroidEntryPoint
(подробнее об этом рассказано в следующем разделе), внедрены будут все поля, помеченные аннотацией @Inject
.
Поля, помеченные аннотацией @Inject
, должны быть общедоступными. Также удобно пометить их модификатором lateinit, чтобы не пришлось обеспечивать поддержку ими пустого значения, поскольку перед внедрением они имеют исходное значение null
.
Обратите внимание, внедрять зависимости как поля стоит только тогда, когда у класса должен быть конструктор без параметров, например Activity
. В большинстве случаев рекомендуется внедрять зависимости посредством параметров конструктора.
Прочие важные понятия
Точка входа
Помните, я сказал, что во многих случаях класс создается путем внедрения и имеет зависимости, внедренные в него? В некоторых случаях у вас будет класс, который не создается путем внедрения зависимости, но имеет зависимости, внедренные в него. Хорошим примером этого являются активности, которые в стандартной ситуации создаются платформой Android, а не библиотекой Hilt.
Эти классы являются точками входа в график зависимостей Hilt, а Hilt необходимо знать, что у них есть зависимости, требующие внедрения.
⮕ Точка входа Android
Большинство ваших точек входа будут так называемыми точками входа Android:
Activity
Fragment
View
Service
BroadcastReceiver
Если это так, такой точке входа следует задать аннотацию @AndroidEntryPoint
.
⮕ Другие точки входа
Большинству приложений обычно нужны только точки входа Android, но если вы взаимодействуете с библиотеками, не работающими с Dagger, или с компонентами Android, которые пока не поддерживаются в Hilt, то вам может потребоваться создать собственную точку входа для доступа к графику Hilt вручную. Можете ознакомиться с инструкциями по преобразованию произвольных классов в точки входа.
ViewModel
ViewModel — это особый случай: экземпляры этого класса не создаются напрямую, так как они должны создаваться платформой, при этом он также не является точкой входа Android. Вместо этого с классами ViewModel
используют специальную аннотацию @ViewModelInject
, которая позволяет Hilt внедрять в них зависимости, когда они создаются с помощью выражения by viewModels()
. Это похоже на то, как @Inject
работает для других классов.
Если вам требуется доступ к состоянию, сохраняемому в классе ViewModel
, внедрите SavedStateHandle в качестве параметра конструктора, добавив аннотацию @Assisted
.
Чтобы использовать @ViewModelInject
, вам нужно будет добавить еще несколько зависимостей. Дополнительные сведения см. в статье: Интеграции Hilt и Jetpack.
Компоненты
Каждый модуль устанавливается внутри компонента Hilt, который указывается с помощью аннотации @InstallIn
(<компонент>). Компонент модуля используется главным образом для предотвращения случайного внедрения зависимости не в том месте. Например, аннотация @InstallIn
(ServiceComponent.class
) не позволит использовать привязки и поставщики, имеющиеся в соответствующем модуле, внутри активности.
Кроме того, использование привязки можно ограничить пределами компонента, в котором находится модуль. Что приводит меня к…
Области
По умолчанию у привязок нет областей. В приведенном выше примере это означает, что каждый раз, когда вы внедряете Milk, вы получаете новый экземпляр OatMilk. Если добавить аннотацию @ActivityScoped
, область использования привязки будет ограничена пределами ActivityComponent
.
Теперь, когда у модуля есть область действия, Hilt будет создавать только один OatMilk на экземпляр активности. Кроме того, этот экземпляр OatMilk будет привязан к жизненному циклу этой активности — он будет создаваться при вызове onCreate()
активности и уничтожаться при вызове onDestroy()
активности.
В этом случае и milk, и moreMilk будут указывать на один и тот же экземпляр OatMilk. Однако, если у вас несколько экземпляров LatteActivity, у каждого из них будет собственный экземпляр OatMilk.
Соответственно, у других зависимостей, внедренных в эту активность, будет та же область, поэтому они тоже будут использовать тот же экземпляр OatMilk:
Область зависит от компонента, в который установлен ваш модуль. Например, @ActivityScoped
можно применить только к привязкам, находящимся внутри модуля, который установлен внутри ActivityComponent
.
Область также определяет жизненный цикл внедренных экземпляров: в данном случае одиночный экземпляр Milk, используемый Fridge и LatteActivity, создается, когда onCreate()
вызывается для LatteActivity, — и уничтожается в его onDestroy()
. Это также означает, что наш Milk не «переживет» изменение конфигурации, поскольку при этом вызывается onDestroy()
для активности. Преодолеть это можно путем использования области с более длительным жизненным циклом, например @ActivityRetainedScope
.
Список имеющихся областей, компонентов, которым они соответствуют, и жизненных циклов, которым они следуют, приведен в статье: Компоненты Hilt.
Внедрение поставщика
Иногда требуется более прямой контроль над созданием внедренных экземпляров. Например, вам требуется, чтобы один или несколько экземпляров какого-то объекта внедрялись только тогда, когда они нужны, в соответствии с бизнес-логикой. В этом случае можно использовать dagger.Provider.
Внедрение поставщика можно использовать независимо от того, чем является зависимость и как она внедряется. Любой объект, который можно внедрить, можно обернуть в Provider<…>
, чтобы для него использовалось внедрение поставщика.
Фреймворки внедрения зависимостей (такие как Dagger и Guice) обычно применяются в крупномасштабных, сложных проектах. В то же время библиотека Hilt, будучи простой в освоении и настройке, предоставляет все возможности Dagger в пакете, который можно использовать в приложениях любого типа, независимо от размеров кодовой базы.
Если хотите поближе познакомиться с библиотекой Hilt и принципами ее работы, а также узнать о других ее возможностях, которые могут оказаться вам полезными, отправляйтесь на ее официальный веб-сайт, где приведен подробный обзор и справочная документация.
Узнать подробнее о курсе "Android Developer. Professional"
Записаться на открытый урок "Пишем Gradle Plugin"
Прямо сейчас в OTUS стартовала новогодняя распродажа. Скидка распространяется абсолютно на все курсы. Сделайте подарок себе или близким - переходите на сайт и забирайте курс со скидкой. А в качестве бонуса предлагаем зарегистрироваться на абсолютно бесплатные демо-уроки :
ЗАБРАТЬ СКИДКУ