Skip to content

Commit

Permalink
Merge pull request #1706 from InsertKoinIO/fix/compose_local_scope
Browse files Browse the repository at this point in the history
Fix compose local scope access
  • Loading branch information
arnaudgiuliani authored Nov 16, 2023
2 parents 8726cc0 + af7743a commit f54dd73
Show file tree
Hide file tree
Showing 11 changed files with 91 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import org.koin.androidx.viewmodel.resolveViewModel
import org.koin.compose.LocalKoinScope
import org.koin.compose.getKoinScope
import org.koin.compose.currentKoinScope
import org.koin.core.annotation.KoinInternalApi
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.qualifier.Qualifier
Expand All @@ -46,7 +46,7 @@ inline fun <reified T : ViewModel> koinNavViewModel(
},
key: String? = null,
extras: CreationExtras = defaultNavExtras(viewModelStoreOwner),
scope: Scope = getKoinScope(),
scope: Scope = currentKoinScope(),
noinline parameters: ParametersDefinition? = null,
): T {
return resolveViewModel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ package org.koin.androidx.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import org.koin.compose.getKoinScope
import org.koin.compose.currentKoinScope
import org.koin.compose.rememberCurrentKoinScope
import org.koin.core.Koin
import org.koin.core.annotation.KoinInternalApi
import org.koin.core.context.GlobalContext
Expand All @@ -40,7 +41,7 @@ import org.koin.core.scope.Scope
@Deprecated("use koinInject() instead")
inline fun <reified T> get(
qualifier: Qualifier? = null,
scope: Scope = getKoinScope(),
scope: Scope = currentKoinScope(),
noinline parameters: ParametersDefinition? = null,
): T = remember(qualifier, parameters, scope) {
scope.get(qualifier, parameters)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import org.koin.androidx.viewmodel.resolveViewModel
import org.koin.compose.LocalKoinScope
import org.koin.compose.getKoinScope
import org.koin.compose.currentKoinScope
import org.koin.compose.rememberCurrentKoinScope
import org.koin.core.annotation.KoinInternalApi
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.qualifier.Qualifier
Expand All @@ -39,14 +40,15 @@ import org.koin.core.scope.Scope
*/

@Composable
@Deprecated("use koinViewModel() instead", replaceWith = ReplaceWith("koinViewModel"))
inline fun <reified T : ViewModel> getViewModel(
qualifier: Qualifier? = null,
viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
"No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
},
key: String? = null,
extras: CreationExtras = defaultExtras(viewModelStoreOwner),
scope: Scope = getKoinScope(),
scope: Scope = rememberCurrentKoinScope(),
noinline parameters: ParametersDefinition? = null,
): T {
return koinViewModel(qualifier, viewModelStoreOwner, key, extras, scope, parameters)
Expand All @@ -61,7 +63,7 @@ inline fun <reified T : ViewModel> koinViewModel(
},
key: String? = null,
extras: CreationExtras = defaultExtras(viewModelStoreOwner),
scope: Scope = getKoinScope(),
scope: Scope = currentKoinScope(),
noinline parameters: ParametersDefinition? = null,
): T {
return resolveViewModel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalContext
import org.koin.android.scope.AndroidScopeComponent
import org.koin.compose.LocalKoinScope
import org.koin.compose.getKoinScope


@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,14 @@ import org.koin.core.scope.Scope
@Composable
inline fun <reified T> koinInject(
qualifier: Qualifier? = null,
scope: Scope = getKoinScope(),
scope: Scope = currentKoinScope(),
noinline parameters: ParametersDefinition? = null,
): T = rememberKoinInject(qualifier, scope, parameters)
): T {
val st = rememberStableParametersDefinition(parameters)
return remember(qualifier, scope) {
scope.get(qualifier, st.parametersDefinition)
}
}

/**
* alias of koinInject()
Expand All @@ -48,7 +53,7 @@ inline fun <reified T> koinInject(
@Composable
inline fun <reified T> rememberKoinInject(
qualifier: Qualifier? = null,
scope: Scope = getKoinScope(),
scope: Scope = rememberCurrentKoinScope(),
noinline parameters: ParametersDefinition? = null,
): T {
val st = rememberStableParametersDefinition(parameters)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ import org.koin.compose.error.UnknownKoinContext
import org.koin.core.Koin
import org.koin.core.KoinApplication
import org.koin.core.annotation.KoinInternalApi
import org.koin.core.module.Module
import org.koin.core.context.startKoin
import org.koin.core.error.ApplicationAlreadyStartedException
import org.koin.core.scope.Scope
import org.koin.dsl.KoinAppDeclaration
import org.koin.dsl.koinApplication
import org.koin.mp.KoinPlatformTools

/**
Expand Down Expand Up @@ -64,7 +64,7 @@ fun getKoin(): Koin = currentComposer.run {
consume(LocalKoinApplication)
} catch (_: UnknownKoinContext) {
val ctx = getKoinContext()
warningNoContext(ctx)
ctx.warnNoContext()
ctx
}
}
Expand All @@ -78,58 +78,65 @@ fun getKoin(): Koin = currentComposer.run {
*/
@OptIn(InternalComposeApi::class)
@Composable
fun getKoinScope(): Scope = currentComposer.run {
fun currentKoinScope(): Scope = currentComposer.run {
try {
consume(LocalKoinScope)
} catch (_: UnknownKoinContext) {
val ctx = getKoinContext()
ctx.warnNoContext()
getKoinContext().scopeRegistry.rootScope
}
}

/**
* Retrieve the current Koin scope from the composition
*
* @author @author jjkester
*
*/
@OptIn(InternalComposeApi::class)
@Composable
fun rememberCurrentKoinScope(): Scope = currentComposer.run {
remember {
try {
consume(LocalKoinScope)
} catch (_: UnknownKoinContext) {
val ctx = getKoinContext()
warningNoContext(ctx)
ctx.warnNoContext()
getKoinContext().scopeRegistry.rootScope
}
}
}

private fun warningNoContext(ctx: Koin) {
ctx.logger.debug("[Warning] - No Compose Koin context setup, taking default. Use KoinContext(), KoinAndroidContext() or KoinApplication() function to setup or create Koin context and avoid such message.")
private fun Koin.warnNoContext() {
logger.debug("[Warning] - No Compose Koin context setup, taking default. Use KoinContext(), KoinAndroidContext() or KoinApplication() function to setup or create Koin context and avoid such message.")
}

/**
* Start a new Koin Application in Compose context
* Start a new Koin Application and associate it for Compose context
* if Koin's Default Context is already set,
*
* @param application - Koin Application declaration lambda (like startKoin)
* @param content - following compose function
*
* @throws ApplicationAlreadyStartedException
* @author Arnaud Giuliani
*/
@Composable
@Throws(ApplicationAlreadyStartedException::class)
fun KoinApplication(
application: KoinAppDeclaration,
content: @Composable () -> Unit
) {
val koinApplication = remember(application) { koinApplication(appDeclaration = application) }
CompositionLocalProvider(
LocalKoinApplication provides koinApplication.koin,
LocalKoinScope provides koinApplication.koin.scopeRegistry.rootScope
) {
content()
val koinApplication = remember(application) {
val alreadyExists = KoinPlatformTools.defaultContext().getOrNull() != null
if (alreadyExists){
throw ApplicationAlreadyStartedException("Trying to run new Koin Application whereas Koin is already started. Use 'KoinContext()' instead of check for any 'startKoin' usage. ")
}
else {
startKoin(application)
}
}
}

/**
* Create a new Koin Application context for Compose
*
* @param moduleList - list of Modules to run within Koin Application
* @param content - following compose function
*
* @author Arnaud Giuliani
*/
@Composable
fun KoinApplication(
moduleList: () -> List<Module>,
content: @Composable () -> Unit
) {
val koinApplication = remember(moduleList) { koinApplication { modules(moduleList()) } }
CompositionLocalProvider(
LocalKoinApplication provides koinApplication.koin,
LocalKoinScope provides koinApplication.koin.scopeRegistry.rootScope
Expand All @@ -139,7 +146,7 @@ fun KoinApplication(
}

/**
* Run and bind Compose with existing Koin context
* Use Compose with existing Koin context, by default 'KoinPlatformTools.defaultContext()'
*
* @see KoinPlatformTools.defaultContext()
* @param content - following compose function
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import org.koin.compose.LocalKoinScope
import org.koin.compose.getKoin
import org.koin.core.Koin
import org.koin.core.annotation.KoinExperimentalAPI
import org.koin.core.component.getScopeId
import org.koin.core.qualifier.Qualifier
import org.koin.core.scope.Scope
import org.koin.core.scope.ScopeID
Expand All @@ -42,7 +41,7 @@ fun KoinScope(
content: @Composable () -> Unit
) {
val scope = scopeDefinition(getKoin())
RememberScope(scope, content)
OnKoinScope(scope, content)
}

/**
Expand All @@ -61,38 +60,7 @@ inline fun <reified T : Any> KoinScope(
noinline content: @Composable () -> Unit
) {
val scope = getKoin().getOrCreateScope<T>(scopeID)
RememberScope(scope, content)
}

@KoinExperimentalAPI
@Composable
@PublishedApi
internal fun RememberScope(scope: Scope, content: @Composable () -> Unit) {
rememberKoinScope(scope)
CompositionLocalProvider(
LocalKoinScope provides scope,
) {
content()
}
}

/**
* Create Koin Scope from context & close it when Composition is on onForgotten/onAbandoned
*
* @param context
*
* @see rememberKoinScope
*
* @author Arnaud Giuliani
*/
@KoinExperimentalAPI
@Composable
inline fun <reified T : Any> KoinScope(
context : Any,
noinline content: @Composable () -> Unit
) {
val scope = getKoin().getOrCreateScope<T>(context.getScopeId())
RememberScope(scope, content)
OnKoinScope(scope, content)
}

/**
Expand All @@ -113,5 +81,17 @@ inline fun KoinScope(
noinline content: @Composable () -> Unit
) {
val scope = getKoin().getOrCreateScope(scopeID, scopeQualifier)
RememberScope(scope, content)
OnKoinScope(scope, content)
}

@KoinExperimentalAPI
@Composable
@PublishedApi
internal fun OnKoinScope(scope: Scope, content: @Composable () -> Unit) {
rememberKoinScope(scope)
CompositionLocalProvider(
LocalKoinScope provides scope,
) {
content()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,18 @@ package org.koin.compose.scope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import org.koin.compose.getKoin
import org.koin.core.Koin
import org.koin.core.annotation.KoinExperimentalAPI
import org.koin.core.annotation.KoinInternalApi
import org.koin.core.qualifier.Qualifier
import org.koin.core.qualifier.StringQualifier
import org.koin.core.scope.Scope
import org.koin.core.scope.ScopeID

/**
* Remember Koin Scope & run CompositionKoinScopeLoader to handle scope closure
*
* @param scope - Koin scope
* @author Arnaud Giuliani
*/
@KoinExperimentalAPI
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ fun ViewModelComposable(
@Composable
fun SingleComposable(
parentStatus: String = "- status -",
mySingle: MySingle = rememberKoinInject()
mySingle: MySingle = koinInject()
) {
var created by remember { mutableStateOf(false) }

Expand All @@ -108,7 +108,7 @@ fun SingleComposable(
@Composable
fun FactoryComposable(
parentStatus: String = "- status -",
myFactory: MyFactory = rememberKoinInject { parametersOf("stable_status") }
myFactory: MyFactory = koinInject { parametersOf("stable_status") }
) {
var created by remember { mutableStateOf(false) }
rememberKoinModules(modules = { listOf(secondModule) })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import org.koin.compose.KoinIsolatedContext
import org.koin.compose.koinInject
import org.koin.compose.rememberKoinInject
import org.koin.sample.androidx.compose.data.sdk.SDKData
import org.koin.sample.androidx.compose.di.IsolatedContextSDK
Expand All @@ -23,7 +24,7 @@ fun IsolatedSDKComposable(
@Composable
private fun SDKComposable(
parentStatus: String = "- status -",
sdkData: SDKData = rememberKoinInject()
sdkData: SDKData = koinInject()
) {
var created by remember { mutableStateOf(false) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import org.koin.compose.KoinApplication
import org.koin.compose.koinInject
import org.koin.dsl.module
import java.util.*
import java.util.UUID

class Myfactory {
val id = UUID.randomUUID().toString()
Expand All @@ -23,9 +27,11 @@ val mod = module {
@Composable
@Preview
fun App() {
KoinApplication(application = {
modules(mod)
}) {
KoinApplication(
application = {
modules(mod)
}
) {
var text by remember { mutableStateOf("Hello, World!") }
val factory = koinInject<Myfactory>()
MaterialTheme {
Expand Down

0 comments on commit f54dd73

Please sign in to comment.