From c50bc91001cdf1f072ccca20d8de226ff18e13df Mon Sep 17 00:00:00 2001 From: Lidonis Calhau Date: Mon, 9 Feb 2026 19:36:45 +0100 Subject: [PATCH] Scope implements AutoCloseable for safe lifecycle management Enables Kotlin's `use { }` pattern on Scope, guaranteeing cleanup even on exceptions. This is a source-compatible and binary-compatible change close() already existed with the right signature. --- docs/reference/koin-core/scopes.md | 31 +++++++++++--- .../kotlin/org/koin/core/scope/Scope.kt | 4 +- .../kotlin/org/koin/core/ScopeAPITest.kt | 40 +++++++++++++++++++ 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/docs/reference/koin-core/scopes.md b/docs/reference/koin-core/scopes.md index ec08d998c..3180e7fe2 100644 --- a/docs/reference/koin-core/scopes.md +++ b/docs/reference/koin-core/scopes.md @@ -81,10 +81,23 @@ class FragmentPresenter ## Creating and Using Scopes +### Using Scopes with `use { }` + +`Scope` implements `AutoCloseable`, so you can use Kotlin's `use { }` block for safe, automatic cleanup. This is the recommended approach for short-lived scopes (request handling, transactions, batch jobs, etc.): + +```kotlin +getKoin().createScope("my_scope_id", named("session")).use { scope -> + val sessionData: SessionData = scope.get() + val prefs: UserPreferences = scope.get() +} +// scope is closed automatically, even on exceptions +``` + ### Manual Scope Management +For longer-lived scopes, you can manage the lifecycle manually: + ```kotlin -// Create a scope val myScope = getKoin().createScope("my_scope_id", named("session")) // Get instances from scope @@ -324,18 +337,24 @@ fun MyScreen() { ### Closing Scopes +`Scope` implements `AutoCloseable`. The recommended way to handle scope cleanup is with `use { }`: + +```kotlin +getKoin().createScope("my_scope", named("session")).use { scope -> + val data: SessionData = scope.get() +} +// All scoped instances released automatically, even on exceptions +``` + When a scope closes: 1. All scoped instances are released 2. `onClose` callbacks are invoked 3. Scope becomes unusable ```kotlin +// Manual close is also supported val scope = getKoin().createScope("my_scope", named("session")) - -// Use the scope val data: SessionData = scope.get() - -// Close when done scope.close() // SessionData instance released // This throws an exception @@ -406,7 +425,7 @@ class CheckoutActivity : AppCompatActivity(), AndroidScopeComponent { 1. **Use singletons sparingly** - Only for truly app-wide dependencies 2. **Scope shared state** - When multiple components need the same instance -3. **Close scopes explicitly** - Don't rely on garbage collection +3. **Close scopes explicitly** - Use `scope.use { }` for short-lived scopes, or call `close()` manually 4. **Keep scopes focused** - Don't put everything in one scope 5. **Use Android scope components** - For automatic lifecycle management diff --git a/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/scope/Scope.kt b/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/scope/Scope.kt index fb65e876b..110a2cd5e 100644 --- a/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/scope/Scope.kt +++ b/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/scope/Scope.kt @@ -51,7 +51,7 @@ class Scope( val scopeArchetype : TypeQualifier? = null, @PublishedApi internal val _koin: Koin, -) : Lockable() { +) : Lockable(), AutoCloseable { internal val linkedScopes = ArrayList() @KoinInternalApi @@ -431,7 +431,7 @@ class Scope( /** * Close all instances from this scope */ - fun close() = KoinPlatformTools.synchronized(this) { + override fun close() = KoinPlatformTools.synchronized(this) { _koin.logger.debug("|- (-) Scope - id:'$id'") _callbacks.forEach { it.onScopeClose(this) } diff --git a/projects/core/koin-core/src/commonTest/kotlin/org/koin/core/ScopeAPITest.kt b/projects/core/koin-core/src/commonTest/kotlin/org/koin/core/ScopeAPITest.kt index 6b9c7f768..9271cc12c 100644 --- a/projects/core/koin-core/src/commonTest/kotlin/org/koin/core/ScopeAPITest.kt +++ b/projects/core/koin-core/src/commonTest/kotlin/org/koin/core/ScopeAPITest.kt @@ -127,6 +127,46 @@ class ScopeAPITest { assertTrue(closed) } + @Test + fun `scope implements AutoCloseable and works with use`() { + val scope = koin.createScope("myScope", scopeKey) + var closed = false + scope.registerCallback(object : ScopeCallback { + override fun onScopeClose(scope: Scope) { + closed = true + } + }) + + val result = scope.use { + it.get() + } + + assertNotNull(result) + assertTrue(closed) + } + + @Test + fun `scope use closes on exception`() { + val scope = koin.createScope("myScope", scopeKey) + var closed = false + scope.registerCallback(object : ScopeCallback { + override fun onScopeClose(scope: Scope) { + closed = true + } + }) + + try { + scope.use { + error("test exception") + } + fail() + } catch (e: IllegalStateException) { + assertEquals("test exception", e.message) + } + + assertTrue(closed) + } + class MyScopeComponent(private val _koin: Koin) : KoinScopeComponent { override fun getKoin(): Koin = _koin override val scope: Scope = createScope()