diff --git a/projects/compose/koin-compose-viewmodel/build.gradle.kts b/projects/compose/koin-compose-viewmodel/build.gradle.kts new file mode 100644 index 000000000..68f2a7401 --- /dev/null +++ b/projects/compose/koin-compose-viewmodel/build.gradle.kts @@ -0,0 +1,30 @@ +import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.compose) +} + +val koinComposeVersion: String by project +version = koinComposeVersion + +kotlin { + jvm { + withJava() + } + + iosX64() + iosArm64() + iosSimulatorArm64() + + sourceSets { + commonMain.dependencies { + api(project(":compose:koin-compose")) + api(libs.compose.jb) + api(libs.androidx.composeViewModel) + } + } +} + +apply(from = file("../../gradle/publish.gradle.kts")) diff --git a/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/ViewModel.kt b/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/ViewModel.kt new file mode 100644 index 000000000..4b757a52a --- /dev/null +++ b/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/ViewModel.kt @@ -0,0 +1,54 @@ +/* + * 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. + */ +@file:Suppress("DeprecatedCallableAddReplaceWith") + +package org.koin.compose + +import androidx.compose.runtime.Composable +import androidx.lifecycle.* +import androidx.lifecycle.viewmodel.CreationExtras +import org.koin.core.annotation.KoinExperimentalAPI +import org.koin.core.annotation.KoinInternalApi +import org.koin.core.parameter.ParametersDefinition +import org.koin.core.qualifier.Qualifier +import org.koin.core.scope.Scope + +/** + * Resolve ViewModel instance + * + * @param qualifier + * @param parameters + * + * @author Arnaud Giuliani + */ + +@OptIn(KoinInternalApi::class) +@KoinExperimentalAPI +@Composable +inline fun koinViewModel( + qualifier: Qualifier? = null, + viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) { + "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner" + }, + key: String? = null, + extras: CreationExtras = defaultExtras(viewModelStoreOwner), + scope: Scope = currentKoinScope(), + 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/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/ViewModelInternals.kt b/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/ViewModelInternals.kt new file mode 100644 index 000000000..611ef9a4f --- /dev/null +++ b/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/ViewModelInternals.kt @@ -0,0 +1,87 @@ +/* + * 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.compose + +import androidx.compose.runtime.Composable +import androidx.lifecycle.HasDefaultViewModelProviderFactory +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelStore +import androidx.lifecycle.ViewModelStoreOwner +import androidx.lifecycle.viewmodel.CreationExtras +import org.koin.core.annotation.KoinInternalApi +import org.koin.core.parameter.ParametersDefinition +import org.koin.core.parameter.ParametersHolder +import org.koin.core.qualifier.Qualifier +import org.koin.core.scope.Scope +import kotlin.reflect.KClass + +@KoinInternalApi +fun resolveViewModel( + vmClass: KClass, + viewModelStore: ViewModelStore, + key: String?, + extras: CreationExtras, + qualifier: Qualifier?, + scope: Scope, + parameters: (() -> ParametersHolder)? +): T { + val factory = KoinViewModelFactory(vmClass, scope, qualifier, parameters) + val provider = ViewModelProvider.create(viewModelStore, factory, extras) + val vmKey = getViewModelKey(qualifier, key, vmClass.qualifiedName) + return when { + vmKey != null -> provider[vmKey, vmClass] + else -> provider[vmClass] + } +} + +@KoinInternalApi +internal fun getViewModelKey(qualifier: Qualifier? = null, key: String? = null, className: String? = null): String? { + return when { + key != null -> key + qualifier != null -> qualifier.value + (className?.let { "_$className" } ?: "") + else -> null + } +} + +class KoinViewModelFactory( + private val kClass: KClass, + private val scope: Scope, + private val qualifier: Qualifier? = null, + private val params: ParametersDefinition? = null +) : ViewModelProvider.Factory { + + //TODO Handle Extras/Bundle + + override fun create(modelClass: KClass, extras: CreationExtras): T { + return scope.get(kClass, qualifier,params) + } +} + +/** + * Resolve ViewModel instance + * + * @param qualifier + * @param parameters + * + * @author Arnaud Giuliani + */ +@Composable +fun defaultExtras(viewModelStoreOwner: ViewModelStoreOwner): CreationExtras = when { + viewModelStoreOwner is HasDefaultViewModelProviderFactory -> viewModelStoreOwner.defaultViewModelCreationExtras + else -> CreationExtras.Empty +} \ No newline at end of file diff --git a/projects/compose/koin-compose/src/commonMain/kotlin/org/koin/compose/ViewModel.kt b/projects/compose/koin-compose/src/commonMain/kotlin/org/koin/compose/ViewModel.kt new file mode 100644 index 000000000..b6cfc0ebb --- /dev/null +++ b/projects/compose/koin-compose/src/commonMain/kotlin/org/koin/compose/ViewModel.kt @@ -0,0 +1,55 @@ +/* + * 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. + */ +@file:Suppress("DeprecatedCallableAddReplaceWith") + +package org.koin.compose + +import androidx.compose.runtime.Composable +import androidx.lifecycle.* +import org.koin.compose.LocalKoinScope +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 +import org.koin.core.scope.Scope + +/** + * Resolve ViewModel instance + * + * @param qualifier + * @param parameters + * + * @author Arnaud Giuliani + */ + + +@OptIn(KoinInternalApi::class) +@Composable +inline fun koinViewModel( + qualifier: Qualifier? = null, + viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) { + "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner" + }, + key: String? = null, + extras: CreationExtras = defaultExtras(viewModelStoreOwner), + scope: Scope = currentKoinScope(), + 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/projects/gradle.properties b/projects/gradle.properties index c3c1688ce..37dbcb273 100644 --- a/projects/gradle.properties +++ b/projects/gradle.properties @@ -12,7 +12,7 @@ koinVersion=3.6.0-Beta4 koinComposeVersion=1.2.0-Beta4 #Compose -jetpackComposeCompiler=1.5.11 +jetpackComposeCompiler=1.5.13 #Android android.useAndroidX=true androidMinSDK=14 diff --git a/projects/gradle/libs.versions.toml b/projects/gradle/libs.versions.toml index 6d8ba4df4..27b72539a 100644 --- a/projects/gradle/libs.versions.toml +++ b/projects/gradle/libs.versions.toml @@ -12,16 +12,14 @@ dokka = "1.9.10" agp = "7.4.2" android-appcompat = "1.6.1" android-activity = "1.9.0" -android-fragment = "1.7.0" -androidx-viewmodel = "2.7.0" -androidx-commonJava8 = "2.7.0" +android-fragment = "1.7.1" +androidx-lifecycle = "2.8.0" androidx-workmanager = "2.9.0" androidx-navigation = "2.7.7" # Compose # /!\ Compose compiler in gradle.properties /!\ -composeJB = "1.6.2" +composeJB = "1.6.10-rc01" composeJetpackRuntime = "1.6.7" -composeJetpackViewmodel = "2.7.0" # Test stately = "2.0.6" junit = "4.13.2" @@ -49,8 +47,8 @@ test-mockk = { module = "io.mockk:mockk", version.ref = "mockk" } android-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "android-appcompat" } android-activity = { module = "androidx.activity:activity-ktx", version.ref = "android-activity" } android-fragment = { module = "androidx.fragment:fragment-ktx", version.ref = "android-fragment" } -androidx-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-viewmodel" } -androidx-commonJava8 = { module = "androidx.lifecycle:lifecycle-common-java8", version.ref = "androidx-commonJava8" } +androidx-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" } +androidx-commonJava8 = { module = "androidx.lifecycle:lifecycle-common-java8", version.ref = "androidx-lifecycle" } androidx-navigation = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "androidx-navigation" } androidx-workmanager = { module = "androidx.work:work-runtime-ktx", version.ref = "androidx-workmanager" } # Ktor @@ -61,7 +59,7 @@ ktor-slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } # Compose compose-jb = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "composeJB" } androidx-composeRuntime = { module = "androidx.compose.runtime:runtime", version.ref = "composeJetpackRuntime" } -androidx-composeViewModel = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "composeJetpackViewmodel" } +androidx-composeViewModel = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" } androidx-composeNavigation = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" } [plugins] diff --git a/projects/settings.gradle.kts b/projects/settings.gradle.kts index 0b569cdea..b1308923d 100644 --- a/projects/settings.gradle.kts +++ b/projects/settings.gradle.kts @@ -32,6 +32,7 @@ include( ":android:koin-android-test", // Compose ":compose:koin-compose", + ":compose:koin-compose-viewmodel", ":compose:koin-androidx-compose", ":compose:koin-androidx-compose-navigation", // Plugin