From be699657ac45de84537f8e18d1cf0473411f04ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Braun?= Date: Thu, 3 Sep 2026 15:17:06 +0200 Subject: [PATCH 1/4] Update AndroidX dependency and remove nonAndroidX source group --- gradle/libs.versions.toml | 2 +- kmp-observableviewmodel-core/build.gradle.kts | 2 - .../kmp/observableviewmodel/Closeables.kt | 56 ------------- .../kmp/observableviewmodel/ViewModel.kt | 80 ------------------- 4 files changed, 1 insertion(+), 139 deletions(-) delete mode 100644 kmp-observableviewmodel-core/src/nonAndroidxMain/kotlin/com/rickclephas/kmp/observableviewmodel/Closeables.kt delete mode 100644 kmp-observableviewmodel-core/src/nonAndroidxMain/kotlin/com/rickclephas/kmp/observableviewmodel/ViewModel.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6248a9e..64ecfa4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin = "2.4.10" kotlinx-coroutines = "1.10.1" android = "8.5.2" -androidx-lifecycle = "2.8.7" +androidx-lifecycle = "2.9.2" atomicfu = "0.32.1" # Sample versions diff --git a/kmp-observableviewmodel-core/build.gradle.kts b/kmp-observableviewmodel-core/build.gradle.kts index 09d5a82..e099df9 100644 --- a/kmp-observableviewmodel-core/build.gradle.kts +++ b/kmp-observableviewmodel-core/build.gradle.kts @@ -21,8 +21,6 @@ kotlin { withJvm() withLinuxX64() group("macos") - } - group("nonAndroidx") { withJs() withLinuxArm64() group("mingw") diff --git a/kmp-observableviewmodel-core/src/nonAndroidxMain/kotlin/com/rickclephas/kmp/observableviewmodel/Closeables.kt b/kmp-observableviewmodel-core/src/nonAndroidxMain/kotlin/com/rickclephas/kmp/observableviewmodel/Closeables.kt deleted file mode 100644 index 8a47c02..0000000 --- a/kmp-observableviewmodel-core/src/nonAndroidxMain/kotlin/com/rickclephas/kmp/observableviewmodel/Closeables.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.rickclephas.kmp.observableviewmodel - -import kotlinx.atomicfu.locks.SynchronizedObject -import kotlinx.atomicfu.locks.synchronized - -/** - * A collection of [AutoCloseable]s behaving similar to the - * [AndroidX ViewModel implementation](https://cs.android.com/androidx/platform/frameworks/support/+/58618bec2592c538b0a9e469f1492365ef2233e3:lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/internal/ViewModelImpl.kt). - */ -internal class Closeables( - private val closeables: MutableSet = mutableSetOf(), - private val keyedCloseables: MutableMap = mutableMapOf() -): SynchronizedObject(), AutoCloseable { - - private var isClosed = false - - operator fun plusAssign(closeable: AutoCloseable): Unit = synchronized(this) { - if (isClosed) { - closeWithRuntimeException(closeable) - return - } - closeables += closeable - } - - operator fun set(key: String, closeable: AutoCloseable): Unit = synchronized(this) { - if (isClosed) { - closeWithRuntimeException(closeable) - return - } - closeWithRuntimeException(keyedCloseables.put(key, closeable)) - } - - operator fun get(key: String): AutoCloseable? = synchronized(this) { - keyedCloseables[key] - } - - override fun close(): Unit = synchronized(this) { - if (isClosed) return - isClosed = true - for (closeable in keyedCloseables.values) { - closeWithRuntimeException(closeable) - } - for (closeable in closeables) { - closeWithRuntimeException(closeable) - } - closeables.clear() - } - - private fun closeWithRuntimeException(closeable: AutoCloseable?) { - try { - closeable?.close() - } catch (e: Exception) { - throw RuntimeException(e) - } - } -} diff --git a/kmp-observableviewmodel-core/src/nonAndroidxMain/kotlin/com/rickclephas/kmp/observableviewmodel/ViewModel.kt b/kmp-observableviewmodel-core/src/nonAndroidxMain/kotlin/com/rickclephas/kmp/observableviewmodel/ViewModel.kt deleted file mode 100644 index 3c7fd94..0000000 --- a/kmp-observableviewmodel-core/src/nonAndroidxMain/kotlin/com/rickclephas/kmp/observableviewmodel/ViewModel.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.rickclephas.kmp.observableviewmodel - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.cancel - -/** - * A Kotlin Multiplatform ViewModel. - */ -public actual abstract class ViewModel { - - /** - * The [ViewModelScope] containing the [CoroutineScope] of this ViewModel. - */ - public actual val viewModelScope: ViewModelScope - - internal val closeables: Closeables - - public actual constructor(): this(DefaultCoroutineScope()) - - public actual constructor(coroutineScope: CoroutineScope) { - viewModelScope = ViewModelScope(coroutineScope) - closeables = Closeables() - } - - public actual constructor(vararg closeables: AutoCloseable): this(DefaultCoroutineScope(), *closeables) - - public actual constructor( - coroutineScope: CoroutineScope, - vararg closeables: AutoCloseable - ) { - viewModelScope = ViewModelScope(coroutineScope) - this.closeables = Closeables(closeables.toMutableSet()) - } - - /** - * Called when this ViewModel is no longer used and will be destroyed. - */ - public actual open fun onCleared() { } - - /** - * Should be called to clear the ViewModel once it's no longer being used. - */ - public fun clear() { - viewModelScope.coroutineScope.cancel() - closeables.close() - onCleared() - } -} - -/** - * Adds an [AutoCloseable] resource with an associated [key] to this [ViewModel]. - * The resource will be closed right before the [onCleared][ViewModel.onCleared] method is called. - * - * If the [key] already has a resource associated with it, the old resource will be replaced and closed immediately. - * - * If [onCleared][ViewModel.onCleared] has already been called, - * the provided resource will not be added and will be closed immediately. - */ -public actual fun ViewModel.addCloseable(key: String, closeable: AutoCloseable) { - closeables[key] = closeable -} - -/** - * Adds an [AutoCloseable] resource to this [ViewModel]. - * The resource will be closed right before the [onCleared][ViewModel.onCleared] method is called. - * - * If [onCleared][ViewModel.onCleared] has already been called, - * the provided resource will not be added and will be closed immediately. - */ -public actual fun ViewModel.addCloseable(closeable: AutoCloseable) { - closeables += closeable -} - -/** - * Returns the [AutoCloseable] resource associated to the given [key], - * or `null` if such a [key] is not present in this [ViewModel]. - */ -@Suppress("UNCHECKED_CAST") -public actual fun ViewModel.getCloseable(key: String): T? = - closeables[key] as T? From c33a55fdb2a4cfe2dcd2681aa0d27bc6758b93a1 Mon Sep 17 00:00:00 2001 From: Rick Clephas Date: Sun, 6 Sep 2026 13:42:27 +0200 Subject: [PATCH 2/4] Remove androidx source-set --- kmp-observableviewmodel-core/build.gradle.kts | 20 +--- .../kmp/observableviewmodel/ViewModel.kt | 94 ------------------- .../kmp/observableviewmodel/ViewModel.kt | 77 +++++++++------ 3 files changed, 48 insertions(+), 143 deletions(-) delete mode 100644 kmp-observableviewmodel-core/src/androidxMain/kotlin/com/rickclephas/kmp/observableviewmodel/ViewModel.kt diff --git a/kmp-observableviewmodel-core/build.gradle.kts b/kmp-observableviewmodel-core/build.gradle.kts index e099df9..45d87e8 100644 --- a/kmp-observableviewmodel-core/build.gradle.kts +++ b/kmp-observableviewmodel-core/build.gradle.kts @@ -15,19 +15,6 @@ kotlin { @OptIn(ExperimentalKotlinGradlePluginApi::class) applyDefaultHierarchyTemplate { common { - group("androidx") { - withAndroidTarget() - group("ios") - withJvm() - withLinuxX64() - group("macos") - withJs() - withLinuxArm64() - group("mingw") - group("tvos") - withWasmJs() - group("watchos") - } group("nonApple") { withAndroidTarget() withJvm() @@ -88,6 +75,7 @@ kotlin { commonMain { dependencies { api(libs.kotlinx.coroutines.core) + api(libs.androidx.lifecycle.viewmodel) } } commonTest { @@ -95,12 +83,6 @@ kotlin { implementation(libs.kotlin.test) } } - - val androidxMain by getting { - dependencies { - api(libs.androidx.lifecycle.viewmodel) - } - } } } diff --git a/kmp-observableviewmodel-core/src/androidxMain/kotlin/com/rickclephas/kmp/observableviewmodel/ViewModel.kt b/kmp-observableviewmodel-core/src/androidxMain/kotlin/com/rickclephas/kmp/observableviewmodel/ViewModel.kt deleted file mode 100644 index c2eafbe..0000000 --- a/kmp-observableviewmodel-core/src/androidxMain/kotlin/com/rickclephas/kmp/observableviewmodel/ViewModel.kt +++ /dev/null @@ -1,94 +0,0 @@ -package com.rickclephas.kmp.observableviewmodel - -import androidx.lifecycle.ViewModel as AndroidXViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.ViewModelStore -import androidx.lifecycle.viewmodel.CreationExtras -import kotlinx.coroutines.CoroutineScope -import kotlin.reflect.KClass - -/** - * A Kotlin Multiplatform ViewModel. - */ -public actual abstract class ViewModel: AndroidXViewModel { - - /** - * The [ViewModelScope] containing the [CoroutineScope] of this ViewModel. - */ - public actual val viewModelScope: ViewModelScope - - public actual constructor(): this(DefaultCoroutineScope()) - - public actual constructor(coroutineScope: CoroutineScope): super(coroutineScope) { - viewModelScope = ViewModelScope(coroutineScope) - } - - public actual constructor(vararg closeables: AutoCloseable): this(DefaultCoroutineScope(), *closeables) - - public actual constructor( - coroutineScope: CoroutineScope, - vararg closeables: AutoCloseable - ): super(coroutineScope, *closeables) { - viewModelScope = ViewModelScope(coroutineScope) - } - - /** - * Called when this ViewModel is no longer used and will be destroyed. - */ - public actual override fun onCleared() { - super.onCleared() - } - - /** - * Internal KMP-ObservableViewModel function used by the Swift implementation to clear the ViewModel. - * Warning: you should NOT call this yourself! - */ - @InternalKMPObservableViewModelApi - public fun clear() { - // We can't directly call the internal clear function from AndroidX. - // To call it indirectly we use the public Store and Provider APIs instead. - val store = ViewModelStore() - ViewModelProvider.create( - store = store, - factory = object : ViewModelProvider.Factory { - override fun create(modelClass: KClass, extras: CreationExtras): T { - @Suppress("UNCHECKED_CAST") - return this@ViewModel as T - } - } - )[ViewModel::class] - store.clear() - } -} - -/** - * Adds an [AutoCloseable] resource with an associated [key] to this [ViewModel]. - * The resource will be closed right before the [onCleared][ViewModel.onCleared] method is called. - * - * If the [key] already has a resource associated with it, the old resource will be replaced and closed immediately. - * - * If [onCleared][ViewModel.onCleared] has already been called, - * the provided resource will not be added and will be closed immediately. - */ -@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "NOTHING_TO_INLINE") -public actual inline fun ViewModel.addCloseable(key: String, closeable: AutoCloseable): Unit = - addCloseable(key, closeable) - -/** - * Adds an [AutoCloseable] resource to this [ViewModel]. - * The resource will be closed right before the [onCleared][ViewModel.onCleared] method is called. - * - * If [onCleared][ViewModel.onCleared] has already been called, - * the provided resource will not be added and will be closed immediately. - */ -@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "NOTHING_TO_INLINE") -public actual inline fun ViewModel.addCloseable(closeable: AutoCloseable): Unit = - addCloseable(closeable) - -/** - * Returns the [AutoCloseable] resource associated to the given [key], - * or `null` if such a [key] is not present in this [ViewModel]. - */ -@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "NOTHING_TO_INLINE") -public actual inline fun ViewModel.getCloseable(key: String): T? = - getCloseable(key) diff --git a/kmp-observableviewmodel-core/src/commonMain/kotlin/com/rickclephas/kmp/observableviewmodel/ViewModel.kt b/kmp-observableviewmodel-core/src/commonMain/kotlin/com/rickclephas/kmp/observableviewmodel/ViewModel.kt index a0c5a48..60b1cb9 100644 --- a/kmp-observableviewmodel-core/src/commonMain/kotlin/com/rickclephas/kmp/observableviewmodel/ViewModel.kt +++ b/kmp-observableviewmodel-core/src/commonMain/kotlin/com/rickclephas/kmp/observableviewmodel/ViewModel.kt @@ -1,53 +1,70 @@ package com.rickclephas.kmp.observableviewmodel +import androidx.lifecycle.ViewModel as AndroidXViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelStore +import androidx.lifecycle.viewmodel.CreationExtras import kotlinx.coroutines.CoroutineScope +import kotlin.reflect.KClass /** * A Kotlin Multiplatform ViewModel. */ -public expect abstract class ViewModel { +public abstract class ViewModel: AndroidXViewModel { /** * The [ViewModelScope] containing the [CoroutineScope] of this ViewModel. */ public val viewModelScope: ViewModelScope - public constructor() + public constructor(): this(DefaultCoroutineScope()) - public constructor(coroutineScope: CoroutineScope) + public constructor(coroutineScope: CoroutineScope): super(coroutineScope) { + viewModelScope = ViewModelScope(coroutineScope) + } - public constructor(vararg closeables: AutoCloseable) + public constructor(vararg closeables: AutoCloseable): this(DefaultCoroutineScope(), *closeables) - public constructor(coroutineScope: CoroutineScope, vararg closeables: AutoCloseable) + public constructor( + coroutineScope: CoroutineScope, + vararg closeables: AutoCloseable + ): super(coroutineScope, *closeables) { + viewModelScope = ViewModelScope(coroutineScope) + } /** - * Called when this ViewModel is no longer used and will be destroyed. + * Internal KMP-ObservableViewModel function used by the Swift implementation to clear the ViewModel. + * Warning: you should NOT call this yourself! */ - public open fun onCleared() + @InternalKMPObservableViewModelApi + public fun clear() { + // We can't directly call the internal clear function from AndroidX. + // To call it indirectly we use the public Store and Provider APIs instead. + val store = ViewModelStore() + ViewModelProvider.create( + store = store, + factory = object : ViewModelProvider.Factory { + override fun create(modelClass: KClass, extras: CreationExtras): T { + @Suppress("UNCHECKED_CAST") + return this@ViewModel as T + } + } + )[ViewModel::class] + store.clear() + } } -/** - * Adds an [AutoCloseable] resource with an associated [key] to this [ViewModel]. - * The resource will be closed right before the [onCleared][ViewModel.onCleared] method is called. - * - * If the [key] already has a resource associated with it, the old resource will be replaced and closed immediately. - * - * If [onCleared][ViewModel.onCleared] has already been called, - * the provided resource will not be added and will be closed immediately. - */ -public expect fun ViewModel.addCloseable(key: String, closeable: AutoCloseable) +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "NOTHING_TO_INLINE", "DeprecatedCallableAddReplaceWith") +@Deprecated("Use addCloseable on AndroidX ViewModel directly") +public inline fun ViewModel.addCloseable(key: String, closeable: AutoCloseable): Unit = + addCloseable(key, closeable) -/** - * Adds an [AutoCloseable] resource to this [ViewModel]. - * The resource will be closed right before the [onCleared][ViewModel.onCleared] method is called. - * - * If [onCleared][ViewModel.onCleared] has already been called, - * the provided resource will not be added and will be closed immediately. - */ -public expect fun ViewModel.addCloseable(closeable: AutoCloseable) +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "NOTHING_TO_INLINE", "DeprecatedCallableAddReplaceWith") +@Deprecated("Use addCloseable on AndroidX ViewModel directly") +public inline fun ViewModel.addCloseable(closeable: AutoCloseable): Unit = + addCloseable(closeable) -/** - * Returns the [AutoCloseable] resource associated to the given [key], - * or `null` if such a [key] is not present in this [ViewModel]. - */ -public expect fun ViewModel.getCloseable(key: String): T? +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "NOTHING_TO_INLINE", "DeprecatedCallableAddReplaceWith") +@Deprecated("Use getCloseable on AndroidX ViewModel directly") +public inline fun ViewModel.getCloseable(key: String): T? = + getCloseable(key) From 96e515f44053c042893f9525e0c74d5acb70613c Mon Sep 17 00:00:00 2001 From: Rick Clephas Date: Sun, 6 Sep 2026 13:42:46 +0200 Subject: [PATCH 3/4] Update AndroidX supported targets --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index af10629..9874766 100644 --- a/README.md +++ b/README.md @@ -5,21 +5,21 @@ A library (previously known as KMM-ViewModel) that allows you to use AndroidX/Ko ## Compatibility You can use this library in any KMP project, -but not all targets support AndroidX and/or SwiftUI interop: - -| Target | Supported | AndroidX | SwiftUI | -|------------|:-----------:|:--------:|:-------:| -| Android | ✅ | ✅ | - | -| JVM | ✅ | ✅ | - | -| iOS | ✅ | ✅ | ✅ | -| macOS | ✅ | ✅ | ✅ | -| tvOS | ✅ | - | ✅ | -| watchOS | ✅ | - | ✅ | -| linuxX64 | ✅ | ✅ | - | -| linuxArm64 | ✅ | - | - | -| mingwX64 | ✅ | - | - | -| JS | ✅ | - | - | -| Wasm | ✅ | - | - | +but not all targets support SwiftUI interop: + +| Target | Supported | AndroidX | SwiftUI | +|------------|:---------:|:--------:|:-------:| +| Android | ✅ | ✅ | - | +| JVM | ✅ | ✅ | - | +| iOS | ✅ | ✅ | ✅ | +| macOS | ✅ | ✅ | ✅ | +| tvOS | ✅ | ✅ | ✅ | +| watchOS | ✅ | ✅ | ✅ | +| linuxX64 | ✅ | ✅ | - | +| linuxArm64 | ✅ | ✅ | - | +| mingwX64 | ✅ | ✅ | - | +| JS | ✅ | ✅ | - | +| Wasm | ✅ | ✅ | - | The latest version of the library uses Kotlin version `2.4.10`. Compatibility versions for older and/or preview Kotlin versions are also available: From 926e1c34e46c44b0c48885f40536a93edc114e53 Mon Sep 17 00:00:00 2001 From: Rick Clephas Date: Sun, 6 Sep 2026 13:50:45 +0200 Subject: [PATCH 4/4] Update AGP to 8.6.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 64ecfa4..773167d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.4.10" kotlinx-coroutines = "1.10.1" -android = "8.5.2" +android = "8.6.0" androidx-lifecycle = "2.9.2" atomicfu = "0.32.1"