diff --git a/compose/koin-androidx-compose-navigation/src/main/java/org/koin/androidx/compose/navigation/NavViewModel.kt b/compose/koin-androidx-compose-navigation/src/main/java/org/koin/androidx/compose/navigation/NavViewModel.kt index abaaeaefd..456ea67cf 100644 --- a/compose/koin-androidx-compose-navigation/src/main/java/org/koin/androidx/compose/navigation/NavViewModel.kt +++ b/compose/koin-androidx-compose-navigation/src/main/java/org/koin/androidx/compose/navigation/NavViewModel.kt @@ -18,11 +18,12 @@ package org.koin.androidx.compose.navigation import androidx.compose.runtime.Composable -import androidx.lifecycle.* +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelStoreOwner 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.core.annotation.KoinInternalApi import org.koin.core.parameter.ParametersDefinition import org.koin.core.qualifier.Qualifier @@ -45,10 +46,10 @@ inline fun koinNavViewModel( }, key: String? = null, extras: CreationExtras = defaultNavExtras(viewModelStoreOwner), - scope: Scope = LocalKoinScope.current, + scope: Scope = getKoinScope(), noinline parameters: ParametersDefinition? = null, ): T { return resolveViewModel( T::class, viewModelStoreOwner.viewModelStore, key, extras, qualifier, scope, parameters ) -} \ No newline at end of file +} diff --git a/compose/koin-androidx-compose/src/main/java/org/koin/androidx/compose/KoinApplication.kt b/compose/koin-androidx-compose/src/main/java/org/koin/androidx/compose/KoinApplication.kt new file mode 100644 index 000000000..c6cee3666 --- /dev/null +++ b/compose/koin-androidx-compose/src/main/java/org/koin/androidx/compose/KoinApplication.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2017-2023 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. + */ +@file:OptIn(KoinInternalApi::class) + +package org.koin.androidx.compose + +import android.app.Application +import android.content.ComponentCallbacks +import android.content.Context +import android.content.ContextWrapper +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import org.koin.android.ext.android.getKoin +import org.koin.compose.LocalKoinApplication +import org.koin.compose.LocalKoinScope +import org.koin.core.annotation.KoinInternalApi +import org.koin.core.component.KoinComponent + +/** + * Provide active Koin application from Android [Context] to Compose + * + * @param content - following compose function + * + * @author Jan-Jelle Kester + */ +@Composable +fun KoinApplication(content: @Composable () -> Unit) { + val context = LocalContext.current + val koinApplication = remember(context) { + context.findContextForKoin().getKoin() + } + CompositionLocalProvider( + LocalKoinApplication provides koinApplication, + LocalKoinScope provides koinApplication.scopeRegistry.rootScope, + content = content + ) +} + +/** + * Find the [KoinComponent] in the Context tree + */ +private fun Context.findContextForKoin(): ComponentCallbacks { + var context = this + while (context is ContextWrapper) { + if (context is KoinComponent && context is ComponentCallbacks) return context + context = context.baseContext + } + return applicationContext as Application +} diff --git a/compose/koin-androidx-compose/src/main/java/org/koin/androidx/compose/ViewModel.kt b/compose/koin-androidx-compose/src/main/java/org/koin/androidx/compose/ViewModel.kt index 8afc4e127..340cf8121 100644 --- a/compose/koin-androidx-compose/src/main/java/org/koin/androidx/compose/ViewModel.kt +++ b/compose/koin-androidx-compose/src/main/java/org/koin/androidx/compose/ViewModel.kt @@ -18,11 +18,12 @@ package org.koin.androidx.compose import androidx.compose.runtime.Composable -import androidx.lifecycle.* +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelStoreOwner 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.core.annotation.KoinInternalApi import org.koin.core.parameter.ParametersDefinition import org.koin.core.qualifier.Qualifier @@ -45,7 +46,7 @@ inline fun getViewModel( }, key: String? = null, extras: CreationExtras = defaultExtras(viewModelStoreOwner), - scope: Scope = LocalKoinScope.current, + scope: Scope = getKoinScope(), noinline parameters: ParametersDefinition? = null, ): T { return koinViewModel(qualifier, viewModelStoreOwner, key, extras, scope, parameters) @@ -60,10 +61,10 @@ inline fun koinViewModel( }, key: String? = null, extras: CreationExtras = defaultExtras(viewModelStoreOwner), - scope: Scope = LocalKoinScope.current, + scope: Scope = getKoinScope(), noinline parameters: ParametersDefinition? = null, ): T { return resolveViewModel( T::class, viewModelStoreOwner.viewModelStore, key, extras, qualifier, scope, parameters ) -} \ No newline at end of file +} diff --git a/compose/koin-compose/src/commonMain/kotlin/org/koin/compose/Inject.kt b/compose/koin-compose/src/commonMain/kotlin/org/koin/compose/Inject.kt index 7e210c065..ba4bc4149 100644 --- a/compose/koin-compose/src/commonMain/kotlin/org/koin/compose/Inject.kt +++ b/compose/koin-compose/src/commonMain/kotlin/org/koin/compose/Inject.kt @@ -33,7 +33,7 @@ import org.koin.core.scope.Scope @Composable inline fun koinInject( qualifier: Qualifier? = null, - scope: Scope = LocalKoinScope.current, + scope: Scope = getKoinScope(), noinline parameters: ParametersDefinition? = null, ): T = rememberKoinInject(qualifier, scope, parameters) @@ -47,10 +47,8 @@ inline fun koinInject( @Composable inline fun rememberKoinInject( qualifier: Qualifier? = null, - scope: Scope = LocalKoinScope.current, + scope: Scope = getKoinScope(), noinline parameters: ParametersDefinition? = null, ): T = remember(qualifier, scope, parameters) { scope.get(qualifier, parameters) } - - diff --git a/compose/koin-compose/src/commonMain/kotlin/org/koin/compose/KoinApplication.kt b/compose/koin-compose/src/commonMain/kotlin/org/koin/compose/KoinApplication.kt index bbd594175..6b689b22c 100644 --- a/compose/koin-compose/src/commonMain/kotlin/org/koin/compose/KoinApplication.kt +++ b/compose/koin-compose/src/commonMain/kotlin/org/koin/compose/KoinApplication.kt @@ -19,10 +19,14 @@ package org.koin.compose import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.InternalComposeApi import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.currentComposer +import androidx.compose.runtime.remember import org.koin.core.Koin import org.koin.core.annotation.KoinInternalApi import org.koin.core.module.Module +import org.koin.core.scope.Scope import org.koin.dsl.KoinAppDeclaration import org.koin.dsl.koinApplication import org.koin.mp.KoinPlatformTools @@ -30,17 +34,51 @@ import org.koin.mp.KoinPlatformTools /** * Current Koin Application context */ -val LocalKoinApplication = compositionLocalOf { getKoinContext() } +val LocalKoinApplication = compositionLocalOf { throw UnknownKoinContext() } /** * Current Koin Scope */ -@OptIn(KoinInternalApi::class) -val LocalKoinScope = compositionLocalOf { getKoinContext().scopeRegistry.rootScope } +val LocalKoinScope = compositionLocalOf { throw UnknownKoinContext() } + +/** + * Marker exception indicating that no Koin context is present in the composition + * + * @author Jan-Jelle Kester + */ +internal class UnknownKoinContext : RuntimeException("No Koin context has been provided") + private fun getKoinContext() = KoinPlatformTools.defaultContext().get() +/** + * Retrieve the current Koin application from the composition. + */ +@OptIn(InternalComposeApi::class) +@Composable +fun getKoin(): Koin = currentComposer.run { + remember { + try { + consume(LocalKoinApplication) + } catch (_: UnknownKoinContext) { + getKoinContext() + } + } +} + +/** + * Retrieve the current Koin scope from the composition + */ +@OptIn(InternalComposeApi::class) @Composable -fun getKoin(): Koin = LocalKoinApplication.current +fun getKoinScope(): Scope = currentComposer.run { + remember { + try { + consume(LocalKoinScope) + } catch (_: UnknownKoinContext) { + getKoinContext().scopeRegistry.rootScope + } + } +} /** * Start Koin Application from Compose @@ -84,4 +122,4 @@ fun KoinApplication( ) { content() } -} \ No newline at end of file +}