From 69d2415edcccd1d7b1f64c7b37ddc9e9f9388116 Mon Sep 17 00:00:00 2001 From: shuja1497 Date: Thu, 9 Oct 2025 00:25:41 +0530 Subject: [PATCH 1/9] init shared module --- get-started/build.gradle.kts | 3 + get-started/gradle/libs.versions.toml | 13 +++ get-started/settings.gradle.kts | 3 +- get-started/shared/.gitignore | 1 + get-started/shared/build.gradle.kts | 100 ++++++++++++++++++ .../kmp/shared/ExampleInstrumentedTest.kt | 24 +++++ .../com/example/kmp/shared/ExampleUnitTest.kt | 16 +++ .../src/androidMain/AndroidManifest.xml | 4 + .../example/kmp/shared/Platform.android.kt | 3 + .../kotlin/com/example/kmp/shared/Platform.kt | 3 + .../com/example/kmp/shared/Platform.ios.kt | 3 + 11 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 get-started/shared/.gitignore create mode 100644 get-started/shared/build.gradle.kts create mode 100644 get-started/shared/src/androidDeviceTest/kotlin/com/example/kmp/shared/ExampleInstrumentedTest.kt create mode 100644 get-started/shared/src/androidHostTest/kotlin/com/example/kmp/shared/ExampleUnitTest.kt create mode 100644 get-started/shared/src/androidMain/AndroidManifest.xml create mode 100644 get-started/shared/src/androidMain/kotlin/com/example/kmp/shared/Platform.android.kt create mode 100644 get-started/shared/src/commonMain/kotlin/com/example/kmp/shared/Platform.kt create mode 100644 get-started/shared/src/iosMain/kotlin/com/example/kmp/shared/Platform.ios.kt diff --git a/get-started/build.gradle.kts b/get-started/build.gradle.kts index 3f139dad..d8c99c18 100644 --- a/get-started/build.gradle.kts +++ b/get-started/build.gradle.kts @@ -22,6 +22,9 @@ plugins { alias(libs.plugins.kotlinAndroid) apply false alias(libs.plugins.kotlinCompose) apply false alias(libs.plugins.spotless) apply false + alias(libs.plugins.kotlinMultiplatform) apply false + alias(libs.plugins.androidKotlinMultiplatformLibrary) apply false + alias(libs.plugins.androidLint) apply false } subprojects { diff --git a/get-started/gradle/libs.versions.toml b/get-started/gradle/libs.versions.toml index 2eabf9f5..18cd117b 100644 --- a/get-started/gradle/libs.versions.toml +++ b/get-started/gradle/libs.versions.toml @@ -25,6 +25,11 @@ kotlinx-coroutines = "1.10.2" spotless = "7.2.1" composeBom = "2025.07.00" ktlint = "1.3.1" +kotlinStdlib = "2.2.0" +kotlinTest = "2.2.0" +runner = "1.5.2" +core = "1.5.0" +junit = "1.1.5" [libraries] @@ -39,6 +44,11 @@ compose-material3 = { module = "androidx.compose.material3:material3" } ## Compose BOM End ## kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } +kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlinStdlib" } +kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlinTest" } +androidx-runner = { group = "androidx.test", name = "runner", version.ref = "runner" } +androidx-core = { group = "androidx.test", name = "core", version.ref = "core" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junit" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } @@ -46,3 +56,6 @@ kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlinxSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } kotlinCompose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } +kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +androidKotlinMultiplatformLibrary = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" } +androidLint = { id = "com.android.lint", version.ref = "agp" } diff --git a/get-started/settings.gradle.kts b/get-started/settings.gradle.kts index 7815053e..22c6a4a0 100644 --- a/get-started/settings.gradle.kts +++ b/get-started/settings.gradle.kts @@ -44,4 +44,5 @@ dependencyResolutionManagement { } rootProject.name = "KmpGetStartedCodelab" -include(":androidApp") \ No newline at end of file +include(":androidApp") +include(":shared") diff --git a/get-started/shared/.gitignore b/get-started/shared/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/get-started/shared/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/get-started/shared/build.gradle.kts b/get-started/shared/build.gradle.kts new file mode 100644 index 00000000..ea4c17f1 --- /dev/null +++ b/get-started/shared/build.gradle.kts @@ -0,0 +1,100 @@ +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.androidKotlinMultiplatformLibrary) + alias(libs.plugins.androidLint) +} + +kotlin { + + // Target declarations - add or remove as needed below. These define + // which platforms this KMP module supports. + // See: https://kotlinlang.org/docs/multiplatform-discover-project.html#targets + androidLibrary { + namespace = "com.example.kmp.shared" + compileSdk = 36 + minSdk = 24 + + withHostTestBuilder { + } + + withDeviceTestBuilder { + sourceSetTreeName = "test" + }.configure { + instrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + } + + // For iOS targets, this is also where you should + // configure native binary output. For more information, see: + // https://kotlinlang.org/docs/multiplatform-build-native-binaries.html#build-xcframeworks + + // 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 = "sharedKit" + + iosX64 { + binaries.framework { + baseName = xcfName + } + } + + 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 + // common to share sources between related targets. + // See: https://kotlinlang.org/docs/multiplatform-hierarchy.html + sourceSets { + commonMain { + dependencies { + implementation(libs.kotlin.stdlib) + // Add KMP dependencies here + } + } + + commonTest { + dependencies { + implementation(libs.kotlin.test) + } + } + + androidMain { + dependencies { + // Add Android-specific dependencies here. Note that this source set depends on + // commonMain by default and will correctly pull the Android artifacts of any KMP + // dependencies declared in commonMain. + } + } + + getByName("androidDeviceTest") { + dependencies { + implementation(libs.androidx.runner) + implementation(libs.androidx.core) + implementation(libs.androidx.junit) + } + } + + iosMain { + dependencies { + // Add iOS-specific dependencies here. This a source set created by Kotlin Gradle + // Plugin (KGP) that each specific iOS target (e.g., iosX64) depends on as + // part of KMP’s default source set hierarchy. Note that this source set depends + // on common by default and will correctly pull the iOS artifacts of any + // KMP dependencies declared in commonMain. + } + } + } + +} \ No newline at end of file diff --git a/get-started/shared/src/androidDeviceTest/kotlin/com/example/kmp/shared/ExampleInstrumentedTest.kt b/get-started/shared/src/androidDeviceTest/kotlin/com/example/kmp/shared/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..7dbb6838 --- /dev/null +++ b/get-started/shared/src/androidDeviceTest/kotlin/com/example/kmp/shared/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.kmp.shared + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.kmp.shared.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/get-started/shared/src/androidHostTest/kotlin/com/example/kmp/shared/ExampleUnitTest.kt b/get-started/shared/src/androidHostTest/kotlin/com/example/kmp/shared/ExampleUnitTest.kt new file mode 100644 index 00000000..071e5f18 --- /dev/null +++ b/get-started/shared/src/androidHostTest/kotlin/com/example/kmp/shared/ExampleUnitTest.kt @@ -0,0 +1,16 @@ +package com.example.kmp.shared + +import kotlin.test.Test +import kotlin.test.assertEquals + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/get-started/shared/src/androidMain/AndroidManifest.xml b/get-started/shared/src/androidMain/AndroidManifest.xml new file mode 100644 index 00000000..a5918e68 --- /dev/null +++ b/get-started/shared/src/androidMain/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/get-started/shared/src/androidMain/kotlin/com/example/kmp/shared/Platform.android.kt b/get-started/shared/src/androidMain/kotlin/com/example/kmp/shared/Platform.android.kt new file mode 100644 index 00000000..e1420b7b --- /dev/null +++ b/get-started/shared/src/androidMain/kotlin/com/example/kmp/shared/Platform.android.kt @@ -0,0 +1,3 @@ +package com.example.kmp.shared + +actual fun platform() = "Android" \ No newline at end of file diff --git a/get-started/shared/src/commonMain/kotlin/com/example/kmp/shared/Platform.kt b/get-started/shared/src/commonMain/kotlin/com/example/kmp/shared/Platform.kt new file mode 100644 index 00000000..da6d6d6d --- /dev/null +++ b/get-started/shared/src/commonMain/kotlin/com/example/kmp/shared/Platform.kt @@ -0,0 +1,3 @@ +package com.example.kmp.shared + +expect fun platform(): String \ No newline at end of file diff --git a/get-started/shared/src/iosMain/kotlin/com/example/kmp/shared/Platform.ios.kt b/get-started/shared/src/iosMain/kotlin/com/example/kmp/shared/Platform.ios.kt new file mode 100644 index 00000000..1341c467 --- /dev/null +++ b/get-started/shared/src/iosMain/kotlin/com/example/kmp/shared/Platform.ios.kt @@ -0,0 +1,3 @@ +package com.example.kmp.shared + +actual fun platform() = "iOS" \ No newline at end of file From 7660d94146b2bb5918b15084c5068c98796c7dc9 Mon Sep 17 00:00:00 2001 From: shuja1497 Date: Thu, 9 Oct 2025 11:00:01 +0530 Subject: [PATCH 2/9] include shared module in android --- get-started/androidApp/build.gradle.kts | 1 + .../kotlin/com/example/kmp/getstarted/android/MainActivity.kt | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/get-started/androidApp/build.gradle.kts b/get-started/androidApp/build.gradle.kts index 0b30d7c5..b5ceab80 100644 --- a/get-started/androidApp/build.gradle.kts +++ b/get-started/androidApp/build.gradle.kts @@ -61,4 +61,5 @@ dependencies { implementation(libs.compose.material3) implementation(libs.androidx.activity.compose) debugImplementation(libs.compose.ui.tooling) + implementation(projects.shared) } diff --git a/get-started/androidApp/src/main/kotlin/com/example/kmp/getstarted/android/MainActivity.kt b/get-started/androidApp/src/main/kotlin/com/example/kmp/getstarted/android/MainActivity.kt index 441e9211..a9b15f47 100644 --- a/get-started/androidApp/src/main/kotlin/com/example/kmp/getstarted/android/MainActivity.kt +++ b/get-started/androidApp/src/main/kotlin/com/example/kmp/getstarted/android/MainActivity.kt @@ -25,6 +25,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import com.example.kmp.shared.platform class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -39,7 +40,7 @@ class MainActivity : ComponentActivity() { .padding(innerPadding), contentAlignment = Alignment.Center, ) { - Text("Hello KMP") + Text("Hello ${platform()}") } } } From 96b25e5679ecd91572c4311cb2a7a216c6d07420 Mon Sep 17 00:00:00 2001 From: shuja1497 Date: Thu, 9 Oct 2025 11:58:08 +0530 Subject: [PATCH 3/9] include shared module in ios --- .../project.pbxproj | 22 +++++++++++++++++++ .../KMPGetStartedCodelab/ContentView.swift | 3 ++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/get-started/iosApp/KMPGetStartedCodelab.xcodeproj/project.pbxproj b/get-started/iosApp/KMPGetStartedCodelab.xcodeproj/project.pbxproj index d080f876..181997bc 100644 --- a/get-started/iosApp/KMPGetStartedCodelab.xcodeproj/project.pbxproj +++ b/get-started/iosApp/KMPGetStartedCodelab.xcodeproj/project.pbxproj @@ -52,6 +52,7 @@ isa = PBXNativeTarget; buildConfigurationList = 2DCC1BFC2D4100C80017AC11 /* Build configuration list for PBXNativeTarget "KMPGetStartedCodelab" */; buildPhases = ( + 04FEC8AD2E978654000D2A5F /* Compile Kotlin Framework */, 2DCC1BEA2D4100C60017AC11 /* Sources */, 2DCC1BEB2D4100C60017AC11 /* Frameworks */, 2DCC1BEC2D4100C60017AC11 /* Resources */, @@ -114,6 +115,27 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 04FEC8AD2E978654000D2A5F /* Compile Kotlin Framework */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Compile Kotlin Framework "; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "cd \"$SRCROOT/..\"\n./gradlew :shared:embedAndSignAppleFrameworkForXcode\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 2DCC1BEA2D4100C60017AC11 /* Sources */ = { isa = PBXSourcesBuildPhase; diff --git a/get-started/iosApp/KMPGetStartedCodelab/ContentView.swift b/get-started/iosApp/KMPGetStartedCodelab/ContentView.swift index c8a97a96..9f51fd11 100644 --- a/get-started/iosApp/KMPGetStartedCodelab/ContentView.swift +++ b/get-started/iosApp/KMPGetStartedCodelab/ContentView.swift @@ -5,6 +5,7 @@ // import SwiftUI +import sharedKit struct ContentView: View { var body: some View { @@ -12,7 +13,7 @@ struct ContentView: View { Image(systemName: "globe") .imageScale(.large) .foregroundStyle(.tint) - Text("Hello, KMP!") + Text("Hello, \(Platform_iosKt.platform())!") } .padding() } From a2c36977e8bf86d103af2bc99097703c1abdc408 Mon Sep 17 00:00:00 2001 From: shuja1497 Date: Fri, 10 Oct 2025 09:57:27 +0530 Subject: [PATCH 4/9] Integrate SKIE --- get-started/build.gradle.kts | 1 + get-started/gradle/libs.versions.toml | 2 ++ get-started/iosApp/KMPGetStartedCodelab/ContentView.swift | 2 +- get-started/shared/build.gradle.kts | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/get-started/build.gradle.kts b/get-started/build.gradle.kts index d8c99c18..9640ca23 100644 --- a/get-started/build.gradle.kts +++ b/get-started/build.gradle.kts @@ -25,6 +25,7 @@ plugins { alias(libs.plugins.kotlinMultiplatform) apply false alias(libs.plugins.androidKotlinMultiplatformLibrary) apply false alias(libs.plugins.androidLint) apply false + alias(libs.plugins.skie) apply false } subprojects { diff --git a/get-started/gradle/libs.versions.toml b/get-started/gradle/libs.versions.toml index 18cd117b..40141a67 100644 --- a/get-started/gradle/libs.versions.toml +++ b/get-started/gradle/libs.versions.toml @@ -30,6 +30,7 @@ kotlinTest = "2.2.0" runner = "1.5.2" core = "1.5.0" junit = "1.1.5" +skie = "0.10.6" [libraries] @@ -59,3 +60,4 @@ spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } androidKotlinMultiplatformLibrary = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" } androidLint = { id = "com.android.lint", version.ref = "agp" } +skie = { id = "co.touchlab.skie", version.ref = "skie" } diff --git a/get-started/iosApp/KMPGetStartedCodelab/ContentView.swift b/get-started/iosApp/KMPGetStartedCodelab/ContentView.swift index 9f51fd11..6451635f 100644 --- a/get-started/iosApp/KMPGetStartedCodelab/ContentView.swift +++ b/get-started/iosApp/KMPGetStartedCodelab/ContentView.swift @@ -13,7 +13,7 @@ struct ContentView: View { Image(systemName: "globe") .imageScale(.large) .foregroundStyle(.tint) - Text("Hello, \(Platform_iosKt.platform())!") + Text("Hello, \(platform())!") } .padding() } diff --git a/get-started/shared/build.gradle.kts b/get-started/shared/build.gradle.kts index ea4c17f1..d196b1e1 100644 --- a/get-started/shared/build.gradle.kts +++ b/get-started/shared/build.gradle.kts @@ -2,6 +2,7 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.androidKotlinMultiplatformLibrary) alias(libs.plugins.androidLint) + alias(libs.plugins.skie) } kotlin { From c10a4b419303baec89a1e774ea5755c1138e7ef5 Mon Sep 17 00:00:00 2001 From: shuja1497 Date: Fri, 10 Oct 2025 10:47:29 +0530 Subject: [PATCH 5/9] Include room runtime and sqllitebundled driver depencency --- migrate-room/androidApp/build.gradle.kts | 4 ++-- migrate-room/gradle/libs.versions.toml | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/migrate-room/androidApp/build.gradle.kts b/migrate-room/androidApp/build.gradle.kts index 5fcdccf0..5172bab0 100644 --- a/migrate-room/androidApp/build.gradle.kts +++ b/migrate-room/androidApp/build.gradle.kts @@ -93,8 +93,8 @@ dependencies { debugImplementation(libs.compose.ui.tooling) ksp(libs.androidx.room.compiler) - // TODO switch this dependency to KMP-ready version - implementation(libs.androidx.room.ktx) + implementation(libs.androidx.room.runtime) + implementation(libs.androidx.sqlite.bundled) implementation(libs.hilt.android) ksp(libs.hilt.compiler) diff --git a/migrate-room/gradle/libs.versions.toml b/migrate-room/gradle/libs.versions.toml index 2f06ccaa..19c92b7b 100644 --- a/migrate-room/gradle/libs.versions.toml +++ b/migrate-room/gradle/libs.versions.toml @@ -37,7 +37,8 @@ core = "1.7.0" skie = "0.10.4" [libraries] -# TODO add room-runtime and sqlite-bundled dependencies +androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidx-room" } +androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "androidx-sqlite" } hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" } androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" } From 61b8a8269ad215e8332154fa41c3595559d285c9 Mon Sep 17 00:00:00 2001 From: shuja1497 Date: Fri, 10 Oct 2025 11:12:10 +0530 Subject: [PATCH 6/9] set driver in room db --- .../fruitties/kmptutorial/android/di/DatabaseModule.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/di/DatabaseModule.kt b/migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/di/DatabaseModule.kt index f72f312b..17580eb7 100644 --- a/migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/di/DatabaseModule.kt +++ b/migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/di/DatabaseModule.kt @@ -17,6 +17,7 @@ package com.example.fruitties.kmptutorial.android.di import android.content.Context import androidx.room.Room +import androidx.sqlite.driver.bundled.BundledSQLiteDriver import com.example.fruitties.kmptutorial.android.database.AppDatabase import dagger.Module import dagger.Provides @@ -31,9 +32,12 @@ internal object DatabaseModule { @Provides @Singleton - fun providesAppDatabase(@ApplicationContext context: Context): AppDatabase = Room - .databaseBuilder(context, AppDatabase::class.java, "sharedfruits.db") - .build() + fun providesAppDatabase(@ApplicationContext context: Context): AppDatabase { + val dbFile = context.getDatabasePath("sharedfruits.db") + return Room.databaseBuilder(context, dbFile.absolutePath) + .setDriver(BundledSQLiteDriver()) + .build() + } @Provides fun providesFruittieDao(appDatabase: AppDatabase) = appDatabase.fruittieDao() From 26bfb08225ca9633bd89851636aa56037f831888 Mon Sep 17 00:00:00 2001 From: shuja1497 Date: Fri, 10 Oct 2025 11:40:11 +0530 Subject: [PATCH 7/9] move db related files from android module to shared module --- .../kmptutorial/android/model/Fruittie.kt | 28 ------------------- migrate-room/shared/build.gradle.kts | 17 ++++++++--- .../1.json | 0 .../android/database/AppDatabase.kt | 0 .../kmptutorial/android/database/CartDao.kt | 0 .../android/database/FruittieDao.kt | 0 .../kmptutorial/android/model/CartItem.kt | 17 +---------- .../kmptutorial/android/model/Fruittie.kt | 13 +++++++++ 8 files changed, 27 insertions(+), 48 deletions(-) delete mode 100644 migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/model/Fruittie.kt rename migrate-room/{androidApp => shared}/schemas/com.example.fruitties.kmptutorial.android.database.AppDatabase/1.json (100%) rename migrate-room/{androidApp/src/main => shared/src/commonMain}/kotlin/com/example/fruitties/kmptutorial/android/database/AppDatabase.kt (100%) rename migrate-room/{androidApp/src/main => shared/src/commonMain}/kotlin/com/example/fruitties/kmptutorial/android/database/CartDao.kt (100%) rename migrate-room/{androidApp/src/main => shared/src/commonMain}/kotlin/com/example/fruitties/kmptutorial/android/database/FruittieDao.kt (100%) rename migrate-room/{androidApp/src/main => shared/src/commonMain}/kotlin/com/example/fruitties/kmptutorial/android/model/CartItem.kt (52%) create mode 100644 migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/model/Fruittie.kt diff --git a/migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/model/Fruittie.kt b/migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/model/Fruittie.kt deleted file mode 100644 index 6bab71f2..00000000 --- a/migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/model/Fruittie.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2024 The Android Open Source Project - * - * 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 - * - * https://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 com.example.fruitties.kmptutorial.android.model - -import androidx.room.Entity -import androidx.room.Index -import androidx.room.PrimaryKey - -@Entity(indices = [Index(value = ["id"], unique = true)]) -data class Fruittie( - @PrimaryKey(autoGenerate = true) val id: Long = 0, - val name: String, - val fullName: String, - val calories: String, -) diff --git a/migrate-room/shared/build.gradle.kts b/migrate-room/shared/build.gradle.kts index 085585d2..0ac849e6 100644 --- a/migrate-room/shared/build.gradle.kts +++ b/migrate-room/shared/build.gradle.kts @@ -17,7 +17,8 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.androidKotlinMultiplatformLibrary) alias(libs.plugins.skie) - // TODO add KSP + ROOM plugins + alias(libs.plugins.ksp) + alias(libs.plugins.room) } kotlin { @@ -76,7 +77,8 @@ kotlin { commonMain { dependencies { implementation(libs.kotlin.stdlib) - // TODO Add KMP dependencies here + implementation(libs.androidx.room.runtime) + implementation(libs.androidx.sqlite.bundled) } } @@ -114,6 +116,13 @@ kotlin { } } -// TODO Add top level dependencies block with ksp and room plugins +dependencies { + add("kspAndroid", libs.androidx.room.compiler) + add("kspIosSimulatorArm64", libs.androidx.room.compiler) + add("kspIosX64", libs.androidx.room.compiler) + add("kspIosArm64", libs.androidx.room.compiler) +} -// TODO Add room configuration block +room { + schemaDirectory("$projectDir/schemas") +} \ No newline at end of file diff --git a/migrate-room/androidApp/schemas/com.example.fruitties.kmptutorial.android.database.AppDatabase/1.json b/migrate-room/shared/schemas/com.example.fruitties.kmptutorial.android.database.AppDatabase/1.json similarity index 100% rename from migrate-room/androidApp/schemas/com.example.fruitties.kmptutorial.android.database.AppDatabase/1.json rename to migrate-room/shared/schemas/com.example.fruitties.kmptutorial.android.database.AppDatabase/1.json diff --git a/migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/database/AppDatabase.kt b/migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/database/AppDatabase.kt similarity index 100% rename from migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/database/AppDatabase.kt rename to migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/database/AppDatabase.kt diff --git a/migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/database/CartDao.kt b/migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/database/CartDao.kt similarity index 100% rename from migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/database/CartDao.kt rename to migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/database/CartDao.kt diff --git a/migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/database/FruittieDao.kt b/migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/database/FruittieDao.kt similarity index 100% rename from migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/database/FruittieDao.kt rename to migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/database/FruittieDao.kt diff --git a/migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/model/CartItem.kt b/migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/model/CartItem.kt similarity index 52% rename from migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/model/CartItem.kt rename to migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/model/CartItem.kt index 161451de..364446b6 100644 --- a/migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/model/CartItem.kt +++ b/migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/model/CartItem.kt @@ -1,18 +1,3 @@ -/* - * Copyright 2024 The Android Open Source Project - * - * 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 - * - * https://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 com.example.fruitties.kmptutorial.android.model import androidx.room.Embedded @@ -40,4 +25,4 @@ data class CartItemWithFruittie( ) val fruittie: Fruittie, @Embedded val cartItem: CartItem, -) +) \ No newline at end of file diff --git a/migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/model/Fruittie.kt b/migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/model/Fruittie.kt new file mode 100644 index 00000000..f7eceb27 --- /dev/null +++ b/migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/model/Fruittie.kt @@ -0,0 +1,13 @@ +package com.example.fruitties.kmptutorial.android.model + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity(indices = [Index(value = ["id"], unique = true)]) +data class Fruittie( + @PrimaryKey(autoGenerate = true) val id: Long = 0, + val name: String, + val fullName: String, + val calories: String, +) \ No newline at end of file From 98548689f126e5df632951a2c38d6493e569610b Mon Sep 17 00:00:00 2001 From: shuja1497 Date: Fri, 10 Oct 2025 11:59:41 +0530 Subject: [PATCH 8/9] Migrate room for android module --- migrate-room/androidApp/build.gradle.kts | 10 ---------- .../kmptutorial/android/di/DatabaseModule.kt | 8 ++------ .../kmptutorial/shared/database/AppDatabase.kt | 13 +++++++++++++ .../kmptutorial/android/database/AppDatabase.kt | 12 +++++++++++- 4 files changed, 26 insertions(+), 17 deletions(-) create mode 100644 migrate-room/shared/src/androidMain/kotlin/com/example/fruitties/kmptutorial/shared/database/AppDatabase.kt diff --git a/migrate-room/androidApp/build.gradle.kts b/migrate-room/androidApp/build.gradle.kts index 5172bab0..4be7c717 100644 --- a/migrate-room/androidApp/build.gradle.kts +++ b/migrate-room/androidApp/build.gradle.kts @@ -19,7 +19,6 @@ plugins { alias(libs.plugins.kotlinCompose) alias(libs.plugins.kotlinxSerialization) alias(libs.plugins.ksp) - alias(libs.plugins.room) alias(libs.plugins.spotless) alias(libs.plugins.hilt) } @@ -69,9 +68,6 @@ android { languageVersion = JavaLanguageVersion.of(17) } } - ksp { - arg("room.generateKotlin", "true") - } } dependencies { @@ -92,9 +88,7 @@ dependencies { implementation(libs.androidx.lifecycle.viewmodel) debugImplementation(libs.compose.ui.tooling) - ksp(libs.androidx.room.compiler) implementation(libs.androidx.room.runtime) - implementation(libs.androidx.sqlite.bundled) implementation(libs.hilt.android) ksp(libs.hilt.compiler) @@ -111,7 +105,3 @@ dependencies { androidTestImplementation(libs.androidx.test.junit) androidTestImplementation(libs.kotlinx.coroutines.test) } - -room { - schemaDirectory("$projectDir/schemas") -} diff --git a/migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/di/DatabaseModule.kt b/migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/di/DatabaseModule.kt index 17580eb7..7109f8e3 100644 --- a/migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/di/DatabaseModule.kt +++ b/migrate-room/androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/di/DatabaseModule.kt @@ -16,9 +16,8 @@ package com.example.fruitties.kmptutorial.android.di import android.content.Context -import androidx.room.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import com.example.fruitties.kmptutorial.android.database.AppDatabase +import com.example.fruitties.kmptutorial.shared.database.appDatabase import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -33,10 +32,7 @@ internal object DatabaseModule { @Provides @Singleton fun providesAppDatabase(@ApplicationContext context: Context): AppDatabase { - val dbFile = context.getDatabasePath("sharedfruits.db") - return Room.databaseBuilder(context, dbFile.absolutePath) - .setDriver(BundledSQLiteDriver()) - .build() + return appDatabase(context) } @Provides diff --git a/migrate-room/shared/src/androidMain/kotlin/com/example/fruitties/kmptutorial/shared/database/AppDatabase.kt b/migrate-room/shared/src/androidMain/kotlin/com/example/fruitties/kmptutorial/shared/database/AppDatabase.kt new file mode 100644 index 00000000..447cda80 --- /dev/null +++ b/migrate-room/shared/src/androidMain/kotlin/com/example/fruitties/kmptutorial/shared/database/AppDatabase.kt @@ -0,0 +1,13 @@ +package com.example.fruitties.kmptutorial.shared.database + +import android.content.Context +import androidx.room.Room +import androidx.sqlite.driver.bundled.BundledSQLiteDriver +import com.example.fruitties.kmptutorial.android.database.AppDatabase + +fun appDatabase(context: Context): AppDatabase { + val dbFile = context.getDatabasePath("sharedfruits.db") + return Room.databaseBuilder(context, dbFile.absolutePath) + .setDriver(BundledSQLiteDriver()) + .build() +} \ No newline at end of file diff --git a/migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/database/AppDatabase.kt b/migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/database/AppDatabase.kt index 8095bf42..d6acdcd9 100644 --- a/migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/database/AppDatabase.kt +++ b/migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/database/AppDatabase.kt @@ -15,16 +15,26 @@ */ package com.example.fruitties.kmptutorial.android.database +import androidx.room.ConstructedBy import androidx.room.Database import androidx.room.RoomDatabase import com.example.fruitties.kmptutorial.android.model.CartItem import com.example.fruitties.kmptutorial.android.model.Fruittie +import androidx.room.RoomDatabaseConstructor + + +// The Room compiler generates the `actual` implementations. +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect object AppDatabaseConstructor : RoomDatabaseConstructor { + override fun initialize(): AppDatabase +} @Database( entities = [Fruittie::class, CartItem::class], version = 1, ) +@ConstructedBy(AppDatabaseConstructor::class) abstract class AppDatabase : RoomDatabase() { abstract fun fruittieDao(): FruittieDao abstract fun cartDao(): CartDao -} +} \ No newline at end of file From 479631266b4035956a2dbf2c8b3f7dca89d79fea Mon Sep 17 00:00:00 2001 From: shuja1497 Date: Wed, 22 Oct 2025 10:21:36 +0530 Subject: [PATCH 9/9] Refactor data handling to use shared database layer and remove Core Data dependencies for iOS --- .../Fruitties.xcodeproj/project.pbxproj | 25 ++++------ .../iosApp/Sources/DI/AppContainer.swift | 10 ++-- .../Sources/Database/DataController.swift | 15 ++---- .../iosApp/Sources/Repository/CartItem.swift | 28 +++++++++++ .../Sources/Repository/CartRepository.swift | 38 ++++----------- .../iosApp/Sources/Repository/Fruittie.swift | 25 ++++++++++ .../Repository/FruittieRepository.swift | 41 ++++++++-------- migrate-room/iosApp/Sources/UI/CartView.swift | 10 ++-- .../iosApp/Sources/UI/ContentView.swift | 2 +- migrate-room/iosApp/Sources/main.swift | 1 - .../kmptutorial/android/model/CartItem.kt | 4 ++ .../kmptutorial/android/model/Fruittie.kt | 4 ++ .../kmptutorial/shared/AppDatabase.ios.kt | 47 +++++++++++++++++++ 13 files changed, 155 insertions(+), 95 deletions(-) create mode 100644 migrate-room/iosApp/Sources/Repository/CartItem.swift create mode 100644 migrate-room/iosApp/Sources/Repository/Fruittie.swift create mode 100644 migrate-room/shared/src/iosMain/kotlin/com/example/fruitties/kmptutorial/shared/AppDatabase.ios.kt diff --git a/migrate-room/iosApp/Fruitties.xcodeproj/project.pbxproj b/migrate-room/iosApp/Fruitties.xcodeproj/project.pbxproj index 38dc0872..8ade3d27 100644 --- a/migrate-room/iosApp/Fruitties.xcodeproj/project.pbxproj +++ b/migrate-room/iosApp/Fruitties.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 04C0352D2EA7FA77004B0531 /* Fruittie.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C0352C2EA7FA77004B0531 /* Fruittie.swift */; }; + 04C0352F2EA8070B004B0531 /* CartItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C0352E2EA8070B004B0531 /* CartItem.swift */; }; 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; }; 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; }; 1800B0E02CBFFD6B0035D9C5 /* AsyncStreamNSFetchRequestResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1800B0DA2CBFFD6B0035D9C5 /* AsyncStreamNSFetchRequestResultTests.swift */; }; @@ -23,7 +25,6 @@ 183004822CACE30F001DF9CA /* ContentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 183004812CACE30F001DF9CA /* ContentViewModel.swift */; }; 183004922CB700CA001DF9CA /* CartRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 183004912CB700C9001DF9CA /* CartRepository.swift */; }; 186680822CBEB6240047633B /* ConcurrencyExtras in Frameworks */ = {isa = PBXBuildFile; productRef = 186680812CBEB6240047633B /* ConcurrencyExtras */; }; - 187640EF2CAB2915002A80F7 /* Fruitties.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 187640ED2CAB2915002A80F7 /* Fruitties.xcdatamodeld */; }; 1885088B2CB99134003F8D5E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1885088A2CB99117003F8D5E /* main.swift */; }; 18C09F492CC1585800B22F47 /* MockFruittieRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C09F482CC1585800B22F47 /* MockFruittieRepository.swift */; }; 18C09F4D2CC1591500B22F47 /* FakeCartRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C09F4C2CC1590F00B22F47 /* FakeCartRepository.swift */; }; @@ -57,6 +58,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 04C0352C2EA7FA77004B0531 /* Fruittie.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fruittie.swift; sourceTree = ""; }; + 04C0352E2EA8070B004B0531 /* CartItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartItem.swift; sourceTree = ""; }; 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 1800B0DA2CBFFD6B0035D9C5 /* AsyncStreamNSFetchRequestResultTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncStreamNSFetchRequestResultTests.swift; sourceTree = ""; }; @@ -72,7 +75,6 @@ 1830047F2CACE2FC001DF9CA /* DataController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataController.swift; sourceTree = ""; }; 183004812CACE30F001DF9CA /* ContentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentViewModel.swift; sourceTree = ""; }; 183004912CB700C9001DF9CA /* CartRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartRepository.swift; sourceTree = ""; }; - 187640EE2CAB2915002A80F7 /* Fruitties.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Fruitties.xcdatamodel; sourceTree = ""; }; 188508792CB71FC8003F8D5E /* FruittiesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FruittiesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 1885088A2CB99117003F8D5E /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 18C09F482CC1585800B22F47 /* MockFruittieRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockFruittieRepository.swift; sourceTree = ""; }; @@ -130,6 +132,8 @@ children = ( 1830047D2CACE2C6001DF9CA /* FruittieRepository.swift */, 183004912CB700C9001DF9CA /* CartRepository.swift */, + 04C0352C2EA7FA77004B0531 /* Fruittie.swift */, + 04C0352E2EA8070B004B0531 /* CartItem.swift */, ); path = Repository; sourceTree = ""; @@ -173,7 +177,6 @@ 058557BA273AAA24004C7B11 /* Assets.xcassets */, 7555FF8C242A565B00829871 /* Info.plist */, 058557D7273AAEEB004C7B11 /* Preview Content */, - 187640ED2CAB2915002A80F7 /* Fruitties.xcdatamodeld */, ); path = Resources; sourceTree = ""; @@ -395,11 +398,12 @@ 2E8773602BC85C2400BF7C40 /* CartView.swift in Sources */, 1830047E2CACE2C7001DF9CA /* FruittieRepository.swift in Sources */, 2DCC1BDB2D3E90BD0017AC11 /* FruittiesResponse.swift in Sources */, - 187640EF2CAB2915002A80F7 /* Fruitties.xcdatamodeld in Sources */, 183004822CACE30F001DF9CA /* ContentViewModel.swift in Sources */, 183004922CB700CA001DF9CA /* CartRepository.swift in Sources */, 1830047C2CACE2B0001DF9CA /* AppContainer.swift in Sources */, + 04C0352F2EA8070B004B0531 /* CartItem.swift in Sources */, 183004742CAC5744001DF9CA /* FruittieApi.swift in Sources */, + 04C0352D2EA7FA77004B0531 /* Fruittie.swift in Sources */, 183004802CACE2FD001DF9CA /* DataController.swift in Sources */, 1885088B2CB99134003F8D5E /* main.swift in Sources */, 183004762CACE153001DF9CA /* AsyncStream+NSFetchRequestResult.swift in Sources */, @@ -684,19 +688,6 @@ productName = ConcurrencyExtras; }; /* End XCSwiftPackageProductDependency section */ - -/* Begin XCVersionGroup section */ - 187640ED2CAB2915002A80F7 /* Fruitties.xcdatamodeld */ = { - isa = XCVersionGroup; - children = ( - 187640EE2CAB2915002A80F7 /* Fruitties.xcdatamodel */, - ); - currentVersion = 187640EE2CAB2915002A80F7 /* Fruitties.xcdatamodel */; - path = Fruitties.xcdatamodeld; - sourceTree = ""; - versionGroupType = wrapper.xcdatamodel; - }; -/* End XCVersionGroup section */ }; rootObject = 7555FF73242A565900829871 /* Project object */; } diff --git a/migrate-room/iosApp/Sources/DI/AppContainer.swift b/migrate-room/iosApp/Sources/DI/AppContainer.swift index 685abe5b..b57b5bc8 100644 --- a/migrate-room/iosApp/Sources/DI/AppContainer.swift +++ b/migrate-room/iosApp/Sources/DI/AppContainer.swift @@ -15,7 +15,7 @@ */ import Combine -import CoreData +import sharedKit import Foundation class AppContainer: ObservableObject { @@ -24,10 +24,6 @@ class AppContainer: ObservableObject { let fruittieRepository: FruittieRepository let cartRepository: CartRepository - var managedObjectContext: NSManagedObjectContext { - dataController.container.viewContext - } - init() { dataController = DataController() api = FruittieNetworkApi( @@ -36,11 +32,11 @@ class AppContainer: ObservableObject { "https://android.github.io/kotlin-multiplatform-samples/fruitties-api" )!) fruittieRepository = DefaultFruittieRepository( - managedObjectContext: dataController.container.viewContext, + fruittieDao: dataController.database.fruittieDao() , api: api ) cartRepository = DefaultCartRepository( - managedObjectContext: dataController.container.viewContext + cartDao: dataController.database.cartDao() ) } } diff --git a/migrate-room/iosApp/Sources/Database/DataController.swift b/migrate-room/iosApp/Sources/Database/DataController.swift index 1dc46963..85b57122 100644 --- a/migrate-room/iosApp/Sources/Database/DataController.swift +++ b/migrate-room/iosApp/Sources/Database/DataController.swift @@ -15,18 +15,9 @@ */ import Combine -import CoreData +import sharedKit class DataController: ObservableObject { - let container = NSPersistentContainer(name: "Fruitties") - - init() { - container.loadPersistentStores { description, error in - if let error { - print("Core Data failed to load: \(error.localizedDescription)") - } - print("CoreData Sqlite location", description.url?.path(percentEncoded: false) ?? "N/A") - } - - } + let database = getPersistentDatabase() + init() {} } diff --git a/migrate-room/iosApp/Sources/Repository/CartItem.swift b/migrate-room/iosApp/Sources/Repository/CartItem.swift new file mode 100644 index 00000000..76e1167e --- /dev/null +++ b/migrate-room/iosApp/Sources/Repository/CartItem.swift @@ -0,0 +1,28 @@ +// +// CartItem.swift +// Fruitties +// +// Created by Muhammad Shuja Reshi on 21/10/25. +// Copyright © 2025 orgName. All rights reserved. +// + +import sharedKit + +struct CartItem: Hashable { + let entity: CartItemWithFruittie + + let fruittie: Fruittie? + + var id: Int64 { + entity.cartItem.id + } + + var count: Int64 { + Int64(entity.cartItem.count) + } + + init(entity: CartItemWithFruittie) { + self.entity = entity + self.fruittie = Fruittie(entity: entity.fruittie) + } +} diff --git a/migrate-room/iosApp/Sources/Repository/CartRepository.swift b/migrate-room/iosApp/Sources/Repository/CartRepository.swift index 4e7b2aa0..b95005d7 100644 --- a/migrate-room/iosApp/Sources/Repository/CartRepository.swift +++ b/migrate-room/iosApp/Sources/Repository/CartRepository.swift @@ -14,7 +14,7 @@ import ConcurrencyExtras * See the License for the specific language governing permissions and * limitations under the License. */ -import CoreData +import sharedKit protocol CartRepository { func addToCart(fruittie: Fruittie) async throws @@ -23,41 +23,19 @@ protocol CartRepository { } class DefaultCartRepository: CartRepository { - private let managedObjectContext: NSManagedObjectContext + private let cartDao: CartDao - init(managedObjectContext: NSManagedObjectContext) { - self.managedObjectContext = managedObjectContext + init(cartDao: CartDao) { + self.cartDao = cartDao } func addToCart(fruittie: Fruittie) async throws { - let context = managedObjectContext - try await context.perform { - let request = CartItem.fetchRequest() - request.predicate = NSPredicate(format: "fruittie == %@", fruittie) - let results = try context.fetch(request) - - if results.isEmpty { - let newItem = CartItem(context: context) - newItem.fruittie = fruittie - newItem.count = 1 - } else { - if results.count > 1 { - print( - "Warning: Multiple CartItem for Fruittie \(fruittie.name!)" - ) - } - - results.forEach { - $0.count += 1 - } - } - - try context.save() - } + try await cartDao.insertOrIncreaseCount(fruittie: fruittie.entity) } func getCartItems() -> AsyncStream<[CartItem]> { - return AsyncStream.resultsStream( - request: CartItem.fetchRequest(), in: managedObjectContext) + return cartDao.getAll().map { entities in + entities.map(CartItem.init(entity:)) + }.eraseToStream() } } diff --git a/migrate-room/iosApp/Sources/Repository/Fruittie.swift b/migrate-room/iosApp/Sources/Repository/Fruittie.swift new file mode 100644 index 00000000..b0e60215 --- /dev/null +++ b/migrate-room/iosApp/Sources/Repository/Fruittie.swift @@ -0,0 +1,25 @@ +// +// Fruittie.swift +// Fruitties +// +// Created by Muhammad Shuja Reshi on 21/10/25. +// Copyright © 2025 orgName. All rights reserved. +// + +import sharedKit + +struct Fruittie: Hashable { + let entity: FruittieEntity + + var id: Int64 { + entity.id + } + + var name: String? { + entity.name + } + + var fullName: String? { + entity.fullName + } +} diff --git a/migrate-room/iosApp/Sources/Repository/FruittieRepository.swift b/migrate-room/iosApp/Sources/Repository/FruittieRepository.swift index 1e7d3770..c7f3a153 100644 --- a/migrate-room/iosApp/Sources/Repository/FruittieRepository.swift +++ b/migrate-room/iosApp/Sources/Repository/FruittieRepository.swift @@ -15,43 +15,40 @@ */ import ConcurrencyExtras -import CoreData +import sharedKit protocol FruittieRepository { func getData() -> AsyncStream<[Fruittie]> } class DefaultFruittieRepository: FruittieRepository { - private let managedObjectContext: NSManagedObjectContext + private let fruittieDao: any FruittieDao private let api: FruittieApi - init(managedObjectContext: NSManagedObjectContext, api: FruittieApi) { - self.managedObjectContext = managedObjectContext + init(fruittieDao: FruittieDao, api: FruittieApi) { + self.fruittieDao = fruittieDao self.api = api } func getData() -> AsyncStream<[Fruittie]> { - let context = managedObjectContext + let dao = fruittieDao Task { - let isEmpty = try await context.perform { - try context.fetch(Fruittie.fetchRequest()).isEmpty - } - - if isEmpty { - let response = try await api.getData(pageNumber: 0) - try await context.perform { - response.feed.forEach { newItem in - let fruittie = Fruittie(context: context) - fruittie.name = newItem.name - fruittie.fullName = newItem.fullName + let isEmpty = try await dao.count() == 0 + if isEmpty { + let response = try await api.getData(pageNumber: 0) + let fruitties = response.feed.map { + FruittieEntity( + id: 0, + name: $0.name, + fullName: $0.fullName, + calories: "" + ) } - - try context.save() + _ = try await dao.insert(fruitties: fruitties) } } - } - - return AsyncStream.resultsStream( - request: Fruittie.fetchRequest(), in: context) + return dao.getAll().map { entities in + entities.map(Fruittie.init(entity:)) + }.eraseToStream() } } diff --git a/migrate-room/iosApp/Sources/UI/CartView.swift b/migrate-room/iosApp/Sources/UI/CartView.swift index 55edc890..b8560afb 100644 --- a/migrate-room/iosApp/Sources/UI/CartView.swift +++ b/migrate-room/iosApp/Sources/UI/CartView.swift @@ -20,15 +20,15 @@ struct CartView : View { @State private var expanded = false - @FetchRequest(sortDescriptors: []) - private var cartItems: FetchedResults + @ObservedObject + private(set) var uiModel: ContentViewModel var body: some View { - if (cartItems.isEmpty) { + if (uiModel.cartItems.isEmpty) { Text("Cart is empty, add some items").padding() } else { HStack { - Text("Cart has \(cartItems.reduce(0){$0 + $1.count}) items of \(cartItems.count) types of fruit") + Text("Cart has \(uiModel.cartItems.reduce(0){$0 + $1.count}) items of \(uiModel.cartItems.count) types of fruit") .padding() Spacer() @@ -46,7 +46,7 @@ struct CartView : View { } if (expanded) { VStack { - ForEach(cartItems, id: \.self) { item in + ForEach(uiModel.cartItems, id: \.self) { item in Text("\(item.fruittie!.name!): \(item.count)") } } diff --git a/migrate-room/iosApp/Sources/UI/ContentView.swift b/migrate-room/iosApp/Sources/UI/ContentView.swift index aed7eff7..4ddd5438 100644 --- a/migrate-room/iosApp/Sources/UI/ContentView.swift +++ b/migrate-room/iosApp/Sources/UI/ContentView.swift @@ -35,7 +35,7 @@ struct ContentView: View { .font(.largeTitle) .fontWeight(.bold) - CartView() + CartView(uiModel: uiModel) ScrollView { LazyVStack { diff --git a/migrate-room/iosApp/Sources/main.swift b/migrate-room/iosApp/Sources/main.swift index aa8ba38a..4f96416a 100644 --- a/migrate-room/iosApp/Sources/main.swift +++ b/migrate-room/iosApp/Sources/main.swift @@ -31,7 +31,6 @@ struct FruittiesApp: App { var body: some Scene { WindowGroup { ContentView(appContainer: appContainer) - .environment(\.managedObjectContext, appContainer.dataController.container.viewContext) } } } diff --git a/migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/model/CartItem.kt b/migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/model/CartItem.kt index 364446b6..d8d6b291 100644 --- a/migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/model/CartItem.kt +++ b/migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/model/CartItem.kt @@ -5,7 +5,11 @@ import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.PrimaryKey import androidx.room.Relation +import kotlin.experimental.ExperimentalObjCName +import kotlin.native.ObjCName +@OptIn(ExperimentalObjCName::class) +@ObjCName("CartItemEntity") @Entity( foreignKeys = [ ForeignKey( diff --git a/migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/model/Fruittie.kt b/migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/model/Fruittie.kt index f7eceb27..1cf38f33 100644 --- a/migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/model/Fruittie.kt +++ b/migrate-room/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/model/Fruittie.kt @@ -3,8 +3,12 @@ package com.example.fruitties.kmptutorial.android.model import androidx.room.Entity import androidx.room.Index import androidx.room.PrimaryKey +import kotlin.experimental.ExperimentalObjCName +import kotlin.native.ObjCName +@OptIn(ExperimentalObjCName::class) @Entity(indices = [Index(value = ["id"], unique = true)]) +@ObjCName("FruittieEntity") data class Fruittie( @PrimaryKey(autoGenerate = true) val id: Long = 0, val name: String, diff --git a/migrate-room/shared/src/iosMain/kotlin/com/example/fruitties/kmptutorial/shared/AppDatabase.ios.kt b/migrate-room/shared/src/iosMain/kotlin/com/example/fruitties/kmptutorial/shared/AppDatabase.ios.kt new file mode 100644 index 00000000..54a2e0d5 --- /dev/null +++ b/migrate-room/shared/src/iosMain/kotlin/com/example/fruitties/kmptutorial/shared/AppDatabase.ios.kt @@ -0,0 +1,47 @@ +package com.example.fruitties.kmptutorial.shared + +import androidx.room.Room +import com.example.fruitties.kmptutorial.android.database.AppDatabase +import kotlinx.cinterop.BetaInteropApi +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.ObjCObjectVar +import kotlinx.cinterop.alloc +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.ptr +import kotlinx.cinterop.value +import platform.Foundation.NSDocumentDirectory +import platform.Foundation.NSError +import platform.Foundation.NSFileManager +import platform.Foundation.NSUserDomainMask + +fun getPersistentDatabase(): AppDatabase { + val dbFilePath = documentDirectory() + "/" + "fruits.db" + return Room.databaseBuilder(name = dbFilePath) + .setDriver(_root_ide_package_.androidx.sqlite.driver.bundled.BundledSQLiteDriver()) + .build() +} + +@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class) +private fun documentDirectory(): String { + memScoped { + val errorPtr = alloc>() + val documentDirectory = NSFileManager.defaultManager.URLForDirectory( + directory = NSDocumentDirectory, + inDomain = NSUserDomainMask, + appropriateForURL = null, + create = false, + error = errorPtr.ptr, + ) + if (documentDirectory != null) { + return requireNotNull(documentDirectory.path) { + """Couldn't determine the document directory. + URL $documentDirectory does not conform to RFC 1808. + """.trimIndent() + } + } else { + val error = errorPtr.value + val localizedDescription = error?.localizedDescription ?: "Unknown error occurred" + error("Couldn't determine document directory. Error: $localizedDescription") + } + } +} \ No newline at end of file