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

Scope declared instance #2082

Merged
merged 4 commits into from
Dec 17, 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
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not thread-safe.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

declare function is synchronized. But this will be fixed. I manage to fix that point


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")
}
}
Loading