Koin

Так, так, так… вот оно! Уважаемые пользователи 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 именует каждый компонент в зависимости от пространства имён модуля. В начале у нас будет два экземпляра типа ComponentAComponentB.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 билдер, чтобы писать определения «умным» способом, а также избавить вас от написания конструкторов. Вы можете использовать singlefactory или 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

Предыдущая статьяУ меня появилась идея для приложения, но…я не разработчик
Следующая статьяПочему React 16 — это благословение для React разработчиков