Как управлять внедрением зависимостей с помощью механизма временной области (scope)
Для будущих студентов курса "Android Developer. Professional" подготовили перевод полезной статьи.
Также приглашаем принять участие в открытом вебинаре на тему "Пишем Gradle plugin"
О чем эта статья
Вы узнаете, как с помощью модулей Koin ограничивать область живучести зависимостей, относящихся к конкретному компоненту. Вы также познакомитесь со стандартными областями Koin и способами работы с настраиваемыми областями.
Введение
Разработчики ОС Android не рекомендуют использовать внедрение зависимостей (Dependency Injection, DI (англ.)), если в вашем приложении три экрана или меньше. Но если их больше, лучше применить DI.
Популярный способ реализации DI в Android-приложениях основан на фреймворке Dagger. Но он требует глубокого изучения. Одна из лучших альтернатив этому фреймворку — Koin, библиотека, написанная на чистом Kotlin.
Если вы уже пользовались Dagger или любой другой библиотекой для DI, то, скорее всего, знаете, насколько важен в этом процессе механизм временной области (scope). Он позволяет определять, в каких случаях нужно получать один и тот же зависимый объект, а в каких — новый. Он также помогает освобождать невостребованные ресурсы и память.
Области в Koin
Концепция области в Koin аналогична таковой в Android. Она позволяет, например, ограничить область живучести модели представления (ViewModel) до определенной активности и использовать эту модель во фрагментах, которыми наполняется активность.
Как правило, в Koin три вида временных областей.
single
(одиночный объект) — создается объект, который сохраняется в течение всего периода существования контейнера (аналогично синглтону);factory
(фабрика объектов) — каждый раз создается новый объект, без сохранения в контейнере (совместное использование невозможно);scoped
(объект в области) — создается объект, который сохраняется в рамках периода существования связанной временной области.
Область вида single
при каждом запросе возвращает один и тот же экземпляр, а factory
каждый раз возвращает новый экземпляр.
Настраиваемая область
Стандартные области single
и factory
в Koin живут в течение жизненного цикла модулей Koin. Однако в реальных сценариях использования требования к внедрению зависимостей будут отличаться.
Зависимости обычно нужны только на определенный период времени. Например, репозиторий OnBoardRepository
в Android-приложении требуется только при регистрации пользователя. Как только пользователь авторизуется, удержание этого репозитория в памяти станет лишней тратой ресурсов.
Чтобы добиться нужного поведения в Koin, можно воспользоваться API для работы с временными областями. В модуле Koin можно создать область со строковым квалификатором и объявить зависимости внутри нее с помощью уникальных квалификаторов. Давайте сделаем это шаг за шагом.
Шаг 1
Сначала создадим модуль, объявим пустую область и присвоим ей имя. В данном случае мы дали области имя CustomScope
. Вы можете назвать ее в соответствии со своими требованиями. Вот как это выглядит:
creating custom koin scope
Шаг 2
Следующим шагом объявим необходимые зависимости с использованием областей single
и factory
в соответствии с требованиями проекта. Ключевой момент заключается в присвоении областям уникальных квалификаторов. Вот так:
dependencies inside custom scopes
Шаг 3
Мы закончили настройку в модуле Koin. На этом шаге нам нужно создать область из того компонента, из которого мы импортируем нужные зависимости. Обычно области создаются из Android-компонентов, например Activity, Fragment и т. п.
Чтобы создать область, сначала нам нужно получить существующий экземпляр компонента Koin, а затем вызвать функцию createScope, передав ей идентификатор и имя области.
val stringQualifiedScope = getKoin().createScope(
"ScopeNameID", named("CustomeScope"))
Получив CustomScope как значение параметра имени, Koin будет искать область, которую мы объявили под этим именем в модулях Koin. ScopeNameID
— это идентификатор, который мы применяем, чтобы отличать одну область от другой. Он используется на внутреннем уровне в качестве ключа для поиска этой области.
Если вы обращаетесь к областям или создаете их из нескольких Android-компонентов, то вместо функции createScope
рекомендуется использовать функцию getOrCreateScope
. Из названий этих функций очевидно, что они делают.
Шаг 4
Наконец, создаем экземпляр зависимости, которую хотим использовать. Мы сделали это с помощью созданной нами области. Вот что получилось.
val sampleClass = stringQualifiedScope.get<SampleClass>(
qualifier = named("scopedName"))
scopedName
и factoryName
— это квалификаторы, которые мы объявили внутри модуля Koin на шаге 2.
Шаг 5
Чтобы избавиться от зависимостей, созданных посредством stringQualifiedScope, в частности sampleclass
, необходимо вызвать функцию close
. Например, если вы хотите избавиться от созданных в этой области зависимостей при уничтожении активности, то нужно вызвать функцию close
в рамках соответствующего метода onDestroy
. Вот так:
override fun onDestroy() {
super.onDestroy()
stringQualifiedScope.close()
}
Koin-Android
Выше описан общий подход к ограничению живучести зависимостей определенной временной областью. Его можно применять на любой платформе, поддерживаемой Koin. Будучи Android-разработчиком, теперь я бы хотел совместить механизмы области Koin и области жизненного цикла, чтобы свести к минимуму работу, которую мне приходится делать при каждом создании активности.
Для этого необходимо импортировать библиотеки Koin-Android. Добавьте следующие строки в узел dependencies файла build.gradle
уровня приложения:
// Koin for Android
implementation "org.koin:koin-android:$koin_version"
// Koin Android Scope features
implementation "org.koin:koin-android-scope:$koin_version"
Модули Koin-Android
Теперь с целью сократить шаблонный код мы хотим, например, автоматически закрывать область в рамках метода onDestroy
компонента Android. Это можно сделать путем привязки Koin к импорту зависимостей посредством lifecyclescope
.
Для начала необходимо создать в модуле Koin область для зависимостей с компонентами Android. Как это сделать:
val androidModule = module {
scope<SampleActivity> {
scoped { SampleClass() }
}
}
scoping dependency with android activity
Затем нужно выполнить внедрение зависимости в активности при помощи lifecyclescope
:
val sampleClass : SampleClass by lifecycleScope.inject()
Это позволит закрывать область при уничтожении активности, избавив нас от ручных операций. Вот так выглядит наш код:
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
if (event == Lifecycle.Event.ON_DESTROY) {
scope.close()
}
}
}
Такой подход позволит автоматизировать работу по созданию областей, назначению квалификаторов и уничтожению областей. Кажется, что это простые действия, их можно выполнить и вручную. Но повторяющуюся работу важно автоматизировать, и по мере развития приложения это становится очевидно.
Дополнительные материалы
Подробнее о внедрении зависимостей читайте в другой статье о библиотеке Koin.
Чтобы узнать больше о Kotlin, прочитайте вторую часть статьи о программировании на Kotlin (продвинутый уровень).
Читайте о корутинах и других расширенных функциях Kotlin в статье о том, как научиться комбинировать потоки в Kotlin.
На этом все. Надеюсь, вы узнали что-то полезное для себя. Спасибо за внимание!
Подробнее о курсе "Android Developer. Professional". Записаться на открытый урок "Пишем Gradle plugin" можно здесь.