";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 34B4ABAF64716B23BDD264EE /* iosApp */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 44EE2F705AA6B3E538FC9729 /* Build configuration list for PBXNativeTarget "iosApp" */;
+ buildPhases = (
+ 4FFC3A21D86797FC93CDECDC /* Compile Kotlin Framework */,
+ AB343A86E64355BAE6CCAADC /* Sources */,
+ EC665D61C40D31409FE43A2C /* Frameworks */,
+ 7D3DFA4B6B247EAE6846A6D5 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ fileSystemSynchronizedGroups = (
+ 19EF4AE01AADB1C64EB88AEF /* iosApp */,
+ );
+ name = iosApp;
+ packageProductDependencies = (
+ );
+ productName = iosApp;
+ productReference = 55C08254D962F05ED0EB3AF8 /* kmpktor.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 11E239104E5257BD886F5A0E /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastSwiftUpdateCheck = 1620;
+ LastUpgradeCheck = 1620;
+ TargetAttributes = {
+ 34B4ABAF64716B23BDD264EE = {
+ CreatedOnToolsVersion = 16.2;
+ };
+ };
+ };
+ buildConfigurationList = 95550963E9C107F3BB841101 /* Build configuration list for PBXProject "iosApp" */;
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 2473A6EB23F4EA8A4ED7FAB4;
+ minimizedProjectReferenceProxies = 1;
+ preferredProjectObjectVersion = 77;
+ productRefGroup = 5D365FA1C446636B4497ECBE /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 34B4ABAF64716B23BDD264EE /* iosApp */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 7D3DFA4B6B247EAE6846A6D5 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 4FFC3A21D86797FC93CDECDC /* Compile Kotlin Framework */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ );
+ name = "Compile Kotlin Framework";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :shared:embedAndSignAppleFrameworkForXcode\n";
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ AB343A86E64355BAE6CCAADC /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 1C67FC9B29EF706187D6989B /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReferenceAnchor = 9F34E57D8491B92E09F4A629 /* Configuration */;
+ baseConfigurationReferenceRelativePath = Config.xcconfig;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 18.2;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ B0727A860545A25724558465 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReferenceAnchor = 9F34E57D8491B92E09F4A629 /* Configuration */;
+ baseConfigurationReferenceRelativePath = Config.xcconfig;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 18.2;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ D41B9A0C117F9D22C1C516C3 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ARCHS = arm64;
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
+ DEVELOPMENT_TEAM = "${TEAM_ID}";
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = iosApp/Info.plist;
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 30BF76ABBBFEC761F8EDAB0A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ARCHS = arm64;
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
+ DEVELOPMENT_TEAM = "${TEAM_ID}";
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = iosApp/Info.plist;
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 95550963E9C107F3BB841101 /* Build configuration list for PBXProject "iosApp" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 1C67FC9B29EF706187D6989B /* Debug */,
+ B0727A860545A25724558465 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 44EE2F705AA6B3E538FC9729 /* Build configuration list for PBXNativeTarget "iosApp" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ D41B9A0C117F9D22C1C516C3 /* Debug */,
+ 30BF76ABBBFEC761F8EDAB0A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 11E239104E5257BD886F5A0E /* Project object */;
+}
\ No newline at end of file
diff --git a/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000..919434a62
--- /dev/null
+++ b/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 000000000..0afb3cf0e
--- /dev/null
+++ b/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors": [
+ {
+ "idiom": "universal"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ }
+}
diff --git a/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..c70a5bff1
--- /dev/null
+++ b/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,35 @@
+{
+ "images": [
+ {
+ "idiom": "universal",
+ "platform": "ios",
+ "size": "1024x1024"
+ },
+ {
+ "appearances": [
+ {
+ "appearance": "luminosity",
+ "value": "dark"
+ }
+ ],
+ "idiom": "universal",
+ "platform": "ios",
+ "size": "1024x1024"
+ },
+ {
+ "appearances": [
+ {
+ "appearance": "luminosity",
+ "value": "tinted"
+ }
+ ],
+ "idiom": "universal",
+ "platform": "ios",
+ "size": "1024x1024"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ }
+}
diff --git a/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/Assets.xcassets/Contents.json b/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/Assets.xcassets/Contents.json
new file mode 100644
index 000000000..74d6a722c
--- /dev/null
+++ b/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info": {
+ "author": "xcode",
+ "version": 1
+ }
+}
diff --git a/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/ContentView.swift b/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/ContentView.swift
new file mode 100644
index 000000000..8c7c2216d
--- /dev/null
+++ b/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/ContentView.swift
@@ -0,0 +1,26 @@
+import SwiftUI
+import Shared
+
+struct ContentView: View {
+ @StateObject private var viewModel = ViewModel()
+
+ var body: some View {
+ Text(viewModel.text)
+ }
+}
+
+extension ContentView {
+ @MainActor
+ class ViewModel: ObservableObject {
+ @Published var text = "Loading..."
+ init() {
+ Greeting().greet { greeting, error in
+ if let greeting = greeting {
+ self.text = greeting
+ } else {
+ self.text = error?.localizedDescription ?? "error"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/Info.plist b/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/Info.plist
new file mode 100644
index 000000000..99d16fa30
--- /dev/null
+++ b/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/Info.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ CADisableMinimumFrameDurationOnPhone
+
+
+
diff --git a/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json b/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json
new file mode 100644
index 000000000..74d6a722c
--- /dev/null
+++ b/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info": {
+ "author": "xcode",
+ "version": 1
+ }
+}
diff --git a/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/iOSApp.swift b/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/iOSApp.swift
new file mode 100644
index 000000000..d83dca611
--- /dev/null
+++ b/codeSnippets/snippets/tutorial-client-kmp/iosApp/iosApp/iOSApp.swift
@@ -0,0 +1,10 @@
+import SwiftUI
+
+@main
+struct iOSApp: App {
+ var body: some Scene {
+ WindowGroup {
+ ContentView()
+ }
+ }
+}
\ No newline at end of file
diff --git a/codeSnippets/snippets/tutorial-client-kmp/settings.gradle.kts b/codeSnippets/snippets/tutorial-client-kmp/settings.gradle.kts
new file mode 100644
index 000000000..f45400c88
--- /dev/null
+++ b/codeSnippets/snippets/tutorial-client-kmp/settings.gradle.kts
@@ -0,0 +1,32 @@
+rootProject.name = "kmpktor"
+enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
+
+pluginManagement {
+ repositories {
+ google {
+ mavenContent {
+ includeGroupAndSubgroups("androidx")
+ includeGroupAndSubgroups("com.android")
+ includeGroupAndSubgroups("com.google")
+ }
+ }
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+dependencyResolutionManagement {
+ repositories {
+ google {
+ mavenContent {
+ includeGroupAndSubgroups("androidx")
+ includeGroupAndSubgroups("com.android")
+ includeGroupAndSubgroups("com.google")
+ }
+ }
+ mavenCentral()
+ }
+}
+
+include(":composeApp")
+include(":shared")
\ No newline at end of file
diff --git a/codeSnippets/snippets/tutorial-client-kmm/shared/build.gradle.kts b/codeSnippets/snippets/tutorial-client-kmp/shared/build.gradle.kts
similarity index 58%
rename from codeSnippets/snippets/tutorial-client-kmm/shared/build.gradle.kts
rename to codeSnippets/snippets/tutorial-client-kmp/shared/build.gradle.kts
index e91329f45..61c67fc26 100644
--- a/codeSnippets/snippets/tutorial-client-kmm/shared/build.gradle.kts
+++ b/codeSnippets/snippets/tutorial-client-kmp/shared/build.gradle.kts
@@ -1,3 +1,5 @@
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidLibrary)
@@ -5,20 +7,17 @@ plugins {
kotlin {
androidTarget {
- compilations.all {
- kotlinOptions {
- jvmTarget = "1.8"
- }
+ compilerOptions {
+ jvmTarget.set(JvmTarget.JVM_11)
}
}
-
+
listOf(
- iosX64(),
iosArm64(),
iosSimulatorArm64()
- ).forEach {
- it.binaries.framework {
- baseName = "shared"
+ ).forEach { iosTarget ->
+ iosTarget.binaries.framework {
+ baseName = "Shared"
isStatic = true
}
}
@@ -28,22 +27,26 @@ kotlin {
implementation(libs.ktor.client.core)
implementation(libs.kotlinx.coroutines.core)
}
- commonTest.dependencies {
- implementation(libs.kotlin.test)
- }
androidMain.dependencies {
implementation(libs.ktor.client.okhttp)
}
iosMain.dependencies {
implementation(libs.ktor.client.darwin)
}
+ commonTest.dependencies {
+ implementation(libs.kotlin.test)
+ }
}
}
android {
- namespace = "com.example.kmmktor"
- compileSdk = 34
+ namespace = "com.example.ktor.kmpktor.shared"
+ compileSdk = libs.versions.android.compileSdk.get().toInt()
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
defaultConfig {
- minSdk = 25
+ minSdk = libs.versions.android.minSdk.get().toInt()
}
-}
+}
\ No newline at end of file
diff --git a/codeSnippets/snippets/tutorial-client-kmp/shared/src/androidMain/kotlin/com/example/ktor/kmpktor/Platform.android.kt b/codeSnippets/snippets/tutorial-client-kmp/shared/src/androidMain/kotlin/com/example/ktor/kmpktor/Platform.android.kt
new file mode 100644
index 000000000..774512566
--- /dev/null
+++ b/codeSnippets/snippets/tutorial-client-kmp/shared/src/androidMain/kotlin/com/example/ktor/kmpktor/Platform.android.kt
@@ -0,0 +1,9 @@
+package com.example.ktor.kmpktor
+
+import android.os.Build
+
+class AndroidPlatform : Platform {
+ override val name: String = "Android ${Build.VERSION.SDK_INT}"
+}
+
+actual fun getPlatform(): Platform = AndroidPlatform()
\ No newline at end of file
diff --git a/codeSnippets/snippets/tutorial-client-kmp/shared/src/commonMain/kotlin/com/example/ktor/kmpktor/Greeting.kt b/codeSnippets/snippets/tutorial-client-kmp/shared/src/commonMain/kotlin/com/example/ktor/kmpktor/Greeting.kt
new file mode 100644
index 000000000..6e6f8e1e3
--- /dev/null
+++ b/codeSnippets/snippets/tutorial-client-kmp/shared/src/commonMain/kotlin/com/example/ktor/kmpktor/Greeting.kt
@@ -0,0 +1,14 @@
+package com.example.ktor.kmpktor
+
+import io.ktor.client.HttpClient
+import io.ktor.client.request.get
+import io.ktor.client.statement.bodyAsText
+
+class Greeting {
+ private val client = HttpClient()
+
+ suspend fun greet(): String {
+ val response = client.get("https://ktor.io/docs/")
+ return response.bodyAsText()
+ }
+}
\ No newline at end of file
diff --git a/codeSnippets/snippets/tutorial-client-kmm/shared/src/commonMain/kotlin/com/example/kmmktor/Platform.kt b/codeSnippets/snippets/tutorial-client-kmp/shared/src/commonMain/kotlin/com/example/ktor/kmpktor/Platform.kt
similarity index 70%
rename from codeSnippets/snippets/tutorial-client-kmm/shared/src/commonMain/kotlin/com/example/kmmktor/Platform.kt
rename to codeSnippets/snippets/tutorial-client-kmp/shared/src/commonMain/kotlin/com/example/ktor/kmpktor/Platform.kt
index 0528b6037..79bd2b114 100644
--- a/codeSnippets/snippets/tutorial-client-kmm/shared/src/commonMain/kotlin/com/example/kmmktor/Platform.kt
+++ b/codeSnippets/snippets/tutorial-client-kmp/shared/src/commonMain/kotlin/com/example/ktor/kmpktor/Platform.kt
@@ -1,4 +1,4 @@
-package com.example.kmmktor
+package com.example.ktor.kmpktor
interface Platform {
val name: String
diff --git a/codeSnippets/snippets/tutorial-client-kmp/shared/src/commonTest/kotlin/com/example/ktor/kmpktor/SharedCommonTest.kt b/codeSnippets/snippets/tutorial-client-kmp/shared/src/commonTest/kotlin/com/example/ktor/kmpktor/SharedCommonTest.kt
new file mode 100644
index 000000000..f885f4121
--- /dev/null
+++ b/codeSnippets/snippets/tutorial-client-kmp/shared/src/commonTest/kotlin/com/example/ktor/kmpktor/SharedCommonTest.kt
@@ -0,0 +1,12 @@
+package com.example.ktor.kmpktor
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class SharedCommonTest {
+
+ @Test
+ fun example() {
+ assertEquals(3, 1 + 2)
+ }
+}
\ No newline at end of file
diff --git a/codeSnippets/snippets/tutorial-client-kmm/shared/src/iosMain/kotlin/com/example/kmmktor/Platform.ios.kt b/codeSnippets/snippets/tutorial-client-kmp/shared/src/iosMain/kotlin/com/example/ktor/kmpktor/Platform.ios.kt
similarity index 75%
rename from codeSnippets/snippets/tutorial-client-kmm/shared/src/iosMain/kotlin/com/example/kmmktor/Platform.ios.kt
rename to codeSnippets/snippets/tutorial-client-kmp/shared/src/iosMain/kotlin/com/example/ktor/kmpktor/Platform.ios.kt
index 7e5bf604d..faa7b46c1 100644
--- a/codeSnippets/snippets/tutorial-client-kmm/shared/src/iosMain/kotlin/com/example/kmmktor/Platform.ios.kt
+++ b/codeSnippets/snippets/tutorial-client-kmp/shared/src/iosMain/kotlin/com/example/ktor/kmpktor/Platform.ios.kt
@@ -1,8 +1,8 @@
-package com.example.kmmktor
+package com.example.ktor.kmpktor
import platform.UIKit.UIDevice
-class IOSPlatform: Platform {
+class IOSPlatform : Platform {
override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}
diff --git a/images/tutorial_client_kmm_android.png b/images/tutorial_client_kmm_android.png
deleted file mode 100644
index ae841cea6..000000000
Binary files a/images/tutorial_client_kmm_android.png and /dev/null differ
diff --git a/images/tutorial_client_kmm_ios.png b/images/tutorial_client_kmm_ios.png
deleted file mode 100644
index 25ff2c3ae..000000000
Binary files a/images/tutorial_client_kmm_ios.png and /dev/null differ
diff --git a/images/tutorial_client_kmp_android.png b/images/tutorial_client_kmp_android.png
new file mode 100644
index 000000000..535fa3dbf
Binary files /dev/null and b/images/tutorial_client_kmp_android.png differ
diff --git a/images/tutorial_client_kmp_create_project.png b/images/tutorial_client_kmp_create_project.png
new file mode 100644
index 000000000..084a4037f
Binary files /dev/null and b/images/tutorial_client_kmp_create_project.png differ
diff --git a/images/tutorial_client_kmp_create_project_dark.png b/images/tutorial_client_kmp_create_project_dark.png
new file mode 100644
index 000000000..1b2fcfe28
Binary files /dev/null and b/images/tutorial_client_kmp_create_project_dark.png differ
diff --git a/images/tutorial_client_kmp_ios.png b/images/tutorial_client_kmp_ios.png
new file mode 100644
index 000000000..e6bd97f34
Binary files /dev/null and b/images/tutorial_client_kmp_ios.png differ
diff --git a/images/tutorial_client_kmp_run_android.png b/images/tutorial_client_kmp_run_android.png
new file mode 100644
index 000000000..515bf1366
Binary files /dev/null and b/images/tutorial_client_kmp_run_android.png differ
diff --git a/images/tutorial_client_kmp_run_android_dark.png b/images/tutorial_client_kmp_run_android_dark.png
new file mode 100644
index 000000000..675e28d94
Binary files /dev/null and b/images/tutorial_client_kmp_run_android_dark.png differ
diff --git a/images/tutorial_client_kmp_run_ios.png b/images/tutorial_client_kmp_run_ios.png
new file mode 100644
index 000000000..b1c9b93dc
Binary files /dev/null and b/images/tutorial_client_kmp_run_ios.png differ
diff --git a/images/tutorial_client_kmp_run_ios_dark.png b/images/tutorial_client_kmp_run_ios_dark.png
new file mode 100644
index 000000000..4d4225886
Binary files /dev/null and b/images/tutorial_client_kmp_run_ios_dark.png differ
diff --git a/topics/client-create-multiplatform-application.md b/topics/client-create-multiplatform-application.md
index 2f616535a..7baaa42b8 100644
--- a/topics/client-create-multiplatform-application.md
+++ b/topics/client-create-multiplatform-application.md
@@ -3,23 +3,17 @@
-
+
-
-Video: Ktor for Networking in Kotlin Multiplatform Mobile projects
-
-Learn how to create a Kotlin Multiplatform Mobile application.
+Learn how to use the Ktor client in a Kotlin Multiplatform Mobile application.
The Ktor HTTP client can be used in multiplatform projects. In this tutorial, we'll create a simple Kotlin Multiplatform
Mobile application, which sends a request and receives a response body as plain HTML text.
-> To learn how to create your first Kotlin Multiplatform Mobile application,
-see [Create your first cross-platform mobile app](https://kotlinlang.org/docs/multiplatform-mobile-create-first-app.html).
-
## Prerequisites {id="prerequisites"}
First, you need to set up an environment for cross-platform mobile development by installing the necessary tools on a
@@ -27,158 +21,149 @@ suitable operating system. Learn how to do this from
the [Set up an environment](https://kotlinlang.org/docs/multiplatform-mobile-setup.html) section.
> You will need a Mac with macOS to complete certain steps in this tutorial, which include writing iOS-specific code and
-running an iOS application.
+> running an iOS application.
>
{style="note"}
## Create a new project {id="new-project"}
-To start a new Kotlin Multiplatform project, there are two approaches available:
+To create a new project, you can use the Kotlin Multiplatform project wizard in IntelliJ IDEA. It will create a basic
+multiplatform project which you can expand with clients and services.
-- You can create a project from a template within Android Studio.
-- Alternatively, you can use the [Kotlin Multiplatform Wizard](https://kmp.jetbrains.com/) to generate a new project.
- The wizard provides options for customizing your project setup, allowing you to exclude Android support
- or include a Ktor Server, for instance.
+
-For the sake of this tutorial, we will demonstrate the process of creating a project from a template:
+1. Launch IntelliJ IDEA.
+2. In IntelliJ IDEA, select **File | New | Project**.
+3. In the panel on the left, select **Kotlin Multiplatform**.
+4. Specify the following fields in the **New Project** window:
+ * **Name**: KmpKtor
+ * **Group**: com.example.ktor
+ { width="450" width="706" border-effect="rounded" style="block" }
+5. Select **Android** and **iOS** targets.
+6. For iOS, select the **Do not share UI** option to keep the UI native.
+7. Click the **Create** button and wait for the IDE to generate and import the project.
-1. In Android Studio, select **File | New | New Project**.
-2. Select **Kotlin Multiplatform App** in the list of project templates, and click **Next**.
-3. Specify a name for your application, and click **Next**. In our tutorial, the application name is `KmmKtor`.
-4. On the next page, leave the default settings and click **Finish** to create a new project.
- Now, wait while your project is set up. It may take some time to download and set up the required components when you
- do this for the first time.
- > To view the complete structure of the generated multiplatform project, switch from **Android** to **Project** in
- a [Project view](https://developer.android.com/studio/projects#ProjectView).
+
## Configure build scripts {id="build-script"}
-### Update Kotlin Gradle plugins {id="update_gradle_plugins"}
-
-Open the `gradle/libs.versions.toml` file and update the Kotlin version to the latest:
-
-```kotlin
-```
-
-{src="snippets/tutorial-client-kmm/gradle/libs.versions.toml" include-lines="3"}
-
-
-
### Add Ktor dependencies {id="ktor-dependencies"}
To use the Ktor HTTP client in your project, you need to add at least two dependencies: a client dependency and an
-engine dependency.
-
-In the `gradle/libs.versions.toml` file add the Ktor version:
-
-```kotlin
-```
-
-{src="snippets/tutorial-client-kmm/gradle/libs.versions.toml" include-lines="1,5"}
-
-
-
-Then, define the Ktor client and engine libraries:
-
-```kotlin
-```
-
-{src="snippets/tutorial-client-kmm/gradle/libs.versions.toml" include-lines="11,19-21"}
-
-To add the dependencies, open the `shared/build.gradle.kts` file and follow the steps below:
+[engine](client-engines.md) dependency.
-1. To use the Ktor client in common code, add the dependency `ktor-client-core` to the `commonMain` source set:
- ```kotlin
- ```
- {src="snippets/tutorial-client-kmm/shared/build.gradle.kts" include-lines="26-28,30,40"}
+1. Open the
+ gradle/libs.versions.toml
+ file add the Ktor version:
+
+ ```kotlin
+ ```
+
+ {src="snippets/tutorial-client-kmm/gradle/libs.versions.toml" include-lines="1,15"}
-2. Add an [engine dependency](client-engines.md) for each required platform to the corresponding source set:
- - For Android, add the `ktor-client-okhttp` dependency to the `androidMain` source set:
- ```kotlin
- ```
- {src="snippets/tutorial-client-kmm/shared/build.gradle.kts" include-lines="34-36"}
+2. In the same
+ gradle/libs.versions.toml
+ define the Ktor client and engine libraries:
+
+ ```kotlin
+ ```
+
+ {src="snippets/tutorial-client-kmm/gradle/libs.versions.toml" include-lines="18,29-31"}
- For Android, you can also use [other engine types](client-engines.md#jvm-android).
- - For iOS, add the `ktor-client-darwin` dependency to `iosMain`:
- ```kotlin
- ```
- {src="snippets/tutorial-client-kmm/shared/build.gradle.kts" include-lines="37-39"}
+3. Open the
+ shared/build.gradle.kts
+ file and add the following dependencies:
+
+ ```kotlin
+ ```
+
+ {src="snippets/tutorial-client-kmm/shared/build.gradle.kts" include-lines="25-27,29-35,39"}
+
+ - Add the `ktor-client-core` to the `commonMain` source set to enable Ktor client functionality in shared code.
+ - In the `androidMain` source set, include the `ktor-client-okhttp` dependency to use the `OkHttp` engine on Android.
+ Alternatively, you can choose from [other available Android/JVM engines](client-engines.md#jvm-android).
+ - In the `iosMain` source set, add the `ktor-client-darwin` dependency to use the Darwin engine on iOS.
### Add coroutines {id="coroutines"}
To use coroutines in [Android code](#android-activity), you need to add `kotlinx.coroutines` to your project:
-1. Open the `gradle/libs.versions.toml` file and specify the coroutines version and libraries:
+1. Open the
+ gradle/libs.versions.toml
+ file and specify the coroutines version and libraries:
```kotlin
```
- {src="snippets/tutorial-client-kmm/gradle/libs.versions.toml" include-lines="1,4,10-11,22-23"}
+ {src="snippets/tutorial-client-kmm/gradle/libs.versions.toml" include-lines="1,16-18,32-33"}
-2. Open the `build.gradle.kts` file and add the `kotlinx-coroutines-core` dependency to the `commonMain` source set:
+2. Open the
+ shared/build.gradle.kts
+ file and add the `kotlinx-coroutines-core` dependency to the `commonMain` source set:
```kotlin
```
- {src="snippets/tutorial-client-kmm/shared/build.gradle.kts" include-lines="26-30,40"}
+ {src="snippets/tutorial-client-kmm/shared/build.gradle.kts" include-lines="25-29,39"}
-3. Then, open the `androidApp/build.gradle.kts` and add the `kotlinx-coroutines-android` dependency:
-
-```kotlin
-```
+3. Then, open the
+ composeApp/build.gradle.kts
+ file and add the `kotlinx-coroutines-android` dependency to the `androidMain` source set:
-{src="snippets/tutorial-client-kmm/androidApp/build.gradle.kts" include-lines="39,45,47"}
+ ```kotlin
+ ```
+ {src="snippets/tutorial-client-kmm/composeApp/build.gradle.kts" include-lines="18-19,22-24,39"}
-Click **Sync Now** in the top right corner of the `gradle.properties` file to install the added dependencies.
+4. Select **Build | Sync Project with Gradle Files** to install the added dependencies.
## Update your application {id="code"}
### Shared code {id="shared-code"}
-To update code shared between Android and iOS, open the `shared/src/commonMain/kotlin/com/example/kmmktor/Greeting.kt`
+To update the code shared between Android and iOS, open the
+shared/src/commonMain/kotlin/com/example/ktor/kmmktor/Greeting.kt
file and add the following code to the `Greeting` class:
```kotlin
```
-{src="snippets/tutorial-client-kmm/shared/src/commonMain/kotlin/com/example/kmmktor/Greeting.kt"}
+{src="snippets/tutorial-client-kmm/shared/src/commonMain/kotlin/com/example/ktor/kmmktor/Greeting.kt"}
-- To create the HTTP client, the `HttpClient` constructor is called.
-- The suspending `greeting` function is used to make a [request](client-requests.md) and receive the body of
+- The `HttpClient` constructor creates the HTTP client.
+- The suspending `greet()` function makes a [request](client-requests.md) and receives the body of
a [response](client-responses.md) as a string value.
### Android code {id="android-activity"}
-To call the suspending `greeting` function from the Android code, we'll be
-using [rememberCoroutineScope](https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary#rememberCoroutineScope(kotlin.Function0)).
-
-Open the `androidApp/src/main/java/com/example/kmmktor/android/MainActivity.kt` file and update `MainActivity` code as
-follows:
+Open the
+composeApp/src/androidMain/kotlin/com/example/ktor/kmmktor/App.kt
+file and update the code as follows:
```kotlin
```
-{src="snippets/tutorial-client-kmm/androidApp/src/main/java/com/example/kmmktor/android/MainActivity.kt"}
+{src="snippets/tutorial-client-kmm/composeApp/src/androidMain/kotlin/com/example/ktor/kmmktor/App.kt"}
-Inside the created scope, we can call the shared `greeting` function and handle possible exceptions.
+`LaunchedEffect()` launches a coroutine tied to the composable’s lifecycle. Within this coroutine, the shared `greet()`
+function is called, its result is assigned to `text`, and any exceptions are caught and handled.
### iOS code {id="ios-view"}
-1. Open the `iosApp/iosApp/iOSApp.swift` file and update the entry point for the application:
- ```Swift
- ```
- {src="snippets/tutorial-client-kmm/iosApp/iosApp/iOSApp.swift"}
+Open the
+iosApp/iosApp/ContentView.swift
+file and update the code in the following way:
-2. Open the `iosApp/iosApp/ContentView.swift` file and update `ContentView` code in the following way:
- ```Swift
- ```
- {src="snippets/tutorial-client-kmm/iosApp/iosApp/ContentView.swift"}
+```Swift
+```
+
+{src="snippets/tutorial-client-kmm/iosApp/iosApp/ContentView.swift"}
- On iOS, the `greeting` suspending function is available as a function with a callback.
+On iOS, the `greet()` suspending function is available as a function with a callback.
## Enable internet access on Android {id="android-internet"}
-The final thing we need to do is to enable internet access for the Android application.
-Open the `androidApp/src/main/AndroidManifest.xml` file and enable the required permission using the `uses-permission`
-element:
+The final step is to enable internet access for the Android application.
+Open the
+composeApp/src/androidMain/AndroidManifest.xml
+file and enable the required permission using the `<uses-permission>` element:
```xml
@@ -189,24 +174,34 @@ element:
```
-## Run your application {id="run"}
+## Run your application on Android {id="run-android"}
-To run the created multiplatform application on the Android or iOS simulator, select **androidApp** or **iosApp** and
-click **Run**.
-The simulator should display the received HTML document as plain text.
+1. In IntelliJ IDEA, select **composeApp** in the list of run configurations.
+2. Choose an Android virtual device next to the list of configurations and click **Run**.
+ {width="381" style="block"}
-
-
+ If you don't have a device in the list, create
+ a [new Android virtual device](https://developer.android.com/studio/run/managing-avds#createavd).
+3. Once loaded, the simulator should display the received HTML document as plain text.
+ {width="381" style="block"}
+
+> If your Android emulator cannot connect to the internet, try performing a cold boot.
+> In the **Device Manager** tool window, click the **⋮** (three dots) next to a stopped device and select **Cold Boot**
+> from the menu. This often helps clear corrupted emulator caches that can cause connectivity issues.
+>
+{style="tip"}
-{width="381"}
+## Run your application on iOS {id="run-ios"}
-
-
+1. In IntelliJ IDEA, select **iosApp** in the list of run configurations.
+2. Choose an iOS simulated device next to the list of configurations and click **Run**.
+ {width="381" style="block"}
-{width="351"}
+ If you don't have an available iOS configuration in the list, add a
+ [new run configuration](https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-create-first-app.html#run-on-a-new-ios-simulated-device).
+3. Once loaded, the simulator should display the received HTML document as plain text.
+ {width="381" style="block"}
-
-