Skip to content

Scope ViewModels with StateObject + export ViewModel dependency #86

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jul 29, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions Fruitties/iosApp/iosApp/IOSViewModelStoreOwner.swift
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import SwiftUI
import shared

/// A ViewModelStoreOwner specifically for iOS.
/// This is used with from iOS with Kotlin Multiplatform (KMP).
class IOSViewModelStoreOwner: ObservableObject, SwiftViewModelStoreOwner {
/// A ViewModelStoreOwner specifically for iOS to be an ObservableObject.
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<T: Lifecycle_viewmodelViewModel>(
func viewModel<T: ViewModel>(
key: String? = nil,
factory: Lifecycle_viewmodelViewModelProviderFactory,
extras: Lifecycle_viewmodelCreationExtras? = nil
factory: ViewModelProviderFactory,
extras: CreationExtras? = nil
) -> T {
do {
return try viewModelStore.getViewModel(
return try viewModel(
modelClass: T.self,
factory: factory,
key: key,
Expand All @@ -26,7 +24,13 @@ class IOSViewModelStoreOwner: ObservableObject, SwiftViewModelStoreOwner {
}
}

/// 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()
}
}
2 changes: 1 addition & 1 deletion Fruitties/iosApp/iosApp/ui/CartView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<AppContainer>
Expand Down
8 changes: 2 additions & 6 deletions Fruitties/iosApp/iosApp/ui/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ struct ContentView: View {
value in
HStack {
NavigationLink {
ViewModelStoreOwnerProvider {
FruittieScreen(fruittie: value)
}
FruittieScreen(fruittie: value)
} label: {
FruittieView(fruittie: value)
}
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Fruitties/iosApp/iosApp/ui/FruittieScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import SwiftUI
import shared

struct FruittieScreen: View {
@EnvironmentObject var viewModelStoreOwner: IOSViewModelStoreOwner
@StateObject var viewModelStoreOwner = IOSViewModelStoreOwner()
@EnvironmentObject var appContainer: ObservableValueWrapper<AppContainer>
let fruittie: Fruittie

Expand Down
27 changes: 9 additions & 18 deletions Fruitties/shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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?,
Expand All @@ -29,10 +30,4 @@ fun ViewModelStore.getViewModel(
?: error("modelClass isn't a ViewModel type")
val provider = ViewModelProvider.create(this, factory, extras ?: CreationExtras.Empty)
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
}
Loading