koin icon indicating copy to clipboard operation
koin copied to clipboard

Let components know their containing scope

Open tlevavasseur-decathlon opened this issue 3 years ago • 1 comments

Considering I have 2 components in root scope :

graph LR
subgraph ROOT SCOPE
COMP5 ...->|by inject| COMP6
end
   class COMP5 { val comp6 by inject<COMP6>() }

Any other component can use them by basic injection, even from a scoped component :

graph LR
subgraph "ROOT SCOPE"
COMP0 ....->|by inject| COMP5
COMP5 ...->|by inject| COMP6
COMP1
end

subgraph "SCOPE&lt;COMP1&gt;"
COMP1_SCOPE([SCOPE])
COMP2
COMP1_SCOPE --->|defines| COMP2
end

COMP1 -->|creates| COMP1_SCOPE
COMP1 ..->|by inject| COMP2
COMP2 ..->|by inject| COMP5
    module {
    	single { COMP0() }
    	single { COMP1() }
    	single { COMP5() }
    	single { COMP6() }
    	scope<COMP1> {
	    	scoped { COMP2() }
    	}
    }

BUT if I want a custom COMP6 for 1 particular component I need to scope it and all components before him in the chain :

graph LR
subgraph "ROOT SCOPE"
COMP5["COMP5(&quot;GLOBAL&quot;)"]
COMP6["COMP6(&quot;GLOBAL&quot;)"]
COMP0 ....->|by inject| COMP5
COMP5 ...->|by inject| COMP6
COMP1
COMP3
end

subgraph "SCOPE&lt;COMP1&gt;"
COMP1_SCOPE([SCOPE])
COMP2
COMP1_SCOPE -->|defines| COMP2
end

subgraph "SCOPE&lt;COMP3&gt;"
COMP3_SCOPE([SCOPE])
COMP4
SCOPED_COMP5["COMP5(&quot;CUSTOM&quot;)"]
SCOPED_COMP6["COMP6(&quot;CUSTOM&quot;)"]

COMP3_SCOPE --->|defines| COMP4 & SCOPED_COMP5 & SCOPED_COMP6

COMP4 ...->|by inject| SCOPED_COMP5
SCOPED_COMP5 ..->|by inject| SCOPED_COMP6
end

COMP1 --->|creates| COMP1_SCOPE
COMP1 ..->|by inject| COMP2
COMP2 ..->|by inject| COMP5

COMP3 --->|creates| COMP3_SCOPE
COMP3 ..->|by inject| COMP4

But for that, all those components have to know their containing scope. This is not a scope they create, it's the scope who created them (and in fact, if they create their own scope, shouldn't it be linked to the containing scope ?).

graph LR

subgraph "SCOPE&lt;COMP3&gt;"
COMP3_SCOPE([SCOPE])
COMP4
SCOPED_COMP5["COMP5(&quot;CUSTOM&quot;)"]
SCOPED_COMP6["COMP6(&quot;CUSTOM&quot;)"]

COMP3_SCOPE --->|defines| COMP4 & SCOPED_COMP5 & SCOPED_COMP6
COMP4 & SCOPED_COMP5 & SCOPED_COMP6 --->|knows| COMP3_SCOPE

COMP4 ...->|by inject| SCOPED_COMP5
SCOPED_COMP5 ..->|by inject| SCOPED_COMP6
end

COMP3 --->|creates| COMP3_SCOPE
COMP3 ..->|by inject| COMP4
    module {
    	single { COMP0() }
    	single { COMP1() }
    	single { COMP3() }
    	single { COMP5("GLOBAL") }
    	single { COMP6("GLOBAL") }
    	scope<COMP1> {
	    	scoped { COMP2() }
    	}
    	scope<COMP3> {
	    	scoped { COMP4(scope = this) }
	    	scoped { COMP5("CUSTOM", scope = this) }
	    	scoped { COMP6("CUSTOM", scope = this) }
    	}
    }

For now, the only way to know its containing scope is to pass it to the constructor but constructor injection is tedious when abstract classes are involved because all params have to be copied.

It could be nice to have the possibility to inject this containing scope :

  • Either with an interface like interface Scoped { var container : Scope? } which is automatically called after instantiation. (the scope should remain nullable because a component could exist in and out a scope, or let us access root scope)

  • or via a method like val container = instantiatingScope() we could use in components during init phase, which could eventually be done with a ThreadLocal trick wrapping the definition execution, like :

val CURRENT_SKOPE = ThreadLocal<Skope>()

inline fun <reified T> ScopeDSL.skoped(
    qualifier: Qualifier? = null,
    noinline definition: Definition<T>
) = scoped(qualifier) { params ->
    val current = CURRENT_SKOPE.get()
    CURRENT_SKOPE.set(this)
    try {
        definition(params)
    } finally {
        CURRENT_SKOPE.set(current)
    }
}

fun KoinScopeComponent.instantiatingScopeOrRoot() =
    CURRENT_SKOPE.get() ?: getKoin().getScope("_root_")

fun KoinScopeComponent.requireInstantiatingScope() = CURRENT_SKOPE.get()
    ?: throw IllegalStateException("use skoped() to declare ${javaClass.simpleName}")

fun KoinScopeComponent.instantiatingSkope(): Skope? = CURRENT_SKOPE.get()

tlevavasseur-decathlon avatar Aug 23 '22 11:08 tlevavasseur-decathlon

Parking your proposal in 4.0.0 for further new scope improvements 👍

arnaudgiuliani avatar Aug 29 '22 13:08 arnaudgiuliani

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jan 26 '23 13:01 stale[bot]

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jun 25 '23 13:06 stale[bot]