From 77f04fc7a0a99ae519bee27093027fc15184a7cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Mlynari=C4=8D?= Date: Tue, 15 Jul 2025 14:30:49 +0200 Subject: [PATCH 1/5] Export ViewModel dependency to remove Lifecycle prefixes in Swift --- .../iosApp/IOSViewModelStoreOwner.swift | 11 ++++---- Fruitties/shared/build.gradle.kts | 27 +++++++------------ .../di/viewmodel/ViewModelStoreUtil.kt | 5 ---- 3 files changed, 14 insertions(+), 29 deletions(-) diff --git a/Fruitties/iosApp/iosApp/IOSViewModelStoreOwner.swift b/Fruitties/iosApp/iosApp/IOSViewModelStoreOwner.swift index b7e9047..1c52262 100644 --- a/Fruitties/iosApp/iosApp/IOSViewModelStoreOwner.swift +++ b/Fruitties/iosApp/iosApp/IOSViewModelStoreOwner.swift @@ -3,16 +3,15 @@ import shared /// A ViewModelStoreOwner specifically for iOS. /// This is used with from iOS with Kotlin Multiplatform (KMP). -class IOSViewModelStoreOwner: ObservableObject, SwiftViewModelStoreOwner { +class IOSViewModelStoreOwner: ObservableObject, ViewModelStoreOwner { - var viewModelStore: Lifecycle_viewmodelViewModelStore = - Lifecycle_viewmodelViewModelStore() + var viewModelStore = ViewModelStore() /// This function allows retrieving the androidx ViewModel from the store. - func viewModel( + func viewModel( key: String? = nil, - factory: Lifecycle_viewmodelViewModelProviderFactory, - extras: Lifecycle_viewmodelCreationExtras? = nil + factory: ViewModelProviderFactory, + extras: CreationExtras? = nil ) -> T { do { return try viewModelStore.getViewModel( diff --git a/Fruitties/shared/build.gradle.kts b/Fruitties/shared/build.gradle.kts index 0a190e7..3cbcf0b 100644 --- a/Fruitties/shared/build.gradle.kts +++ b/Fruitties/shared/build.gradle.kts @@ -50,26 +50,17 @@ kotlin { // A step-by-step guide on how to include this library in an XCode // project can be found here: // https://developer.android.com/kotlin/multiplatform/migrate - val xcfName = "shared" - iosX64 { - binaries.framework { - baseName = xcfName + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64() + ).forEach { + it.binaries.framework { + export(libs.androidx.lifecycle.viewmodel) + baseName = "shared" } } - - iosArm64 { - binaries.framework { - baseName = xcfName - } - } - - iosSimulatorArm64 { - binaries.framework { - baseName = xcfName - } - } - // Source set declarations. // Declaring a target automatically creates a source set with the same name. By default, the // Kotlin Gradle Plugin creates additional source sets that depend on each other, since it is @@ -90,7 +81,7 @@ kotlin { implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.serialization.kotlinx.json) implementation(libs.skie.annotations) - implementation(libs.androidx.lifecycle.viewmodel) + api(libs.androidx.lifecycle.viewmodel) implementation(libs.androidx.paging.common) implementation(libs.androidx.room.runtime) implementation(libs.sqlite.bundled) diff --git a/Fruitties/shared/src/iosMain/kotlin/com/example/fruitties/di/viewmodel/ViewModelStoreUtil.kt b/Fruitties/shared/src/iosMain/kotlin/com/example/fruitties/di/viewmodel/ViewModelStoreUtil.kt index 89384e8..5c2a6db 100644 --- a/Fruitties/shared/src/iosMain/kotlin/com/example/fruitties/di/viewmodel/ViewModelStoreUtil.kt +++ b/Fruitties/shared/src/iosMain/kotlin/com/example/fruitties/di/viewmodel/ViewModelStoreUtil.kt @@ -31,8 +31,3 @@ fun ViewModelStore.getViewModel( return key?.let { provider[key, vmClass] } ?: provider[vmClass] } -/** - * The ViewModelStoreOwner isn't used anywhere in the project, therefore it's not visible for Swift by default. - */ -@Suppress("unused") -interface SwiftViewModelStoreOwner : ViewModelStoreOwner From aa47c9541fe47299f1cf04247e79094e7c423513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Mlynari=C4=8D?= Date: Tue, 15 Jul 2025 14:54:12 +0200 Subject: [PATCH 2/5] use VmStoreOwner instead of VmStore directly --- Fruitties/iosApp/iosApp/IOSViewModelStoreOwner.swift | 2 +- .../example/fruitties/di/viewmodel/ViewModelStoreUtil.kt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Fruitties/iosApp/iosApp/IOSViewModelStoreOwner.swift b/Fruitties/iosApp/iosApp/IOSViewModelStoreOwner.swift index 1c52262..c5c0451 100644 --- a/Fruitties/iosApp/iosApp/IOSViewModelStoreOwner.swift +++ b/Fruitties/iosApp/iosApp/IOSViewModelStoreOwner.swift @@ -14,7 +14,7 @@ class IOSViewModelStoreOwner: ObservableObject, ViewModelStoreOwner { extras: CreationExtras? = nil ) -> T { do { - return try viewModelStore.getViewModel( + return try viewModel( modelClass: T.self, factory: factory, key: key, diff --git a/Fruitties/shared/src/iosMain/kotlin/com/example/fruitties/di/viewmodel/ViewModelStoreUtil.kt b/Fruitties/shared/src/iosMain/kotlin/com/example/fruitties/di/viewmodel/ViewModelStoreUtil.kt index 5c2a6db..d1ed26a 100644 --- a/Fruitties/shared/src/iosMain/kotlin/com/example/fruitties/di/viewmodel/ViewModelStoreUtil.kt +++ b/Fruitties/shared/src/iosMain/kotlin/com/example/fruitties/di/viewmodel/ViewModelStoreUtil.kt @@ -10,6 +10,7 @@ import kotlinx.cinterop.ObjCClass import kotlinx.cinterop.getOriginalKotlinClass import kotlin.reflect.KClass + /** * This function allows retrieving any ViewModel from Swift Code with generics. * We only get [ObjCClass] type for the [modelClass], because the interop between Kotlin and Swift @@ -18,7 +19,7 @@ import kotlin.reflect.KClass @Suppress("unused") // Android Studio is not aware of iOS usage. @OptIn(BetaInteropApi::class) @Throws(IllegalStateException::class) -fun ViewModelStore.getViewModel( +fun ViewModelStoreOwner.viewModel( modelClass: ObjCClass, factory: ViewModelProvider.Factory, key: String?, @@ -28,6 +29,5 @@ fun ViewModelStore.getViewModel( val vmClass = getOriginalKotlinClass(modelClass) as? KClass ?: error("modelClass isn't a ViewModel type") val provider = ViewModelProvider.create(this, factory, extras ?: CreationExtras.Empty) - return key?.let { provider[key, vmClass] } ?: provider[vmClass] -} - + return key?.let { provider.get(key, vmClass) } ?: provider.get(vmClass) +} \ No newline at end of file From 2b8944b3a763ab8b1bc938030b1ab59d266cc702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Mlynari=C4=8D?= Date: Mon, 21 Jul 2025 08:17:26 +0200 Subject: [PATCH 3/5] Use StateObject for IOSViewModelSToreOwner directly --- Fruitties/iosApp/iosApp/IOSViewModelStoreOwner.swift | 9 +++++++-- Fruitties/iosApp/iosApp/ui/CartView.swift | 2 +- Fruitties/iosApp/iosApp/ui/ContentView.swift | 8 ++------ Fruitties/iosApp/iosApp/ui/FruittieScreen.swift | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Fruitties/iosApp/iosApp/IOSViewModelStoreOwner.swift b/Fruitties/iosApp/iosApp/IOSViewModelStoreOwner.swift index c5c0451..c992f8c 100644 --- a/Fruitties/iosApp/iosApp/IOSViewModelStoreOwner.swift +++ b/Fruitties/iosApp/iosApp/IOSViewModelStoreOwner.swift @@ -1,8 +1,7 @@ import SwiftUI import shared -/// A ViewModelStoreOwner specifically for iOS. -/// This is used with from iOS with Kotlin Multiplatform (KMP). +/// A ViewModelStoreOwner specifically for iOS to be an ObservableObject. class IOSViewModelStoreOwner: ObservableObject, ViewModelStoreOwner { var viewModelStore = ViewModelStore() @@ -25,7 +24,13 @@ class IOSViewModelStoreOwner: ObservableObject, ViewModelStoreOwner { } } + /// This can be called from outside when using the `ViewModelStoreOwnerProvider` func clear() { viewModelStore.clear() } + + /// This is called when this class is used as a `@StateObject` + deinit { + viewModelStore.clear() + } } diff --git a/Fruitties/iosApp/iosApp/ui/CartView.swift b/Fruitties/iosApp/iosApp/ui/CartView.swift index fc78eff..35f7d2b 100644 --- a/Fruitties/iosApp/iosApp/ui/CartView.swift +++ b/Fruitties/iosApp/iosApp/ui/CartView.swift @@ -20,7 +20,7 @@ import shared struct CartView: View { /// Injects the `IOSViewModelStoreOwner` from the environment, which manages the lifecycle of `ViewModel` instances. - @EnvironmentObject var viewModelStoreOwner: IOSViewModelStoreOwner + @StateObject var viewModelStoreOwner = IOSViewModelStoreOwner() /// Injects the `AppContainer` from the environment, providing access to application-wide dependencies. @EnvironmentObject var appContainer: ObservableValueWrapper diff --git a/Fruitties/iosApp/iosApp/ui/ContentView.swift b/Fruitties/iosApp/iosApp/ui/ContentView.swift index eddbb9c..c92a4d8 100644 --- a/Fruitties/iosApp/iosApp/ui/ContentView.swift +++ b/Fruitties/iosApp/iosApp/ui/ContentView.swift @@ -39,9 +39,7 @@ struct ContentView: View { value in HStack { NavigationLink { - ViewModelStoreOwnerProvider { - FruittieScreen(fruittie: value) - } + FruittieScreen(fruittie: value) } label: { FruittieView(fruittie: value) } @@ -62,9 +60,7 @@ struct ContentView: View { .toolbar { ToolbarItem(placement: .navigationBarTrailing) { NavigationLink { - ViewModelStoreOwnerProvider { - CartView() - } + CartView() } label: { Observing(mainViewModel.homeUiState) { homeUIState in let total = homeUIState.cartItemCount diff --git a/Fruitties/iosApp/iosApp/ui/FruittieScreen.swift b/Fruitties/iosApp/iosApp/ui/FruittieScreen.swift index c8c4d1f..61e55f9 100644 --- a/Fruitties/iosApp/iosApp/ui/FruittieScreen.swift +++ b/Fruitties/iosApp/iosApp/ui/FruittieScreen.swift @@ -19,7 +19,7 @@ import SwiftUI import shared struct FruittieScreen: View { - @EnvironmentObject var viewModelStoreOwner: IOSViewModelStoreOwner + @StateObject var viewModelStoreOwner = IOSViewModelStoreOwner() @EnvironmentObject var appContainer: ObservableValueWrapper let fruittie: Fruittie From 0584dc2e146cee001b759849ebe3637c90975641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Mlynari=C4=8D?= Date: Mon, 21 Jul 2025 08:18:29 +0200 Subject: [PATCH 4/5] Use get operator --- .../com/example/fruitties/di/viewmodel/ViewModelStoreUtil.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Fruitties/shared/src/iosMain/kotlin/com/example/fruitties/di/viewmodel/ViewModelStoreUtil.kt b/Fruitties/shared/src/iosMain/kotlin/com/example/fruitties/di/viewmodel/ViewModelStoreUtil.kt index d1ed26a..6521b63 100644 --- a/Fruitties/shared/src/iosMain/kotlin/com/example/fruitties/di/viewmodel/ViewModelStoreUtil.kt +++ b/Fruitties/shared/src/iosMain/kotlin/com/example/fruitties/di/viewmodel/ViewModelStoreUtil.kt @@ -29,5 +29,5 @@ fun ViewModelStoreOwner.viewModel( val vmClass = getOriginalKotlinClass(modelClass) as? KClass ?: error("modelClass isn't a ViewModel type") val provider = ViewModelProvider.create(this, factory, extras ?: CreationExtras.Empty) - return key?.let { provider.get(key, vmClass) } ?: provider.get(vmClass) + return key?.let { provider[key, vmClass] } ?: provider[vmClass] } \ No newline at end of file From c33a3e0b7e973002a5c2eacb32279b4fd4b0efa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Mlynari=C4=8D?= Date: Mon, 21 Jul 2025 08:51:39 +0200 Subject: [PATCH 5/5] SpotlessApply --- Fruitties/shared/build.gradle.kts | 2 +- .../com/example/fruitties/di/viewmodel/ViewModelStoreUtil.kt | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Fruitties/shared/build.gradle.kts b/Fruitties/shared/build.gradle.kts index 3cbcf0b..fd025e2 100644 --- a/Fruitties/shared/build.gradle.kts +++ b/Fruitties/shared/build.gradle.kts @@ -54,7 +54,7 @@ kotlin { listOf( iosX64(), iosArm64(), - iosSimulatorArm64() + iosSimulatorArm64(), ).forEach { it.binaries.framework { export(libs.androidx.lifecycle.viewmodel) diff --git a/Fruitties/shared/src/iosMain/kotlin/com/example/fruitties/di/viewmodel/ViewModelStoreUtil.kt b/Fruitties/shared/src/iosMain/kotlin/com/example/fruitties/di/viewmodel/ViewModelStoreUtil.kt index 6521b63..0acafb9 100644 --- a/Fruitties/shared/src/iosMain/kotlin/com/example/fruitties/di/viewmodel/ViewModelStoreUtil.kt +++ b/Fruitties/shared/src/iosMain/kotlin/com/example/fruitties/di/viewmodel/ViewModelStoreUtil.kt @@ -2,7 +2,6 @@ package com.example.fruitties.di.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.ViewModelStore import androidx.lifecycle.ViewModelStoreOwner import androidx.lifecycle.viewmodel.CreationExtras import kotlinx.cinterop.BetaInteropApi @@ -10,7 +9,6 @@ import kotlinx.cinterop.ObjCClass import kotlinx.cinterop.getOriginalKotlinClass import kotlin.reflect.KClass - /** * This function allows retrieving any ViewModel from Swift Code with generics. * We only get [ObjCClass] type for the [modelClass], because the interop between Kotlin and Swift @@ -30,4 +28,4 @@ fun ViewModelStoreOwner.viewModel( ?: error("modelClass isn't a ViewModel type") val provider = ViewModelProvider.create(this, factory, extras ?: CreationExtras.Empty) return key?.let { provider[key, vmClass] } ?: provider[vmClass] -} \ No newline at end of file +}