Skip to content

Commit

Permalink
Merge pull request #2082 from InsertKoinIO/scope_declared_instance
Browse files Browse the repository at this point in the history
Scope declared instance
  • Loading branch information
arnaudgiuliani authored Dec 17, 2024
2 parents afffe59 + e548a3b commit 849f169
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 30 deletions.
2 changes: 1 addition & 1 deletion examples/androidx-samples/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ dependencies {
implementation "io.insert-koin:koin-core-coroutines"
implementation "io.insert-koin:koin-androidx-workmanager"
implementation "io.insert-koin:koin-androidx-navigation"
implementation "io.insert-koin:koin-androidx-startup"
// implementation "io.insert-koin:koin-androidx-startup"
testImplementation "io.insert-koin:koin-test-junit4"
testImplementation "io.insert-koin:koin-android-test"
}
Original file line number Diff line number Diff line change
@@ -1,51 +1,74 @@
package org.koin.sample.sandbox

import android.app.Application
import android.os.StrictMode
import androidx.work.WorkManager
import androidx.work.await
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.runBlocking
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidFileProperties
import org.koin.android.ext.koin.androidLogger
import org.koin.androidx.fragment.koin.fragmentFactory
import org.koin.androidx.workmanager.koin.workManagerFactory
import org.koin.androix.startup.KoinStartup
import org.koin.core.context.GlobalContext.startKoin
import org.koin.core.lazyModules
import org.koin.core.logger.Level
import org.koin.dsl.KoinAppDeclaration
import org.koin.core.waitAllStartJobs
import org.koin.mp.KoinPlatform
import org.koin.sample.sandbox.di.allModules


class MainApplication : Application() {

class MainApplication : Application(), KoinStartup {

override fun onKoinStartup() : KoinAppDeclaration = {
androidLogger(Level.DEBUG)
androidContext(this@MainApplication)
androidFileProperties()
fragmentFactory()
workManagerFactory()
modules(allModules)
}
// override fun onKoinStartup() : KoinAppDeclaration = {
// androidLogger(Level.DEBUG)
// androidContext(this@MainApplication)
// androidFileProperties()
// fragmentFactory()
// workManagerFactory()
// modules(allModules)
// }

companion object {
var startTime: Long = 0
}

override fun onCreate() {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectAll() // or .detectAll() for all detectable problems
.penaltyLog()
.build()
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build()
)
super.onCreate()

startTime = System.currentTimeMillis()
// startKoin {
// androidLogger(Level.DEBUG)
// androidContext(this@MainApplication)
// androidFileProperties()
// fragmentFactory()
// workManagerFactory()
//
// modules(allModules)
// }

startKoin {
androidLogger(Level.DEBUG)
androidContext(this@MainApplication)
androidFileProperties()
fragmentFactory()
workManagerFactory()
// lazyModules(allModules, dispatcher = IO)
modules(allModules)
}

//TODO Load/Unload Koin modules scenario cases
cancelPendingWorkManager(this)

KoinPlatform.getKoin().waitAllStartJobs()
}
}

Expand All @@ -58,6 +81,6 @@ private fun cancelPendingWorkManager(mainApplication: MainApplication) {
WorkManager.getInstance(mainApplication)
.cancelAllWork()
.result
.await()
.get()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class WorkManagerActivity : AppCompatActivity() {
WorkManager.getInstance(this@WorkManagerActivity)
.cancelAllWork()
.result
.await()
.get()

enqueueWork<SimpleWorker>(createData(42))
enqueueWork<SimpleWorker>(createData(43))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,19 @@ inline fun <reified T> _createDefinition(
secondaryTypes = secondaryTypes,
)
}

inline fun <reified T> _createDeclaredDefinition(
kind: Kind = Kind.Singleton,
qualifier: Qualifier? = null,
secondaryTypes: List<KClass<*>> = emptyList(),
scopeQualifier: Qualifier,
): BeanDefinition<T> {
return BeanDefinition(
scopeQualifier,
T::class,
qualifier,
{ error("declared instance error ") },
kind,
secondaryTypes = secondaryTypes,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2017-Present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.koin.core.instance

import org.koin.core.definition.BeanDefinition
import org.koin.core.scope.Scope
import org.koin.core.scope.ScopeID

/**
* Declared Instance in scope - Value holder to get back the value for the given scope Id
* to avoid ScopedInstanceFactory where we need the bean definition to return a definition
*
* @author Arnaud Giuliani
*/
class DeclaredScopedInstance<T>(beanDefinition: BeanDefinition<T>, val scopeID : ScopeID) :
InstanceFactory<T>(beanDefinition) {

private var value : T? = null

fun setValue(v : T){
value = v
}

override fun get(context: ResolutionContext): T {
return value ?: error("Scoped instance not found for ${context.scope.id} in $beanDefinition")
}

override fun isCreated(context: ResolutionContext?): Boolean = value != null

override fun drop(scope: Scope?) {
value = null
}

override fun dropAll() {
value = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.koin.core.definition.IndexKey
import org.koin.core.definition.Kind
import org.koin.core.definition._createDefinition
import org.koin.core.definition.indexKey
import org.koin.core.instance.DeclaredScopedInstance
import org.koin.core.instance.ResolutionContext
import org.koin.core.instance.InstanceFactory
import org.koin.core.instance.NoClass
Expand Down Expand Up @@ -111,7 +112,7 @@ class InstanceRegistry(val _koin: Koin) {
}

@PublishedApi
internal inline fun <reified T> declareScopedInstance(
internal inline fun <reified T> scopeDeclaredInstance(
instance: T,
qualifier: Qualifier? = null,
secondaryTypes: List<KClass<*>> = emptyList(),
Expand All @@ -121,16 +122,17 @@ class InstanceRegistry(val _koin: Koin) {
) {
val def = _createDefinition(Kind.Scoped, qualifier, { instance }, secondaryTypes, scopeQualifier)
val indexKey = indexKey(def.primaryType, def.qualifier, def.scopeQualifier)
val existingFactory = instances[indexKey] as? ScopedInstanceFactory
val existingFactory = instances[indexKey] as? DeclaredScopedInstance<T>
if (existingFactory != null) {
existingFactory.refreshInstance(scopeID, instance as Any)
existingFactory.setValue(instance)
} else {
val factory = ScopedInstanceFactory(def)
val factory = DeclaredScopedInstance(def,scopeID)
saveMapping(allowOverride, indexKey, factory)
def.secondaryTypes.forEach { clazz ->
val index = indexKey(clazz, def.qualifier, def.scopeQualifier)
saveMapping(allowOverride, index, factory)
}
factory.setValue(instance)
}
}

Expand All @@ -154,6 +156,7 @@ class InstanceRegistry(val _koin: Koin) {

internal fun dropScopeInstances(scope: Scope) {
_instances.values.filterIsInstance<ScopedInstanceFactory<*>>().forEach { factory -> factory.drop(scope) }
_instances.values.removeAll { it is DeclaredScopedInstance<*> && it.scopeID == scope.id }
}

internal fun close() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,8 +379,10 @@ class Scope(

/**
* Declare a component definition from the given instance
* This result of declaring a scoped/single definition of type T, returning the given instance
* This result of declaring a scoped definition of type T, returning the given instance
* (single definition of the current scope is root)
*
* The instance will be drop at scope.close()
*
* @param instance The instance you're declaring.
* @param qualifier Qualifier for this declaration
Expand All @@ -393,7 +395,7 @@ class Scope(
secondaryTypes: List<KClass<*>> = emptyList(),
allowOverride: Boolean = true,
) = KoinPlatformTools.synchronized(this) {
_koin.instanceRegistry.declareScopedInstance(
_koin.instanceRegistry.scopeDeclaredInstance(
instance,
qualifier,
secondaryTypes,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package org.koin.core

import org.koin.Simple
import org.koin.Simple.ComponentA
import org.koin.core.component.KoinScopeComponent
import org.koin.core.component.createScope
import org.koin.core.error.NoScopeDefFoundException
import org.koin.core.error.ScopeAlreadyCreatedException
import org.koin.core.logger.Level
import org.koin.core.module.dsl.scopedOf
import org.koin.core.qualifier.named
import org.koin.core.scope.Scope
import org.koin.core.scope.ScopeCallback
Expand Down Expand Up @@ -121,4 +126,28 @@ class ScopeAPITest {
scope1.close()
assertTrue(closed)
}

class MyScopeComponent(private val _koin: Koin) : KoinScopeComponent {
override fun getKoin(): Koin = _koin
override val scope: Scope = createScope()
}

@Test
fun scope_clean_test() {
val koin = koinApplication {
printLogger(Level.DEBUG)
}.koin

val scopeComponent = MyScopeComponent(koin)
val scope = scopeComponent.scope
scope.declare("hello")

assertEquals("hello", scope.get<String>())
scope.close()

val scopeComponent2 = MyScopeComponent(koin)
val scope2 = scopeComponent2.scope

assertNull(scope2.getOrNull<String>())
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package org.koin.dsl

import org.koin.Simple
import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin
import org.koin.core.logger.Level
import org.koin.mp.KoinPlatform
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class CloseDefinitionTest {
Expand Down Expand Up @@ -50,4 +54,58 @@ class CloseDefinitionTest {
koin.unloadModules(listOf(module))
assertTrue(!closed)
}

class MyClass(val name: String)

@Test
fun override_onclose() {

var cleanup = ""

val overrideModule = module {
single { MyClass("override") } onClose { cleanup = it?.name ?: "override" }
}

val module = module {
// eager initialization
single(createdAtStart = true) { MyClass("original") } onClose { cleanup = it?.name ?: "original" }
}

// override
val koin = koinApplication { modules(module, overrideModule) }.koin

val instance = koin.get<MyClass>()
println("Accessing '${instance.name}'")
assertEquals(instance.name, "override")

koin.close()
println("Koin stopped")
assertEquals(cleanup, "override")
}

@Test
fun override_onclose_2() {

var cleanup = ""

val overrideModule = module {
single { MyClass("override") } onClose { cleanup = it?.name ?: "override" }
}

val module = module {
// eager initialization
single(createdAtStart = true) { MyClass("original") } onClose { cleanup = it?.name ?: "original" }
}

// override
startKoin { modules(module, overrideModule) }

val instance = KoinPlatform.getKoin().get<MyClass>()
println("Accessing '${instance.name}'")
assertEquals(instance.name, "override")

stopKoin()
println("Koin stopped")
assertEquals(cleanup, "override")
}
}

0 comments on commit 849f169

Please sign in to comment.