Koin android что это
Перейти к содержимому

Koin android что это

  • автор:

Koin — библиотека для внедрения зависимостей, написанная на чистом Kotlin

Для будущих студентов курса «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)

  • 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( qualifier = named("scopedName"))

scopedName и factoryName — это квалификаторы, которые мы объявили внутри модуля Koin на шаге 2.

Шаг 5

Чтобы избавиться от зависимостей, созданных посредством stringQualifiedScope, в частности sampleclass , необходимо вызвать функцию close . Например, если вы хотите избавиться от созданных в этой области зависимостей при уничтожении активности, то нужно вызвать функцию close в рамках соответствующего метода onDestroy . Вот так:

override fun onDestroy()

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"

Теперь с целью сократить шаблонный код мы хотим, например, автоматически закрывать область в рамках метода onDestroy компонента Android. Это можно сделать путем привязки Koin к импорту зависимостей посредством lifecyclescope .

Для начала необходимо создать в модуле Koin область для зависимостей с компонентами Android. Как это сделать:

val androidModule = module < scope < 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» можно здесь.

Monitor your Koin application

A framework to help you build any kind of Kotlin & Kotlin Multiplatform application, from Android mobile, Multiplatform apps to backend Ktor server applications Koin is developed by Kotzilla and open-source contributors

Latest News

Making your Kotlin development easy and productive

Koin is a smart Kotlin dependency injection library to keep you focused on your app, not on your tools.

Koin gives you simple tools and API to let you build, assemble Kotlin related technologies into your application and let you scale your business with easyness.

  • Define Modules
  • Start Koin
  • Start on Android
  • Inject on Android
  // Given some classes class MyRepository() // Inject via constructor class MyPresenter(val repository : MyRepository)  // just declare it val myModule = module   singleOf(::MyPresenter) singleOf(::MyRepository) > 
  fun main()    // Just start Koin  startKoin   // load modules modules(myModule) > > 
  class MyApplication : Application()   override fun onCreate()   super.onCreate() // start Koin!  startKoin   // declare used Android context androidContext(this) // declare modules modules(myModule) > > > 
  class MyActivity : Application()   // Just inject your dependency val myPresenter : MyPresenter by inject() > 

Ready for Android

Thanks to the Kotlin language, Koin extends the Android platform and provides new features as part of the original platform.

Koin provides easy and powerfull API to retrieve your dependencies anywhere in Android components, with just using by inject() or by viewModel()

Powering Kotlin Multiplatform

Sharing code between mobile platforms is one of the major Kotlin Multiplatform use cases. With Kotlin Multiplatform Mobile, you can build cross-platform mobile applications and share common code between Android and iOS.

Koin provides multiplatform dependency injection and help build your components accross your native mobile applications, and web/backend applications.

Performances and Productivity

Koin is a pure Kotlin framework, designed to be straight forward in terms of usage and execution. It easy to use and doesn’t impact your compilation time, nor require any extra plugin configuration.

Talks and Videos

You can follow the latest news from the Koin project team on Twitter, to follow conferences and talks.

Take a tour on the Kotzilla Blog to read latest news and videos about Koin project.

Open Source and Community Driven, Supported by Kotzilla

Arnaud Giuliani created and released Koin in 2017 and is still maintaining it with the help of open-source contributors.

In 2018, Koin is one of the most trending Kotlin framework . Today, the project is supported by contributions from individuals and companies around the world.

Koin is being used in thousands of apps in the world, but it’s likely you’ve already used it in one of these apps:

Let’s Try Koin!

Our Special Tech Sponsors ❤️

Our OpenCollective Sponsors

  • Core Reference
  • Android Reference

Koin: Простой и легковесный фреймворк для внедрения зависимостей

Принцип внедрения (инжектирования) зависимостей становится все более неотъемлемой частью процесса разработки. Без него сложно представить себе достижение желанного разделения обязанностей в коде или обеспечение должного уровня тестируемости.

В то же время, хотя Spring Framework и является широко распространенным выбором, он далеко не всем подходит. Некоторым было бы предпочтительнее использовать более простые и легковесные фреймворки с продвинутой поддержкой асинхронных операций ввода-вывода. Другие были бы признательны за статическое разрешение зависимостей для более быстрого запуска приложения.

Конечно, существует фреймворк Guice, но если мы хотим иметь что-то более подходящее для языка Kotlin, стоит обратить внимание на Koin. Этот легковесный фреймворк предоставляет возможности для внедрения зависимостей через DSL, что является нетривиальной задачей в случае Java-ориентированного Guice.

Помимо того, что Koin обладает возможностью выразительного объявления зависимостей между компонентами в коде, он также имеет интегрированную поддержку для известных приложений, разрабатываемых на языке программирования Kotlin. Конкретно, он облегчает взаимодействие и интеграцию с популярными фреймворками и платформами, такими как Ktor для создания серверных приложений и Android-платформа для мобильных приложений. Важно отметить, что Koin создан без использования «магии» — он не генерирует прокси-объектов, не оперирует рефлексией и не предпринимает эвристических попыток найти подходящую реализацию для удовлетворения нашей зависимости. Вместо этого он делает только то, что ему явно указано, и не обладает «автосвязыванием», как в Spring.

В этом руководстве мы рассмотрим основы Koin и подготовим почву для более глубокого и продвинутого использования фреймворка.

2. Как начать работу с Koin

Как и в случае с любой библиотекой, нам необходимо добавить некоторые зависимости. Все зависит от конкретного проекта: для успешной работы нам потребуется либо стандартная настройка с использованием Kotlin (в таком случае, это будет так называемый «ванильный» (чистый) Kotlin-проект), либо у нас может быть проект, основанный на фреймворке Ktor, который позволяет разрабатывать серверные приложения. Если мы используем систему сборки Gradle с Kotlin DSL, потребуется указать две зависимости в нашем проекте. Эти зависимости нужны для того, чтобы библиотека Koin корректно функционировала в режиме «ванильного» Kotlin-проекта, то есть в обычном режиме без специфических фреймворков:

val koin_version = "3.2.0-beta-1" implementation("io.insert-koin:koin-core:$koin_version") testImplementation("io.insert-koin:koin-test:$koin_version")

Если мы планируем использовать библиотеку JUnit 5 для написания и запуска тестов, нам необходимо явно указать, что наш проект зависит от этой библиотеки. Чтобы обеспечить работу JUnit 5 в нашем проекте, мы должны указать его зависимость в файле настроек нашего проекта:

testImplementation("io.insert-koin:koin-test-junit5:$koin_version")

Аналогично, для версии с использованием фреймворка Ktor, чтобы осуществить интеграцию с Koin, имеется специальная зависимость (она заменяет основную зависимость (применяемую по умолчанию) в Ktor-приложениях):

implementation("io.insert-koin:koin-ktor:$koin_version")

Вот и всё, что нам нужно, чтобы начать применение библиотеки Koin. Мы будем использовать последнюю бета-версию, чтобы руководство оставалось актуальным в течение длительного времени.

3. Модули и Определения

Давайте начнем наше путешествие, создав реестр для нашего паттерна DI (Dependency Injection, Внедрение зависимостей). Мы будем регистрировать зависимости в этом реестре, чтобы впоследствии инжектировать их в различные части нашего приложения.

3.1. Модули

Модули содержат объявления зависимостей между сервисами, ресурсами и репозиториями. Они позволяют организовать эти зависимости и предоставить информацию о том, какие компоненты могут быть доступны для инжекции в другие части приложения. Может быть несколько модулей, по одному для каждого семантического поля. При создании контекста Koin, все модули передаются в функцию modules() , о которой будет рассказано позже.

Каждый модуль может зависеть от определений, которые находятся в других модулях. Koin выполняет вычисление зависимостей в модулях “лениво”, то есть он не создает или не разрешает зависимости до тех пор, пока они действительно не потребуются во время выполнения программы. Это позволяет избегать избыточных вычислений и создания ненужных объектов, что повышает эффективность и производительность приложения. Определения могут даже образовывать циклы зависимостей. Такое обычно возникает в сложных приложениях, где различные компоненты взаимодействуют между собой и зависят от результатов работы друг друга.

В Koin, благодаря ленивому вычислению зависимостей, циклические зависимости не вызывают проблем. Тем не менее, все же имеет смысл избегать создания семантических циклов, так как в дальнейшем такую структуру поддерживать сложно.

Для создания модуля мы должны использовать функцию module <> :

class HelloSayer() < fun sayHello() = "Hello!" >val koinModule = module < single < HelloSayer() >>

Модули могут быть включены друг в друга:

val koinModule = module < // Some configuration >val anotherKoinModule = module < // More configuration >val compositeModule = module

Более того, они могут образовывать структуру в виде сложного дерева без значительного ущерба для производительности. includes() объединит в одну общую коллекцию все определения (выполнит операцию «сплющивания» (flatten)) и они станут доступными на одном уровне. Это позволяет избежать необходимости долгого иерархического доступа к определениям внутри вложенных модулей и сможет упростить их использование в других частях приложения.

Функция includes() в библиотеке Koin объединяет не только определения (зависимости), но также и сами компоненты (сервисы, ресурсы и другие элементы), которые были объявлены в разных модулях.

3.2. Определения Singleton и Factory

Для создания определения чаще всего приходится использовать единственную функцию single<> , где T — это тип, который должен соответствовать запрашиваемому типу в последующих вызовах get() :

single

single <> создаст определение для объекта-синглтона и каждый раз, когда вызывается метод get() , будет возвращать один и тот же экземпляр (инстанс) этого объекта.

Еще один способ создания синглтона (singleton) — новая фича singleOf() в версии 3.2.. Этот подход основан на двух наблюдениях.

Во-первых, большинство классов Kotlin имеют только один конструктор. Благодаря значениям по умолчанию, им не требуются несколько конструкторов для поддержки различных сценариев использования, как в Java.

Во-вторых, большинство определений не имеют альтернативных вариантов. В старых версиях Koin это приводило к появлению таких определений, как:

single

Следовательно, вместо этого мы можем указать конструктор, который хотим вызвать:

class BackLoop(val dependency: Dependency) val someModule = module

Данный подход основан на упрощении процесса определения синглтонов, благодаря чему код становится более лаконичным и понятным.

Еще одним вариантом подхода является функция factory <> , используемая для определения зависимости, которая будет создаваться новым экземпляром каждый раз при вызове get() для этой зависимости:

factory

Если мы не объявим зависимость при помощи single <> c параметром createdAtStart = true (т.е. сразу при старте приложения), то лямбда-выражение (*creator lambda) будет выполняться только в том случае, если какой-либо компонент KoinComponent явно запросит эту зависимость. (*Лямбда-выражение указывает на функцию, которая определяет способ создания экземпляра зависимости при использовании фреймворка Koin).

3.3. Варианты определений

Важно понимать, что каждое определение — это лямбда. Это означает, что хотя часто определение может быть простым вызовом конструктора, оно не ограничивается только этим:

fun helloSayer() = HelloSayer() val factoryFunctionModule = module < single < helloSayer() >>

Кроме того, определение может иметь параметр:

module < factory < (rumour: String) ->RumourSource(rumour) > >

В случае синглтона, первый вызов создаст экземпляр, и все последующие попытки передать параметр будут проигнорированы:

val singleWithParamModule = module < single < (rumour: String) ->RumourSource(rumour) > > startKoin < modules(singleWithParamModule) >val component = object : KoinComponent < val instance1 = get < parametersOf("I've seen nothing") >val instance2 = get  < parametersOf("Jane is seeing Gill") >> assertEquals("I've heard that I've seen nothing", component.instance1.tellRumour()) assertEquals("I've heard that I've seen nothing", component.instance2.tellRumour())

В случае фабрики (factory), каждое внедрение (injection) будет инстанцироваться со своим параметром, как и ожидалось:

val factoryScopeModule = module < factory < (rumour: String) ->RumourSource(rumour) > > startKoin < modules(factoryScopeModule) >// Same component instantiation assertEquals("I've heard that I've seen nothing", component.instance1.tellRumour()) assertEquals("I've heard that Jane is seeing Gill", component.instance2.tellRumour())

Это означает, что фабрика создает новые экземпляры зависимостей с каждым запросом и передает необходимые параметры, чтобы каждая зависимость была индивидуальной и могла быть настроена с учетом конкретных требований.

Еще один прием связан с тем, как определить несколько объектов одного типа. Надо создать несколько различных экземпляров одного и того же класса или типа, но с разными параметрами или настройками. Это на самом деле сделать очень просто:

val namedSources = module < single(named("Silent Bob")) < RumourSource("I've seen nothing") >single(named("Jay")) < RumourSource("Jack is kissing Alex") >>

У нас есть несколько объектов одного и того же типа RumourSource, но с разными именами – «Silent Bob» и «Jay».

В этом коде мы определяем два разных экземпляра RumourSource с помощью функции single . Особенность заключается в использовании функции named , которая позволяет нам давать имена для определенных экземпляров.

В первой строчке мы создаем экземпляр RumourSource с именем «Silent Bob» и задаем ему сообщение «Я ничего не видел». Во второй строчке – экземпляр с именем «Jay» и сообщением «Джек целуется с Алекс».

Теперь, когда мы инжектируем эти экземпляры в другие компоненты нашего приложения, то сможем легко различать их по имени. Например, в коде мы запросим экземпляр с именем «Silent Bob» или «Jay» для дальнейшего использования.

Таким образом, этот прием позволяет нам создавать и инжектировать несколько разных экземпляров одного типа, различая их по именам, чтобы удовлетворить разные потребности и сценарии нашего приложения.

4. Koin-компоненты

Определения из модулей используются в KoinComponents . Класс, реализующий интерфейс KoinComponent , в некоторой степени аналогичен Spring @Component. Он Он связан с глобальным экземпляром Koin , который создается при инициализации приложения, и служит точкой входа в дерево объектов, описанных в модулях:

class SimpleKoinApplication : KoinComponent

По умолчанию ссылка на экземпляр Koin является неявной и использует GlobalContext , однако этот механизм может быть переопределен в некоторых случаях. Например, в случае, если нам нужно иметь несколько изолированных контекстов Koin в рамках одного приложения.

Мы должны инстанцировать компоненты Koin обычным образом, через их конструкторы, а не путем инжектирования их в модуль. Такова рекомендация авторов библиотеки: вероятно, инжектирование компонентов в модули приводит к снижению производительности или бесконечной рекурсии, когда зависимости циклично вызывают друг друга.

Используя обычные конструкторы для создания компонентов, мы имеем больше контроля над процессом инстанцирования и можем явно указать, какие зависимости должны быть созданы и как они должны взаимодействовать. Это обеспечивает более надежное и предсказуемое поведение приложения, а также улучшает производительность и общую структуру кода.

4.1. Немедленное вычисление в сравнении с ленивым

Объект, реализующий интерфейс KoinComponent , обладает способностью использовать методы inject() и get() для получения зависимостей:

class SimpleKoinApplication : KoinComponent

Внедрение (инжектирование) зависимости с использованием метода inject() означает, что зависимость не создается сразу при инициализации объекта, а откладывается до момента первого обращения к ней. Для этого используется ключевое слово by вместе с методом inject() . Создается делегат — специальный объект, который отвечает за ленивое вычисление зависимости. Он будет выполнен при первом обращении к зависимости.

В отличие от ленивого вычисления ( inject() ), метод get() позволяет получить зависимость немедленно, без ожидания. Когда объект вызывает метод get() для определенной зависимости, он сразу получает непосредственно созданный экземпляр этой зависимости.

Таким образом, использование методов inject() и get() дает разработчикам гибкость в управлении зависимостями, позволяя выбирать между ленивым и немедленным вычислением с учетом конкретных требований приложения.

5. Экземпляр Koin

Для активации всех наших определений нам необходимо создать экземпляр Koin . Его можно сделать и зарегистрировать в GlobalContext , и он будет доступен во время работы (рантайма) всего приложения. Либо мы сформируем автономный (standalone) экземпляр Koin и управлять ссылкой на него будем самостоятельно.

Другими словами, мы делаем экземпляр Koin , который существует отдельно от какого-либо глобального контекста, и тогда мы сами ответственны за управление данным экземпляром.

Когда мы создаем «standalone» экземпляр Koin , то осуществляем это с помощью функции koinApplication<> , которая позволяет определить модули и другие настройки:

val app = koinApplication

Этот экземпляр Koin представляет собой наше приложение или «стартовую точку» для управления зависимостями.

Мы должны сохранить ссылку на приложение (экземпляр Koin , созданный с помощью koinApplication<> ) в переменной app и использовать его позднее для инициализации наших компонентов.

Важно заметить, что создание экземпляра Koin с помощью koinApplication<> еще не инициализирует наши компоненты. Это лишь создает конфигурацию Koin . Для того чтобы начать использовать наши зависимости, мы должны вызвать startKoin <> , передавая ему модули приложения.

Функция startKoin <> создает глобальный экземпляр Koin , который будет доступен для использования в нашем коде:

startKoin

Этот экземпляр предоставляет доступ к зависимостям, определенным в наших модулях.

Вместе с тем, стоит отметить, что Koin имеет определенные предпочтения в отношении некоторых фреймворков. Один из таких примеров — Ktor. У него есть свой способ инициализации конфигурации Koin. Давайте поговорим о настройке Koin как в «ванильном» Kotlin-приложении, так и в веб-сервере Ktor.

5.1. Базовое ванильное приложение с Koin

Для начала работы с базовой конфигурацией Koin необходимо создать экземпляр Koin :

startKoin

Эта функция может быть вызвана только один раз за весь жизненный цикл JVM (Java Virtual Machine). Наиболее важной ее частью является вызов modules() , в котором загружаются все определения. Однако мы можем загрузить дополнительные модули и некоторые выгрузить позже с помощью loadKoinModules() и unloadKoinModules() .

Давайте рассмотрим другие вызовы внутри лямбды startKoin <> . Это logger() , различные *properties() и вызов createEagerInstances() . Первые две из функций, а именно logger() и *properties() , требуют отдельных пояснений, так что им следует посвятить отдельный раздел.

Третья функция, о которой идет речь, createEagerInstances() , позволяет явно создавать и инициализировать те самые определенные синглтоны при запуске приложения (с аргументом createdAtStart = true ), даже если они еще не были явно запрошены.

5.2. Базовый Ktor-сервер с Koin

Для Ktor-сервера Koin — это всего лишь еще одна установленная фича. Она играет роль вызова startKoin <> :

fun Application.module(testing: Boolean = false) < koin < modules(koinModule) >>

После этого класс Application получает функциональность KoinComponent и может inject() (инжектировать) зависимости:

routing < val helloSayer: HelloSayer by inject() get("/") < call.respondText("$, world!") > >

5.3. Изолированный (standalone) экземпляр Koin

Может было бы разумным не использовать глобальный экземпляр Koin для SDK и библиотек. Для достижения этой цели, мы можем использовать функцию koinApplication <> чтобы создать изолированный экземпляр Koin-контейнера и сохранить на него ссылку:

val app = koinApplication

Затем нам нужно переопределить часть стандратной функциональности KoinComponent :

class StandaloneKoinApplication(private val koinInstance: Koin) : KoinComponent < override fun getKoin(): Koin = koinInstance // other component configuration >

После этого мы сможем инстанцировать компоненты во время выполнения (рантайма) с одним дополнительным аргументом — экземпляром Koin :

StandaloneKoinApplication(app.koin).invoke()

6. Логирование и Свойства

Теперь давайте поговорим о тех функциях logger() и properties() в настройке startKoin <> .

6.1. Логгер Koin

Чтобы упростить поиск проблем в настройке, мы можем включить логгер Koin. Фактически, он и так всегда включен, но по умолчанию используется его имплементация EmptyLogger . Мы можем изменить ее на PrintLogger , чтобы просматривать логи Koin в стандартном выводе:

startKoin

Либо, в качестве альтернативы, можно реализовать свой Logger . Если мы используем Ktor, Spark или Android-версию Koin, то есть возможность применить их логгеры: SLF4JLogger или AndroidLogger .

6.2. Свойства

Koin также может использовать свойства из файла (обычно это файл koin.properties , который располагается по умолчанию в папке classpath:koin.properties , следовательно, в нашем проекте этот файл должен быть в src/main/resources/koin.properties ), из переменных системного окружения и непосредственно из переданной карты (мапы):

startKoin

Затем в модуле мы можем получить доступ к этим свойствам с помощью методов getProperty() :

val initByProperty = module < single < RumourSource(getProperty("rumour", "Some default rumour")) >>

7. Тестирование приложений Koin

Koin также предоставляет достаточно развитую инфраструктуру тестирования. Реализуя интерфейс KoinTest , мы предоставляем нашему тесту функциональность KoinComponent и даже больше:

class KoinSpecificTest : KoinTest < @Test fun `when test implements KoinTest then KoinComponent powers are available`() < startKoin < modules(koinModule) >val helloSayer: HelloSayer = get() assertEquals("Hello!", helloSayer.sayHello()) > >

7.1. Мокирование (Mocking) определений Koin

Простой способ мокирования или иной замены сущностей, объявленных в модулях, — это создание специального ситуативного модуля на лету при запуске Koin в тесте:

startKoin < modules( koinModule, module < single < RumourSource("I know everything about everyone!") >> ) >

Другой способ — использовать расширения JUnit 5 для startKoin <> и мокирования:

@JvmField @RegisterExtension val koinTestExtension = KoinTestExtension.create < modules( module < single < RumourSource("I know everything about everyone!") >> ) > @JvmField @RegisterExtension val mockProvider = MockProviderExtension.create < clazz ->mockkClass(clazz) >

После регистрации этих расширений мокирование не обязательно должно быть в специальном модуле или в одном месте. Любой вызов declareMock <> создаст подходящий мок:

@Test fun when_extensions_are_used_then_mocking_is_easier() < declareMock < every < tellRumour() >returns "I don't even know." > val mockedTeller: RumourTeller by inject() assertEquals("I don't even know.", mockedTeller.tellRumour()) >

Мы можем использовать любую библиотеку или подход при мокировании, так как Koin специально для этого фреймворк не определяет.

7.2. Проверка модулей Koin

Koin также предоставляет инструменты для проверки конфигурации модуля и выявления всех возможных проблем с инжекцией, с которыми мы можем столкнуться в процессе рантайма. Сделать это очень просто: мы должны вызвать checkModules() внутри конфигурации KoinApplication , либо проверить список модулей с помощью checkKoinModules() :

koinApplication

Проверка модулей имеет свой DSL. Этот язык позволяет мокировать некоторые из значений в модуле или предоставлять альтернативное значение. Он также обеспечивает передачу параметров, которые могут потребоваться модулю для инстанцирования:

koinApplication < modules(koinModule, module < single < RumourSource() >>) checkModules < withInstance() withParameter  < "Some param" >> >

8. Заключение

В этом руководстве мы внимательно изучили библиотеку Koin и научились ее применять.

Koin создает корневой контекстный объект, который содержит настройки для создания зависимостей и специфические параметры для этих зависимостей. Для синглтон-объектов он также сохраняет ссылки на экземпляры этих зависимостей.

Это своего рода хранилище, где определения зависимостей (singleton, factory и т. д.) объединяются вместе и могут быть запрошены из вашего кода.

Зависимости могут быть описаны в виде модулей.

Вы можете создавать и конфигурировать зависимости внутри отдельных модулей. Каждый модуль содержит набор определений, которые описывают, какие зависимости будут созданы, какие компоненты будут предоставлены и как они будут связаны вместе.

Такой подход помогает разделять функциональность вашего приложения на логические блоки, что упрощает понимание и управление зависимостями. Это также улучшает переиспользование кода, облегчает тестирование и поддержку, так как каждый модуль может быть независимо конфигурирован и заменен при необходимости.

Модули предоставляют описание зависимостей, которые загружаются в функцию-создатель (creator function) объекта Koin . Они описывают, как создавать зависимости с помощью функций-продюсеров (producer functions), которые часто представляют собой простые вызовы конструкторов.

Модули могут зависеть друг от друга, и отдельные определения способны иметь различные области видимости (scopes), такие как синглтон (singleton) и фабрика (factory). Области видимости (scopes) в контексте Koin определяют, как долго будет существовать созданный объект зависимости и каким образом он будет создаваться. Например, синглтон означает, что объект создается один раз и сохраняется для повторного использования. Все последующие запросы на этот объект будут возвращать один и тот же экземпляр. Фабрика, напротив, создает новый экземпляр каждый раз, когда запрашивается зависимость. Определения также способны содержать параметры, которые позволяют нам внедрять данные, специфичные для окружения, в дерево объектов. Это означает, что мы можем передавать конкретные значения или настройки в конструкторы зависимостей в момент их создания. Таким образом, мы в состоянии адаптировать поведение наших зависимостей в соответствии с текущим окружением или требованиями приложения.

Для доступа к зависимостям нам нужно пометить один или несколько объектов как KoinComponent. После этого мы можем объявлять поля в таких объектах с использованием делегатов inject() или же производить их немедленную инициализацию с помощью get() .

Во время создания Koin мы можем инжектировать параметры из файла, среды и программно созданной Map (карты). Можно использовать все три способа или любое их подмножество. В объекте Koin эти свойства хранятся в едином реестре, так что преимущество получает последнее загруженное определение свойства.

То же самое относится и к модулям: в рамках Koin вы можете переопределять определения зависимостей, если это необходимо. Если есть несколько определений для одной и той же зависимости, то будет использоваться последнее определение, которое было объявлено.

Другими словами, последний раз заданное определение зависимости перезаписывает предыдущие определения. Это позволяет вам динамически изменять конфигурацию зависимостей в разных частях вашего приложения. Например, вы можете переопределить определение в тестовом модуле, чтобы заменить реальную зависимость на мок-объект во время тестирования.

Koin предоставляет возможности для тестирования как функциональности, так и самой конфигурации Koin.

Как обычно, весь наш код доступен на GitHub.

Материал подготовлен в преддверии старта онлайн-курса «Kotlin Backend Developer. Professional». Недавно в рамках курса прошел открытый урок на тему применимости Kotlin в различных направлениях разработки: Multimedia, ML, 3D/VR, Frontend, IoT/Robotics, Blockchain. Если интересно, запись занятия можно посмотреть по ссылке.

Релиз Koin 1.0.0✨

Так, так, так… вот оно! Уважаемые пользователи Koin, настал момент релиза нашей первой стабильной версии Koin. Спустя чуть больше года после начальной версии, мы вернулись с крутыми фичами, которые упростят процесс разработки на Kotlin и внедрение зависимостей. Поехали. ��

Установка Koin

Версия Koin 1.0.0 доступна на Jcenter. Как обычно, обновите свой скрипт Gradle, указав новый номер версии. Ниже представлен полный список проектов Koin:

// Koin core features
compile "org.koin:koin-core:1.0.0"
compile "org.koin:koin-core-ext:1.0.0"
compile "org.koin:koin-java:1.0.0"
testCompile "org.koin:koin-test:1.0.0"
// Koin for Android
compile "org.koin:koin-android:1.0.0"
compile "org.koin:koin-android-scope:1.0.0"
compile "org.koin:koin-android-viewmodel:1.0.0"
// AndroidX
compile "org.koin:koin-androidx-scope:1.0.0"
compile "org.koin:koin-androidx-viewmodel:1.0.0"
// Koin for Spark Kotlin
compile "org.koin:koin-spark:1.0.0"
// Koin for Ktor Kotlin
compile "org.koin:koin-ktor:1.0.0"

Ссылка на гайд по установке с помощью Gradle.

Обратите внимание на проект koin-core-ext , в котором собраны расширенные и экспериментальные фичи (бывший koin-reflect )

Koin DSL��

Koin это первый DSL фреймворк для внедрения зависимостей. Для объявления компонентов, вам нужно знать всего 4 слова:

  • module — объявляет модуль, т.е. пространство для сбора всех ваших определений компонентов.
  • single — объявляет синглтон определения данного типа. Koin хранит только один экземпляр этого определения.
  • factory — объявляет фабричное определение данного типа. Koin создаёт новый экземпляр, каждый раз.
  • get — разрешает компонентные зависимости.

single и factory ключевые слова, которые помогают построить компонент с помощью лямбда-выражения. В этом выражении можно использовать функцию get() для получения необходимой зависимости из контейнера Koin.

Мы создаём Koin «модули» и объявляем «single» или «factory» экземпляры определений, вот и всё. Ниже, простой пример Koin модуля с использованием Koin 1.0:

и мощный функционал для Android��

Много было сделано для работы с Android! Проекты были переименованы, благодаря их функционалу. Теперь у нас есть следующие Android проекты:

// Koin for Android
compile "org.koin:koin-android:~"
// Koin Android Scope feature
compile "org.koin:koin-android-scope:~"
// Koin Android ViewModel feature
compile "org.koin:koin-android-viewmodel:~"

Мы отказались от koin-android-architecture и koin-androidx.

Новая фича перенесённая из koin-android-scope , помогает привязать жизненный цикл компонента Android к Koin scope. В конце жизненного цикла, Koin закрывает ассоциированный scope. Функция bindScope привяжет Koin scope к текущему жизненному циклу.

Фича ViewModel в Koin, предоставляется проектом koin-android-viewmodel и преследует ту же цель: облегчить внедрение зависимостей для компонентов Android Architecture ViewModel.

Объявляйте класс ViewModel с помощью ключевого слова viewModel (доступно в формате API билдера, чтобы не писать конструктор). Используйте это в Activity или Fragment с помощью by viewModel() или getViewModel().

И последнее большое новшество: starKoin() отныне не требует запуска из класса Application. Функции нужен только экземпляр Context , чтобы работать и запускаться из любого Android класса:

startKoin(androidContext, appModules)

DSL функции androidApplication() и androidContext() позволяют восстановить экземпляры Application и Context .

AndroidX Ready

Для тех, кто хочет протестировать новую package-систему Android, мы подготовили версию проектов для AndroidX. Вот они:

// Koin AndroidX Scope feature
compile "org.koin:koin-androidx-scope:1.0.0"
// Koin AndroidX ViewModel feature
compile "org.koin:koin-androidx-viewmodel:1.0.0"

Функции те же, что и в стандартном пакете, но с новыми пакетами AndroidX.

Прочие изменения⚙️

Для koin-ktor и koin-spark теперь можно использовать koin-logger-slf4j логгер, чтобы облегчить себе логирование, с предпочитаемой имплементацией (logback, log4j …).

  • Расширение Ktor было добавлено в классы Route и Routing.
  • Теперь вы можете использовать Koin внутри Ktor, с помощью installKoin() и сохранить совместимость с автоматической перезагрузкой.
  • SparkJava понадобится, чтобы расширить интерфейс SparkController, если вы захотите объявить контроллер.

Переход с версии Koin 0.9.x��

Для тех, кто переходит со старой версии Koin, у нас есть страничка с помощью: https://insert-koin.io/docs/1.0/quick-references/upgrade/

Проекты для начала работы. На Github��

Все проекты, чтобы начать работу, доступны на Github. Их можно скачать архивом здесь.

�� Rendez-vous @ insert-koin.io

Доступна новая версия сайта, с большими разделами:

  • Проектная документация
  • Справочник
  • Раздел «Начало работы»

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *