Использование koin в качестве инструмента для Android-инъекций

теги:

koin предоставляет простой в использовании интерфейс API для Android , позволяющий легко получить доступ к инфраструктуре koin.

[градусная конфигурация коина в Android]

mp.weixin.qq.com/s/bscC7mO4O…

1. startKoin в классе Application

Из своего класса вы можете использовать функцию и внедрить контекст Android следующим образом:

Application startKoin androidContext
class MainApplication : Application() 
override fun onCreate() 
    super.onCreate()

    startKoin 
        // Log Koin into Android logger
        androidLogger()
        // Reference Android context
        androidContext(this@MainApplication)
        // Load modules
        modules(myAppModules)

Если вам нужно запустить Koin из другого класса Android, вы можете использовать эту функцию, чтобы предоставить свой экземпляр Android следующим образом:startKoin Context

startKoin 
    //inject Android context
    androidContext(/* your android context */)
    // ...

2. Дополнительная настройка

Из вашей конфигурации Koin (в блочном коде) вы также можете настроить различные части Koin.startKoin

2.1 Логирование монет для Android

koin предоставляет реализацию log.

startKoin 
    // use Android logger - Level.INFO by default
    androidLogger()
    // ...

2.2 Свойства нагрузки

Вы можете использовать свойства Koin для хранения ключей/значений в файле: assets/koin.properties

startKoin 
    // ...
    // use properties from assets/koin.properties
    androidFileProperties()

3. Внедрить экземпляр объекта в Android

3.1 Подготовка к классу Android

koin предоставляет KoinComponentsрасширения, доступные для компонентов Android, в том числеActivity Fragment Service ComponentCallbacks

Вы можете получить доступ к расширениям Kotlin следующим образом:

by inject()- Ленивый вычислительный экземпляр из контейнера Koin

get()- Получить экземпляр из контейнера Koin

Мы можем объявить свойство для ленивой инъекции:

module 
    // definition of Presenter
    factory  Presenter() 

класс DetailActivity : AppCompatActivity()

// Lazy inject Presenter
override val presenter : Presenter by inject()

override fun onCreate(savedInstanceState: Bundle?) 
    //...

Или мы можем получить экземпляр напрямую:

override fun onCreate(savedInstanceState: Bundle?) 
    super.onCreate(savedInstanceState)
// Retrieve a Presenter instance
val presenter : Presenter = get()

Примечание. Если ваш класс не расширен, просто добавьте интерфейс KoinComponent, если вам нужно, или экземпляр из другого класса. ввести() получить()

3.2 Использование контекста Android

class MainApplication : Application() 
override fun onCreate() 
    super.onCreate()

    startKoin 
        //inject Android context
        androidContext(this@MainApplication)
        // ...

В вашем определении следующая функция позволяет вам получать экземпляры в модуле Koin, чтобы помочь вам легко писать выражения, требующие экземпляров.androidContext() androidApplication() Context Application

val appModule = module 
// create a Presenter instance with injection of R.string.mystring resources from Android
factory 
    MyPresenter(androidContext().resources.getString(R.string.mystring))

4. Конструкторы DSL для Android

4.1 Конструкторы DSL

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

Для Android это означает следующие новые ключевые слова конструктора DSL:

viewModelOf()- эквивалентноviewModel

fragmentOf()- эквивалентноfragment

workerOf()- эквивалентноworker

Примечание. Обязательно используйте перед именем класса, чтобы найти конструктор класса::

4.2 Примеры функций Android DSL

Дано приложение Android со следующими компонентами:

// A simple service
class SimpleServiceImpl() : SimpleService

// Presenter, использующий SimpleService, может получить «id», введенный в
класс параметра FactoryPresenter (valid id: String, val service: SimpleService)

// ViewModel, которая может получить введенный параметр «id», использовать SimpleService и получить
класс SavedStateHandle SimpleViewModel (val id: String, val service: SimpleService, val handle: SavedStateHandle) : ViewModel()

// Сессия с заданной областью действия, которая может получить ссылку на
класс MyActivity (из области видимости) Session(val activity: MyActivity)

// Worker, использующий SimpleService и получающий
класс Context и WorkerParameters SimpleWorker(
private val simpleService: SimpleService,
appContext: Context,
private val params: WorkerParameters
) : CoroutineWorker(appContext, params)

Мы можем объявить их так:

module 
    singleOf(::SimpleServiceImpl) bind<SimpleService>() 
factoryOf(::FactoryPresenter)

viewModelOf(::SimpleViewModel)

scope&lt;MyActivity&gt;()
    scopedOf(::Session)


workerOf(::SimpleWorker)

5. Мультимодульное использование koin в Android

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

5.1 коин мультимодуль

Компоненты не обязательно должны находиться в одном модуле. Модули — это логические пространства, которые помогают вам организовать определения и могут зависеть от других модулей определений. Определения ленивы и затем разрешаются только тогда, когда компонент запрашивает их.

Возьмем пример, когда связанные компоненты находятся в отдельных модулях:

// ComponentB <- ComponentA
class ComponentA()
class ComponentB(val componentA : ComponentA)

val moduleA = модуль
// Singleton ComponentA
single ComponentA()

val moduleB = модуль
// Singleton ComponentB со связанным экземпляром ComponentA
single ComponentB(get())

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

class MainApplication : Application() 
override fun onCreate() 
    super.onCreate()

    startKoin 
        // ...

        // Load modules
        modules(moduleA, moduleB)

5.2 Модуль содержит

В классе доступна новая функция, которая позволяет комбинировать модули, включая другие модули в организованном и структурированном виде.includes() Module

Новый модуль имеет 2 выдающиеся особенности:

Разделите большие модули на более мелкие, более целенаправленные модули.

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

Как это работает? Возьмем несколько модулей, включаем модули в: parentModule

// `:feature` module
val childModule1 = module 
    /* Other definitions here. */

val childModule2 = модуль
/* Другие определения здесь. */

val parentModule = модуль
включает (childModule1, childModule2)

// :appмодуль
startKoin modules(parentModule)

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

parentModule includes childModule1 childModule2 parentModule childModule1 childModule2

ИНФОРМАЦИЯ: загрузка модулей теперь оптимизирована, чтобы сгладить все графики модулей и избежать дублирования определений модулей.

Наконец, вы можете включить несколько вложенных или повторяющихся модулей, и Koin объединит все включенные модули, удалив дубликаты:

// :feature module
val dataModule = module 
    /* Other definitions here. */

val domainModule = модуль
/* Другие определения здесь. */

val featureModule1 = модуль
включает (domainModule, dataModule)

val featureModule2 = модуль
включает (domainModule, dataModule)

// :
класс модуля приложения MainApplication : Application()

override fun onCreate() 
    super.onCreate()

    startKoin 
        // ...

        // Load modules
         modules(featureModule1, featureModule2)

Обратите внимание, что все модули будут включены только один раз:dataModule domainModule featureModule1 featureModule2

5.3 Android ViewModel и навигация

Модуль Gradle представляет новое ключевое слово DSL, которое помогает объявить и связать компоненты ViewModel с жизненным циклом компонентов Android. Также доступно ключевое слово, позволяющее объявить ViewModel с помощью его конструктора.koin-android viewModel singlefactory viewModelOf

val appModule = module 
// ViewModel for Detail View
viewModel  DetailViewModel(get(), get()) 

// or directly with constructor
viewModelOf(::DetailViewModel)

Объявленный компонент должен расширять как минимум класс. Вы можете указать, как внедрить конструктор класса и использовать эту функцию для внедрения зависимостей.android.arch.lifecycle.ViewModel get()

Примечание. Ключевое слово помогает объявить фабричный экземпляр ViewModel. Этот экземпляр будет обрабатываться внутренним ViewModelFactory и при необходимости повторно подключать экземпляр ViewModel. Это также позволит вводить параметры.viewModel viewModelOf

5.4 Внедрение ViewModel

Используйте viewModel в компоненте Android,Activity Fragment Service

by viewModel()- Ленивые свойства делегата для внедрения моделей представления в свойства

getViewModel()- Получить экземпляр модели представления напрямую

class DetailActivity : AppCompatActivity() 
// Lazy inject ViewModel
val detailViewModel: DetailViewModel by viewModel()

5.5 Общая ViewModel активности

Экземпляр ViewModel может совместно использоваться фрагментом и его основным действием.

Чтобы внедрить общую модель представления при использовании: Фрагмент

by activityViewModel()- Ленивые делегированные свойства для внедрения общих экземпляров viewModel в свойства.

get ActivityViewModel()- Получить общий экземпляр viewModel напрямую

Просто объявите модель представления один раз:

val weatherAppModule = module 
// WeatherViewModel declaration for Weather View components
viewModel  WeatherViewModel(get(), get()) 

Примечание: квалификаторы viewModel будут рассматриваться как теги viewModel.

и повторно используйте его в Activity и Fragment:

class WeatherActivity : AppCompatActivity() 
/*
 * Declare WeatherViewModel with Koin and allow constructor dependency injection
 */
private val weatherViewModel by viewModel&lt;WeatherViewModel&gt;()

класс WeatherHeaderFragment: Фрагмент()

/*
 * Declare shared WeatherViewModel with WeatherActivity
 */
private val weatherViewModel by activityViewModel&lt;WeatherViewModel&gt;()

класс WeatherListFragment: Фрагмент()

/*
 * Declare shared WeatherViewModel with WeatherActivity
 */
private val weatherViewModel by activityViewModel&lt;WeatherViewModel&gt;()

5.6 Передача аргументов конструкторам

Передайте параметры в viewModel, пример кода выглядит следующим образом:

в модуле

val appModule = module 
// ViewModel for Detail View with id as parameter injection
viewModel  parameters -&gt; DetailViewModel(id = parameters.get(), get(), get()) 
// ViewModel for Detail View with id as parameter injection, resolved from graph
viewModel  DetailViewModel(get(), get(), get()) 
// or Constructor DSL
viewModelOf(::DetailViewModel)

Входящие параметры точки внедрения зависимостей

class DetailActivity : AppCompatActivity() 
val id : String // id of the view

// Lazy inject ViewModel with id parameter
val detailViewModel: DetailViewModel by viewModel parametersOf(id)

5.7 Внедрение SavedStateHandle

Добавьте новые свойства, введенные в конструктор, для обработки состояния ViewModel:SavedStateHandle

class MyStateVM(val handle: SavedStateHandle, val myService : MyService) : ViewModel()В модуле Koin просто проанализируйте его с параметром или: get()

viewModel MyStateVM(get(), get()) или используйте конструктор DSL:

viewModelOf(::MyStateVM)Фрагмент активности

by viewModel()- Ленивые свойства делегата для внедрения экземпляров модели представления состояния в свойства.

getViewModel()- Получить экземпляр модели представления состояния напрямую

class DetailActivity : AppCompatActivity() 
// MyStateVM viewModel injected with SavedStateHandle
val myStateVM: MyStateVM by viewModel()

5.8 ViewModel в навигационной навигационной диаграмме

Вы можете ограничить экземпляры ViewModel графом навигации. Просто передайте идентификатор, чтобыby koinNavGraphViewModel()

class NavFragment : Fragment() 
val mainViewModel: NavViewModel by koinNavGraphViewModel(R.id.my_graph)

5.9 Общий API viewModel

Koin предоставляет некоторые «низкоуровневые» API для непосредственной настройки экземпляра ViewModel.viewModelForClass ComponentActivity Fragment

ComponentActivity.viewModelForClass(
    clazz: KClass<T>,
    qualifier: Qualifier? = null,
    owner: ViewModelStoreOwner = this,
    state: BundleDefinition? = null,
    key: String? = null,
    parameters: ParametersDefinition? = null,
): Lazy<T>

Также предусмотрена функция верхнего уровня:

fun <T : ViewModel> getLazyViewModelForClass(
    clazz: KClass<T>,
    owner: ViewModelStoreOwner,
    scope: Scope = GlobalContext.get().scopeRegistry.rootScope,
    qualifier: Qualifier? = null,
    state: BundleDefinition? = null,
    key: String? = null,
    parameters: ParametersDefinition? = null,
): Lazy<T>

5.10 ViewModel API — совместимость с Java

В зависимости необходимо добавить совместимость с Java:

// Java Compatibility
implementation "io.insert-koin:koin-android-compat:$koin_version"
您可以使用以下函数或静态函数将 ViewModel 实例注入到 Java 代码库中:viewModel() getViewModel() ViewModelCompat

@JvmOverloads
@JvmStatic
@MainThread
fun <T : ViewModel> getViewModel(
владелец: ViewModelStoreOwner,
clazz: Class<T>,
квалификатор: Qualifier? = null,
параметры: ParametersDefinition? = null
)

6. Внедрить в Jetpack Compose

Сначала узнайте о Jetpack Compose:

developer.android.com/jetpack/com…

6.1 Внедрение @Composable

При написании составных функций вы можете получить доступ к следующим API-интерфейсам Koin:

get()- Получить экземпляр из контейнера Koin

getKoin()- Получить текущий экземпляр Koin

Для модуля, объявляющего компонент «MyService»:

val androidModule = module 
single  MyService() 

Мы можем получить ваш экземпляр следующим образом:

@Composable
fun App() 
    val myService = get<MyService>()

Примечание. Для обеспечения единообразия функциональности Jetpack Compose лучше всего вводить экземпляр непосредственно в атрибут функции. Этот подход позволяет использовать Koin для реализации по умолчанию, но оставляет его открытым для внедрения экземпляров по мере необходимости.

@Composable
fun App(myService: MyService = get()) 

6.2 ViewModel @Composable

Точно так же, как вы получаете доступ к классическому экземпляру singleton/factory, вы можете получить доступ к следующим API-интерфейсам Koin ViewModel:

getViewModel()или - получить экземплярkoinViewModel()

Для модуля, объявляющего компонент "MyViewModel":

module 
    viewModel  MyViewModel() 
    // or constructor DSL
    viewModelOf(::MyViewModel)

Мы можем получить ваш экземпляр следующим образом:

@Composable
fun App() 
    val vm = koinViewModel<MyViewModel>()

Мы можем получить ваш экземпляр в параметре функции:

@Composable
fun App(vm : MyViewModel = koinViewModel()) 

7. Управление областями Android

Компоненты Android, например Activity、Fragment、Service, имеют жизненный цикл, эти компоненты создаются Системой, и в компонентах есть соответствующие обратные вызовы жизненного цикла.

Поскольку у компонентов Android есть атрибуты жизненного цикла, экземпляры компонентов нельзя передавать в koin. По продолжительности жизненного цикла компоненты можно разделить на три категории:

  • • Долгоживущий компонент ( Service、database) — используется несколькими экранами, никогда не выбрасывается.
  • • Компоненты среднего периода ( User session) — используются несколькими экранами и должны быть удалены через определенный период времени.
  • • Компоненты с коротким сроком службы ( ViewModel)--используются только одним Экраном и должны быть удалены в конце Экрана

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

В режиме архитектуры MVP Presenter является компонентом с коротким циклом.

Способ создания его в Activity выглядит следующим образом

class DetailActivity : AppCompatActivity() 
// injected Presenter
override val presenter : Presenter by inject()

Мы также можем создать в модуле

Мы используем область фабрики для создания экземпляра Presenter.

val androidModule = module 
// Factory instance of Presenter
factory  Presenter() 

Создайте область экземпляра, привязанную к области

val androidModule = module 
scope&lt;DetailActivity&gt; 
    scoped  Presenter() 

Большинство утечек памяти Android происходят из-за ссылок на компоненты пользовательского интерфейса/Android из компонентов, отличных от Android. Система хранит ссылки на него и не может полностью восстановить его с помощью сборки мусора.

7.1 Объявление области Android

Чтобы квалифицировать зависимость от компонента Android, вы должны объявить область действия с помощью следующего блока:

class MyPresenter()
class MyAdapter(val presenter : MyPresenter)

модуль
// Объявить область действия для MyActivity
scope<MyActivity>
// получить экземпляр MyPresenter из текущей области
видимости MyAdapter(get())
scoped MyPresenter()

7.2 Класс Android Scope

Koin предоставляет классы Scope, связанные с компонентами жизненного цикла Android.ScopeActivity Retained ScopeActivity ScopeFragment

class MyActivity : ScopeActivity() 
// MyPresenter is resolved from MyActivity's scope
val presenter : MyPresenter by inject()

Android Scope необходимо использовать с интерфейсами для реализации таких полей:AndroidScopeComponent scope

abstract class ScopeActivity(
    @LayoutRes contentLayoutId: Int = 0,
) : AppCompatActivity(contentLayoutId), AndroidScopeComponent 
override val scope: Scope by activityScope()

override fun onCreate(savedInstanceState: Bundle?) 
    super.onCreate(savedInstanceState)

    checkNotNull(scope)

Нам нужно использовать интерфейсы и реализовывать свойства. Это установит область действия по умолчанию, используемую классом.AndroidScopeComponent scope

7.3 Интерфейс Android Scope

Чтобы создать область действия Koin, привязанную к компоненту Android, просто используйте следующую функцию:

createActivityScope()- Создайте Scope для текущего Activity (часть Scope должна быть объявлена)

createActivityRetainedScope()- Создайте RetainedScope (поддерживается жизненным циклом ViewModel) для текущего действия (часть Scope должна быть объявлена)

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

activityScope()- Создайте Scope для текущего Activity (часть Scope должна быть объявлена)

activityRetainedScope()- Создайте RetainedScope (поддерживается жизненным циклом ViewModel) для текущего действия (часть Scope должна быть объявлена)

fragmentScope()- Создайте область для текущего фрагмента и свяжите ее с родительской областью действия.

class MyActivity() : AppCompatActivity(contentLayoutId), AndroidScopeComponent 
override val scope: Scope by activityScope()

Мы также можем установить область хранения (на основе жизненного цикла ViewModel), используя следующее:

class MyActivity() : AppCompatActivity(contentLayoutId), AndroidScopeComponent 
override val scope: Scope by activityRetainedScope()

Если вы не хотите использовать класс Android Scope, вы можете использовать свой собственный класс и создать API с Scope.AndroidScopeComponent

7.4 Ссылка на объем

Ссылки областей позволяют совместно использовать экземпляры между компонентами с настраиваемыми областями. В более широком смысле вы можете использовать экземпляры в компонентах. Например, если нам нужно поделиться экземпляром.Scope UserSession

Сначала объявите определение области действия:

module 
    // Shared user session data
    scope(named("session")) 
        scoped  UserSession() 

Когда пришло время начать использовать экземпляр, создайте для него область действия:UserSession

val ourSession = getKoin().createScope("ourSession",named("session"))

// связываем область видимости ourSession с текущей scopeиз ScopeActivity или ScopeFragment
scope.linkTo(ourSession)

Затем используйте его везде, где вам нужно:

class MyActivity1 : ScopeActivity() 
fun reuseSession()
    val ourSession = getKoin().createScope("ourSession",named("session"))

    // link ourSession scope to current `scope`, from ScopeActivity or ScopeFragment
    scope.linkTo(ourSession)

    // will look at MyActivity1's Scope + ourSession scope to resolve
    val userSession = get&lt;UserSession&gt;()

класс MyActivity2: ScopeActivity()

fun reuseSession()
    val ourSession = getKoin().createScope("ourSession",named("session"))

    // link ourSession scope to current `scope`, from ScopeActivity or ScopeFragment
    scope.linkTo(ourSession)

    // will look at MyActivity2's Scope + ourSession scope to resolve
    val userSession = get&lt;UserSession&gt;()

8. Фабрика фрагментов

Поскольку AndroidX выпустил серию пакетов для расширения функциональности Androidandroidx.fragment Fragment

developer.android.com/jetpack/и…

8.1 Фабрика фрагментов

С момента выпуска был представлен класс, предназначенный для создания экземпляров класса: 2.1.0-alpha-3 FragmentFactoryФрагмент.

developer.android.com/reference/k…

Koin также предоставляет фабричный класс KoinFragmentFactoryFragment для создания Fragment.

8.2 Настройка фабрики фрагментов

Во-первых, в KoinApplicationобъявлении установите экземпляр по умолчанию, используя ключевое слово: fragmentFactory()KoinFragmentFactory .

 startKoin 
    // setup a KoinFragmentFactory instance
    fragmentFactory()
modules(...)

8.3 Объявить и внедрить фрагмент

Объявить фрагмент и внедрить его в модуль

class MyFragment(val myService: MyService) : Fragment() 

val appModule = модуль
, один фрагмент MyService()
MyFragment(get())

8.4 Получить фрагмент

использовать setupKoinFragmentFactory()настройкиFragmentFactory

Чтобы запросить свой фрагмент, используйтеsupportFragmentManager

supportFragmentManager.beginTransaction()
            .replace<MyFragment>(R.id.mvvm_frame)
            .commit()

Добавьте необязательные параметры

supportFragmentManager.beginTransaction()
            .replace<MyFragment>(
                containerViewId = R.id.mvvm_frame,
                args = MyBundle(),
                tag = MyString()
            )

8.5 Фабрика фрагментов и прицелы монет

Если вы хотите использовать Koin Activity Scope, вы должны объявить свой фрагмент как определение в своей области действия:scoped

val appModule = module 
    scope<MyActivity> 
        fragment  MyFragment(get()) 

И настройте свою фабрику фрагментов монет с вашим прицелом:setupKoinFragmentFactory(lifecycleScope)

class MyActivity : AppCompatActivity() 
override fun onCreate(savedInstanceState: Bundle?) 
    // Koin Fragment Factory
    setupKoinFragmentFactory(lifecycleScope)

    super.onCreate(savedInstanceState)
    //...

9. Инъекция коинов WorkManager

koin предоставляет отдельный пакет компонентов koin-androidx-workmanager для WorkManager.

Во-первых, в объявлении KoinApplication используйте ключевое слово для установки пользовательского экземпляра WorkManager:workManagerFactory()

class MainApplication : Application(), KoinComponent 
override fun onCreate() 
    super.onCreate()
    startKoin 
        // setup a WorkManager instance
        workManagerFactory()
        modules(...)
    
    setupWorkManagerFactory()

Модификация AndroidManifest.xml, чтобы избежать использования значения по умолчанию

    <application . . .>
        . . .
        <provider
            android:name="androidx.work.impl.WorkManagerInitializer"
            android:authorities="$applicationId.workmanager-init"
            tools:node="remove" />
  </application>

9.1 Объявление ListenableWorker

val appModule = module 
single  MyService() 
worker  MyListenableWorker(get()) 

9.2 Создайте дополнительную фабрику WorkManagerFactory

class MainApplication : Application(), KoinComponent 
override fun onCreate() 
    super.onCreate()

    startKoin 
       workManagerFactory(workFactory1, workFactory2)
       . . .
    

    setupWorkManagerFactory()

Если и Koin, и workFactory1 WorkManagerFactoryмогут быть созданы ListenableWorker, будет использована предоставленная Koin фабрика.

9.3 Изменение манифеста самой koin lib

Если koin-androidx-workmanagerфабрика по умолчанию отключена, и разработчик приложения не инициализирует инфраструктуру диспетчера работ koin, у него не будет доступной фабрики диспетчера работ.

В связи с описанной выше ситуацией, мы делаем следующие улучшения DSL:

val workerFactoryModule = module 
factory<WorkFactory>  WorkFactory1() 
factory<WorkFactory>  WorkFactory2() 

Затем внутри коина сделайте что-то вроде

fun Application.setupWorkManagerFactory(
// no vararg for WorkerFactory
) 
. . .
getKoin().getAll<WorkerFactory>()
.forEach 
delegatingWorkerFactory.addFactory(it)

справочная ссылка

вставка-koin.io/

рекомендуемое чтение

Автор: Calvin873
Ссылка: https://juejin.cn/post/7189917106580750395

Guess you like

Origin blog.csdn.net/gqg_guan/article/details/131801957