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..1cb5c27a2 --- /dev/null +++ b/projects/compose/koin-compose-viewmodel/build.gradle.kts @@ -0,0 +1,52 @@ +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() + } + + js(IR) { + nodejs() + browser() + binaries.executable() + } + + wasmJs { + binaries.executable() + nodejs() + } + + iosX64() + iosArm64() + iosSimulatorArm64() + macosX64() + macosArm64() + + sourceSets { + commonMain.dependencies { + api(project(":compose:koin-compose")) + api(libs.compose.viewmodel) + api(libs.compose.navigation) + } + } +} + +rootProject.the().apply { + nodeVersion = "21.0.0-v8-canary202309143a48826a08" + nodeDownloadBaseUrl = "https://nodejs.org/download/v8-canary" +} + +tasks.withType().configureEach { + args.add("--ignore-engines") +} + +apply(from = file("../../gradle/publish.gradle.kts")) diff --git a/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/NavViewModel.kt b/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/NavViewModel.kt new file mode 100644 index 000000000..6e7aacdaf --- /dev/null +++ b/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/NavViewModel.kt @@ -0,0 +1,59 @@ +/* + * 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.viewmodel + +import androidx.compose.runtime.Composable +import androidx.lifecycle.* +import androidx.lifecycle.viewmodel.CreationExtras +import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner +import org.koin.compose.currentKoinScope +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 + +/* + Ported directly from Android side. Waiting more feedback + */ + +/** + * Resolve ViewModel instance with Navigation NavBackStackEntry as extras parameters + * + * @param qualifier + * @param parameters + * + * @author Arnaud Giuliani + */ +@OptIn(KoinInternalApi::class) +@KoinExperimentalAPI +@Composable +inline fun koinNavViewModel( + qualifier: Qualifier? = null, + viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) { + "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner" + }, + key: String? = null, + extras: CreationExtras = defaultNavExtras(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/viewmodel/NavViewModelInternals.kt b/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/NavViewModelInternals.kt new file mode 100644 index 000000000..5d7ecd518 --- /dev/null +++ b/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/NavViewModelInternals.kt @@ -0,0 +1,62 @@ +/* + * 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.viewmodel + +import androidx.compose.runtime.Composable +import androidx.core.bundle.Bundle +import androidx.lifecycle.* +import androidx.lifecycle.viewmodel.CreationExtras +import androidx.lifecycle.viewmodel.MutableCreationExtras +import androidx.navigation.NavBackStackEntry +import androidx.savedstate.SavedStateRegistryOwner +import org.koin.core.annotation.KoinInternalApi + +/** + * Resolve ViewModel instance + * + * @param qualifier + * @param parameters + * + * @author Arnaud Giuliani + */ +@OptIn(KoinInternalApi::class) +@Composable +fun defaultNavExtras(viewModelStoreOwner: ViewModelStoreOwner): CreationExtras = when { + //TODO To be fully verified + viewModelStoreOwner is NavBackStackEntry && viewModelStoreOwner.arguments != null -> viewModelStoreOwner.arguments?.toExtras(viewModelStoreOwner) ?: CreationExtras.Empty + viewModelStoreOwner is HasDefaultViewModelProviderFactory -> viewModelStoreOwner.defaultViewModelCreationExtras + else -> CreationExtras.Empty +} + +/** + * Convert current Bundle to CreationExtras + * @param viewModelStoreOwner + */ +@KoinInternalApi +fun Bundle.toExtras(viewModelStoreOwner: ViewModelStoreOwner): CreationExtras? { + return if (keySet().isEmpty()) null + else { + runCatching { + MutableCreationExtras().also { extras -> + extras[DEFAULT_ARGS_KEY] = this + extras[VIEW_MODEL_STORE_OWNER_KEY] = viewModelStoreOwner + extras[SAVED_STATE_REGISTRY_OWNER_KEY] = viewModelStoreOwner as SavedStateRegistryOwner + } + }.getOrNull() + } +} \ No newline at end of file diff --git a/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/ViewModel.kt b/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/ViewModel.kt new file mode 100644 index 000000000..03ba8c350 --- /dev/null +++ b/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/ViewModel.kt @@ -0,0 +1,60 @@ +/* + * 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.viewmodel + +import androidx.compose.runtime.Composable +import androidx.lifecycle.* +import androidx.lifecycle.viewmodel.CreationExtras +import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner +import org.koin.compose.currentKoinScope +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 + +/* + Ported directly from Android side. Waiting more feedback + */ + +/** + * 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/viewmodel/ViewModelInternals.kt b/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/ViewModelInternals.kt new file mode 100644 index 000000000..6178f61cd --- /dev/null +++ b/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/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.viewmodel + +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 org.koin.mp.KoinPlatformTools +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, KoinPlatformTools.getClassName(vmClass)) + 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 Should handle Extras/Bundle injection here (see Android side) + 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-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/dsl/ModuleExt.kt b/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/dsl/ModuleExt.kt new file mode 100644 index 000000000..bacaaaebb --- /dev/null +++ b/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/dsl/ModuleExt.kt @@ -0,0 +1,40 @@ +/* + * 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. + */ +package org.koin.compose.viewmodel.dsl + +import androidx.lifecycle.ViewModel +import org.koin.core.definition.Definition +import org.koin.core.definition.KoinDefinition +import org.koin.core.module.Module +import org.koin.core.qualifier.Qualifier + +//TODO Ported from Android side + +/** + * ViewModel DSL Extension + * Allow to declare a ViewModel - be later inject into Activity/Fragment with dedicated injector + * + * @author Arnaud Giuliani + * + * @param qualifier - definition qualifier + * @param definition - allow definition override + */ +inline fun Module.viewModel( + qualifier: Qualifier? = null, + noinline definition: Definition +): KoinDefinition { + return factory(qualifier, definition) +} diff --git a/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/dsl/ScopeSetExt.kt b/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/dsl/ScopeSetExt.kt new file mode 100644 index 000000000..1d663df70 --- /dev/null +++ b/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/dsl/ScopeSetExt.kt @@ -0,0 +1,38 @@ +/* + * 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. + */ +package org.koin.compose.viewmodel.dsl + +import androidx.lifecycle.ViewModel +import org.koin.core.definition.Definition +import org.koin.core.definition.KoinDefinition +import org.koin.core.qualifier.Qualifier +import org.koin.dsl.ScopeDSL + +/** + * ViewModel DSL Extension + * Allow to declare a ViewModel - be later inject into Activity/Fragment with dedicated injector + * + * @author Arnaud Giuliani + * + * @param qualifier - definition qualifier + * @param definition - allow definition override + */ +inline fun ScopeDSL.viewModel( + qualifier: Qualifier? = null, + noinline definition: Definition +): KoinDefinition { + return factory(qualifier, definition) +} \ No newline at end of file diff --git a/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/dsl/ScopeViewModelOf.kt b/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/dsl/ScopeViewModelOf.kt new file mode 100644 index 000000000..36b0cd95d --- /dev/null +++ b/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/dsl/ScopeViewModelOf.kt @@ -0,0 +1,224 @@ +/* + * 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. + */ + +package org.koin.compose.viewmodel.dsl + +import androidx.lifecycle.ViewModel +import org.koin.core.annotation.KoinInternalApi +import org.koin.core.definition.KoinDefinition +import org.koin.core.module.dsl.DefinitionOptions +import org.koin.core.module.dsl.new +import org.koin.core.module.dsl.onOptions +import org.koin.dsl.ScopeDSL + +/** + * Declare a [ScopeDSL.viewModel] definition by resolving a constructor reference for the dependency. + * The resolution is done at compile time by leveraging inline functions, no reflection is required. + * + * Example: + * ``` + * class MyViewModel : ViewModel() + * + * val myModule = module { + * viewModelOf(::MyViewModel) + * } + * ``` + * + * @author Arnaud Giuliani + * + * @see new - function operator + */ + +inline fun ScopeDSL.viewModelOf( + crossinline constructor: () -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2, T3) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2, T3, T4) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun ScopeDSL.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) \ No newline at end of file diff --git a/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/dsl/ViewModelOf.kt b/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/dsl/ViewModelOf.kt new file mode 100644 index 000000000..aa6c49ff5 --- /dev/null +++ b/projects/compose/koin-compose-viewmodel/src/commonMain/kotlin/org/koin/compose/viewmodel/dsl/ViewModelOf.kt @@ -0,0 +1,223 @@ +/* + * 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. + */ + +package org.koin.compose.viewmodel.dsl + +import androidx.lifecycle.ViewModel +import org.koin.core.annotation.KoinInternalApi +import org.koin.core.definition.KoinDefinition +import org.koin.core.module.Module +import org.koin.core.module.dsl.DefinitionOptions +import org.koin.core.module.dsl.new +import org.koin.core.module.dsl.onOptions + +/** + * Declare a [Module.viewModel] definition by resolving a constructor reference for the dependency. + * The resolution is done at compile time by leveraging inline functions, no reflection is required. + * + * Example: + * ``` + * class MyViewModel : ViewModel() + * + * val myModule = module { + * viewModelOf(::MyViewModel) + * } + * ``` + * + * @author Arnaud Giuliani + * + * @see new + */ +inline fun Module.viewModelOf( + crossinline constructor: () -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2, T3) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2, T3, T4) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) + +/** + * @see viewModelOf + */ +inline fun Module.viewModelOf( + crossinline constructor: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22) -> R, + noinline options: DefinitionOptions? = null, +): KoinDefinition = viewModel { new(constructor) }.onOptions(options) diff --git a/projects/gradle.properties b/projects/gradle.properties index c3c1688ce..1580106f8 100644 --- a/projects/gradle.properties +++ b/projects/gradle.properties @@ -12,9 +12,13 @@ koinVersion=3.6.0-Beta4 koinComposeVersion=1.2.0-Beta4 #Compose -jetpackComposeCompiler=1.5.11 +jetpackComposeCompiler=1.5.14 +org.jetbrains.compose.experimental.jscanvas.enabled=true +org.jetbrains.compose.experimental.macos.enabled=true + #Android android.useAndroidX=true androidMinSDK=14 androidCompileSDK=34 #android.nonTransitiveRClass=true + diff --git a/projects/gradle/libs.versions.toml b/projects/gradle/libs.versions.toml index 6d8ba4df4..d05d801a2 100644 --- a/projects/gradle/libs.versions.toml +++ b/projects/gradle/libs.versions.toml @@ -3,25 +3,25 @@ # /!\ Koin in gradle.properties /!\ # Core -kotlin = "1.9.23" +kotlin = "1.9.24" binaryValidator = "0.13.2" publish = "2.0.0-rc-1" -coroutines = "1.8.0" # "1.8.0-RC2" for wasm +coroutines = "1.8.0" dokka = "1.9.10" # Android 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-rc03" +composeLifecycle = "2.8.0-rc03" +composeNavigation = "2.7.0-alpha06" composeJetpackRuntime = "1.6.7" -composeJetpackViewmodel = "2.7.0" # Test stately = "2.0.6" junit = "4.13.2" @@ -49,8 +49,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 @@ -60,8 +60,10 @@ ktor-testHost = { module = "io.ktor:ktor-server-test-host", version.ref = "ktor" ktor-slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } # Compose compose-jb = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "composeJB" } +compose-viewmodel = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "composeLifecycle" } +compose-navigation = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "composeNavigation" } 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