Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix coroutine engine fix dispatcher #1798

Merged
merged 5 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions docs/reference/koin-core/lazy-modules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
title: Lazy Modules and Background Loading
---

In this section we will see how to organize your modules with lazy loading approach.

## Defining Lazy Modules [Experimental]

You can now declare lazy Koin module, to avoid trigger any pre allocation of resources and load them in background with Koin start.

- `lazyModule` - declare a Lazy Kotlin version of Koin Module
- `Module.includes` - allow to include lazy Modules

A good example is always better to understand:

```kotlin
// Some lazy modules
val m2 = lazyModule {
singleOf(::ClassB)
}

// include m2 lazy module
val m1 = lazyModule {
includes(m2)
singleOf(::ClassA) { bind<IClassA>() }
}
```

:::info
LazyModule won't trigger any resources until it has been loaded by the following API
:::

## Background loading with Kotlin coroutines [Experimental]

Once you have declared some lazy modules, you can load them in background from your Koin configuration and further more.

- `KoinApplication.lazyModules` - load lazy modules in background with coroutines, regarding platform default Dispatchers
- `Koin.waitAllStartJobs` - wait for start jobs to complete
- `Koin.runOnKoinStarted` - run block code after start completion

A good example is always better to understand:

```kotlin
startKoin {
// load lazy Modules in background
lazyModules(m1)
}

val koin = KoinPlatform.getKoin()

// wait for loading jobs to finish
koin.waitAllStartJobs()

// or run code after loading is done
koin.runOnKoinStarted { koin ->
// run after background load complete
}
```

:::note
The `lazyModules` function allow you to specify a dispatcher: `lazyModules(modules, dispatcher = Dispatcher.IO)`
:::

:::info
Default dispatcher for coroutines engine is `Dispatchers.Default`
:::
40 changes: 0 additions & 40 deletions docs/reference/koin-core/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,43 +255,3 @@ Notice that all modules will be included only once: `dataModule`, `domainModule`
:::info
If you have any compiling issue while including modules from the same file, either use `get()` Kotlin attribute operator on your module either separate each module in files. See https://github.com/InsertKoinIO/koin/issues/1341 workaround
:::

## Lazy modules & background modules loading with Kotlin coroutines [Experimental]

You can now declare "lazy" Koin module, to avoid trigger any pre allocation of resources and load them in background with Koin start.

- `lazyModule` - declare a Lazy Kotlin version of Koin Module
- `Module.includes` - allow to include lazy Modules
- `KoinApplication.lazyModules` - load lazy modules in background with coroutines, regarding platform default Dispatchers
- `Koin.waitAllStartJobs` - wait for start jobs to complete
- `Koin.runOnKoinStarted` - run block code after start completion

A good example is always better to understand:

```kotlin
// Some lazy modules
val m2 = lazyModule {
singleOf(::ClassB)
}

// include m2 lazy module
val m1 = lazyModule {
includes(m2)
singleOf(::ClassA) { bind<IClassA>() }
}

startKoin {
// load lazy Modules in background
lazyModules(m1)
}

val koin = KoinPlatform.getKoin()

// wait for loading jobs to finish
koin.waitAllStartJobs()

// or run code after loading is done
koin.runOnKoinStarted { koin ->
// run after background load complete
}
```
3 changes: 3 additions & 0 deletions projects/compose/koin-compose/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ plugins {
alias(libs.plugins.compose)
}

val koinComposeVersion: String by project
version = koinComposeVersion

kotlin {
jvm {
withJava()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package org.koin.core

import kotlinx.coroutines.CoroutineDispatcher
import org.koin.core.annotation.KoinExperimentalAPI
import org.koin.core.annotation.KoinInternalApi
import org.koin.core.extension.coroutinesEngine
Expand All @@ -35,10 +36,10 @@ import org.koin.core.module.Module
* run coroutinesEngine() to setup if needed
*/
@KoinExperimentalAPI
fun KoinApplication.lazyModules(vararg list: Lazy<Module>) {
coroutinesEngine()
fun KoinApplication.lazyModules(vararg moduleList: Lazy<Module>,dispatcher: CoroutineDispatcher? = null) {
coroutinesEngine(dispatcher)
koin.coroutinesEngine.launchStartJob {
modules(list.map { it.value })
modules(moduleList.map { it.value })
}
}

Expand All @@ -51,9 +52,9 @@ fun KoinApplication.lazyModules(vararg list: Lazy<Module>) {
* run coroutinesEngine() to setup if needed
*/
@KoinExperimentalAPI
fun KoinApplication.lazyModules(list: List<Lazy<Module>>) {
coroutinesEngine()
fun KoinApplication.lazyModules(moduleList: List<Lazy<Module>>,dispatcher: CoroutineDispatcher? = null) {
coroutinesEngine(dispatcher)
koin.coroutinesEngine.launchStartJob {
modules(list.map { it.value })
modules(moduleList.map { it.value })
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import org.koin.core.Koin
import org.koin.core.annotation.KoinExperimentalAPI
import org.koin.core.annotation.KoinInternalApi
import org.koin.core.extension.KoinExtension
import org.koin.core.logger.Logger
import org.koin.mp.KoinPlatformCoroutinesTools
import kotlin.coroutines.CoroutineContext

Expand All @@ -28,35 +29,43 @@ import kotlin.coroutines.CoroutineContext
*
* Help handle coroutines jobs for different purposes
*
* @author Arnaud Giulani
* @author Arnaud Giuliani
*/
@KoinExperimentalAPI
@KoinInternalApi
class KoinCoroutinesEngine : CoroutineScope, KoinExtension {
private val dispatcher: CoroutineDispatcher = KoinPlatformCoroutinesTools.defaultCoroutineDispatcher()
class KoinCoroutinesEngine(coroutineDispatcher: CoroutineDispatcher? = null) : CoroutineScope, KoinExtension {
private val dispatcher: CoroutineDispatcher = coroutineDispatcher ?: KoinPlatformCoroutinesTools.defaultCoroutineDispatcher()
private val supervisorJob = SupervisorJob()
override val coroutineContext: CoroutineContext = supervisorJob + dispatcher

internal val startJobs = arrayListOf<Deferred<*>>()

override lateinit var koin: Koin
private var _koin: Koin? = null
private fun getKoin() : Koin = _koin ?: error("No Koin instance is registered for plugin $this")
private fun getLogger() : Logger = getKoin().logger

override fun onRegister(koin: Koin) {
_koin = koin
koin.logger.debug("$TAG - init ($dispatcher)")
}

fun <T> launchStartJob(block: suspend CoroutineScope.() -> T) {
startJobs.add(async { block() })
}

suspend fun awaitAllStartJobs() {
koin.logger.debug("await All Start Jobs ...")
getLogger().debug("$TAG - await All Start Jobs ...")
startJobs.map { it.await() }
startJobs.clear()
}

override fun onClose() {
koin.logger.debug("onClose $this")
getLogger().debug("$TAG - onClose $this")
cancel("KoinCoroutinesEngine shutdown")
}

companion object {
const val TAG = "[CoroutinesEngine]"
const val EXTENSION_NAME = "coroutine-engine"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.koin.core.extension

import kotlinx.coroutines.CoroutineDispatcher
import org.koin.core.Koin
import org.koin.core.KoinApplication
import org.koin.core.annotation.KoinExperimentalAPI
Expand All @@ -31,10 +32,10 @@ import org.koin.core.coroutine.KoinCoroutinesEngine.Companion.EXTENSION_NAME
*/
@OptIn(KoinInternalApi::class)
@KoinExperimentalAPI
fun KoinApplication.coroutinesEngine() {
fun KoinApplication.coroutinesEngine(dispatcher : CoroutineDispatcher? = null) {
with(koin.extensionManager) {
if (getExtensionOrNull<KoinCoroutinesEngine>(EXTENSION_NAME) == null) {
registerExtension(EXTENSION_NAME, KoinCoroutinesEngine())
registerExtension(EXTENSION_NAME, KoinCoroutinesEngine(dispatcher))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ import org.koin.core.annotation.KoinExperimentalAPI

@KoinExperimentalAPI
actual object KoinPlatformCoroutinesTools {
actual fun defaultCoroutineDispatcher(): CoroutineDispatcher = Dispatchers.IO
actual fun defaultCoroutineDispatcher(): CoroutineDispatcher = Dispatchers.Default
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.koin.test

import kotlinx.coroutines.Dispatchers
import org.koin.core.lazyModules
import org.koin.core.logger.Level
import org.koin.core.module.dsl.bind
Expand Down Expand Up @@ -39,4 +40,26 @@ class LazyModuleTest {

assertNotNull(koin.getOrNull<ClassB>())
}

@Test
fun test_dispatchers() {
var resolved: Boolean? = null
val m2 = lazyModule {
resolved = true
singleOf(::ClassB)
}
val m1 = lazyModule {
includes(m2)
singleOf(::ClassA) { bind<IClassA>() }
}
assertTrue(resolved == null, "resolved should be null: $resolved")

val koin = koinApplication {
printLogger(Level.DEBUG)
lazyModules(m1, dispatcher = Dispatchers.IO)
}.koin
koin.waitAllStartJobs()

assertNotNull(koin.getOrNull<ClassB>())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class ExtensionManager(internal val _koin: Koin) {

fun <T : KoinExtension> registerExtension(id: String, extension: T) {
extensions[id] = extension
extension.koin = _koin
extension.onRegister(_koin)
}

fun close() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ import org.koin.core.Koin
interface KoinExtension {

/**
* Current Koin instance
* register from Koin instance
*/
var koin: Koin
fun onRegister(koin : Koin)

/**
* Called when closing Koin
Expand Down
5 changes: 4 additions & 1 deletion projects/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ org.gradle.parallel=true

#Kotlin
kotlin.code.style=official

#Koin
koinVersion=3.6.0-alpha1
koinVersion=3.5.4-rc-1
koinComposeVersion=1.1.3-rc-1

#Compose
composeCompiler=1.5.7
#Android
Expand Down
Loading