Как управлять внедрением зависимостей с помощью механизма временной области (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» можно здесь.
ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/530024/
Добавить комментарий