Так, так, так… вот оно! Уважаемые пользователи 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:
val appModule = module { // singleton for Repository single { Repository() } module("view") { // factory of View factory { View() } // factory of Presenter - inject Repository instance factory { Presenter(get()) } } } class Repository() class View() class Presenter(val repository : Repository)
Подробней можно узнать по ссылкам: Koin DSL Quick Reference или Koin DSL Documentation.
Модули ― определение пространства имён и видимости?
Модуль позволяет использовать ваши определения, а также сделать логическое разделение между определениями (пространством имён) и ограничить их видимость.
Во фрагменте кода, представленном ниже, ComponentB
и ComponentC
будут видеть только определение ComponentA
из их модуля. Если нам нужно запросить экземпляр ComponentA,
мы должны использовать его имя (иначе, Koin не сможет выбрать из двух определений).
По умолчанию, Koin именует каждый компонент в зависимости от пространства имён модуля. В начале у нас будет два экземпляра типа ComponentA
: ComponentB.ComponentA
и ComponentC.ComponentA.
Ниже показано, как мы можем получить эти экземпляры:
class ComponentA class ComponentB(val componentA: ComponentA) class ComponentC(val componentA: ComponentA) val module = module { module("ComponentB") { single { ComponentA() } single { ComponentB(get()) } } module("ComponentC") { single { ComponentA() } single { ComponentC(get()) } } }
// retrieve ComponentA from /ComponentB val a_b = get<ComponentA>(name = "ComponentB.ComponentA") // retrieve ComponentA from /ComponentC val a_c = get<ComponentA>(name = "ComponentC.ComponentA")
Для определений и модулей, у нас есть два флага, чтобы задать дополнительное поведение:
createdAtStart
: создаёт экземпляр модуля/определения с функцией startKoin().override
: изменение определения и модуля должно быть явным. Необходимо задатьoverride=true
для определения или модуля, которому нужно переопределить существующий контент.
В главе «modules» из справочной документации, можно узнать об этом поподробнее.
Новый Scope API⭐️
В предыдущей версии Koin, функция release()
помогала высвобождать экземпляры из пути. Это было очень неявно и действительно очень полезно.
В Koin 1.0 появился Scope API. Как это работает? Scope — это фиксированная продолжительность времени, в течение которого существует объект. После истечения этого времени, объекты под влиянием scope не могут быть внедрены снова (они выбрасываются из контейнера).
Нам нужно специальное объявление определения, между временем жизни определений single
и factory
. Используйте ключевое слово scope,
чтобы объявить scope определение:
module {
scope("session") { Presenter() }
}
Scope обозначен собственным идентификатором ― scope id. Чтобы разрешить scope определение, вам нужно создать ассоциированный scope с этим id. Однажды созданное, разрешение будет выполнено в отношении scopeId, указанному в DSL.
module { scope("session") { Presenter() } } // getKoin() is accessible from any KoinComponent // create a scope in Koin's registry getKoin().createScope("session") // will return the same instance of Presenter until scope 'session' is closed val presenter = get<Presenter>() // create scope "session" val session = getKoin().createScope("session") // will return the same instance of Presenter until 'session' is closed // Presenter is bound to scope "session" val presenter = get<Presenter>() // close it // drop instance of Presenter session.close()
Scope API будет полезен для написания компонентов с ограниченным временем жизни (сессии аутентификации, кошельки, корзины…).
За деталями, обращайтесь к документации Scope API.
Параметры внедрения✨
API для внедрения зависимостей, позволяет использовать деструктурированное объявление Kotlin напрямую, чтобы описать значения параметров, для их внедрения на лету:
class Presenter(val view : View, id : String) val myModule = module { // define a single with incoming parameters single{ (view : View, id : String) -> Presenter(view,id) } }
// retrieve Presenter instance and pass it paramaters val presenter : Presenter by inject { parametersOf(this,id) }
Для запроса экземпляра используйте функцию parametersOf(),
чтобы указать, какие параметры нужно предоставить.
Документация по параметрам внедрения, доступна по ссылке.
Лучшие объявления определений☢️
Может показаться что Koin DSL недостаточно автоматизирован, потому что приходиться писать в конструкторах вручную, с функцией get.
Если вам это не по душе, то у нас есть хорошие новости. Мы запустили экспериментальный API билдер, чтобы писать определения «умным» способом, а также избавить вас от написания конструкторов. Вы можете использовать single
, factory
или scope
определения непосредственно с данным типом, без всяких выражений.
// Used classes class ComponentA() class ComponentB(val a : ComponentA) class ComponentC(val B : ComponentB) : Component interface Component // Direct DSL way module { single { ComponentA() } single { ComponentB(get()) } single<Component> { ComponentC(get()) } } // DSL & API BUilder way module { single<ComponentA>() single<ComponentB>() singleBy<Component,ComponentC>() }
Если у вас есть класс реализации, для согласования с интерфейсом используйте ключевое слово singleBy. Допускается два типа параметров: singleBy<Target,Implementation>().
Все эти ключи, всё ещё экспериментальные (и используют интроспекцию, чтобы получить конструктор вашего класса). Нам нужен ваш фидбэк по этому поводу?
Они находятся в проекте koin-core-ext
(краткая справка).
Koin для Java разработчиков?
Большое событие: появление проекта koin-java.
Идея проекта в том, чтобы создать условия для простого старта и внедрения в Java, с помощью статических хелперов:
// accessible from KoinJavaStarter startKoin(singletonList(koinModule)); // Accessible by KoinJavaComponent static helper ComponentA a = get(ComponentA.class); Lazy<ComponentA> lazy_a = inject(ComponentA.class);
// Tag it with @JvmField to make it usable for Java @JvmField val koinModule = module { single { ComponentA() } single { ComponentB(get()) } single { ComponentC(get(), get()) } }
Вы должны описать Koin модуль в Kotlin, но все ваши классы могут быть в Java! KoinComponent static helper пришёл из класса KoinJavaComponent
, а startKoin перенесён из KoinJavaStarter
.
Справка по koin-java.
и мощный функционал для 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 к текущему жизненному циклу.
val appModule = module { // Shared session object scope("wallet") { Wallet() } } class WalletActivity : AppCompatActivity() { // Bind via "wallet" scope val wallet: Wallet by inject() override fun onCreate(savedInstanceState: Bundle?) { // ... // Bind "session" scope to WalletActivity lifecycle bindScope(createScope("wallet")) } }
Фича ViewModel в Koin, предоставляется проектом koin-android-viewmodel
и преследует ту же цель: облегчить внедрение зависимостей для компонентов Android Architecture ViewModel.
Объявляйте класс ViewModel с помощью ключевого слова viewModel
(доступно в формате API билдера, чтобы не писать конструктор). Используйте это в Activity или Fragment с помощью by viewModel()
или getViewModel().
// used classes class MyRepository() class MyViewModel(val repository : MyRepository) : ViewModel() // module val myModule : Module = module { // ViewModel instance of MyViewModel // get() will resolve Repository instance viewModel { MyViewModel(get()) } // or even (with builder API) viewModel<MyViewModel>() // Single instance of Repository single<Repository> { MyRepository() } } // Use directly your ViewModel class MyActivity : AppCompatActivity(){ // Lazy inject MyViewModel val model : MyViewModel by viewModel() override fun onCreate() { super.onCreate() // or direct retrieve instance val model : MyViewModel = getViewModel() } }
И последнее большое новшество: starKoin()
отныне не требует запуска из класса Application.
Функции нужен только экземпляр Context
, чтобы работать и запускаться из любого Android класса:
startKoin(androidContext, appModules)
DSL функции androidApplication()
и androidContext()
позволяют восстановить экземпляры Application
и Context
.
Подробней в справке для Android.
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
Доступна новая версия сайта, с большими разделами:
Перевод статьи Arnaud Giuliani : Koin 1.0.0 Unleashed