|
| 1 | +# Creating the Shared Module |
| 2 | + |
| 3 | +The goal of this tutorial is to demonstrate the reusability of Kotlin code between Android and iOS. Let's start |
| 4 | +by manually creating a `SharedCode` sub-project in our Gradle project. The source code from the `SharedCode` |
| 5 | +project will be shared between platforms. |
| 6 | +We will create several new files in our project to implement this. |
| 7 | + |
| 8 | +## Updating Gradle Scripts |
| 9 | + |
| 10 | +The `SharedCode` sub-project should generate several artifacts for us: |
| 11 | + - A JAR file for the Android project, from the `androidMain` source set |
| 12 | + - The Apple framework |
| 13 | + - for iOS device and App Store (`arm64` target) |
| 14 | + - for iOS simulator (`x86_64` target) |
| 15 | + |
| 16 | +Let's update the Gradle scripts now to implement this and configure our IDE. |
| 17 | +First, we add the new project to the `settings.gradle` file, simply by adding the following line to the end of the file: |
| 18 | + |
| 19 | +```groovy |
| 20 | +include ':SharedCode' |
| 21 | +``` |
| 22 | + |
| 23 | +Next, |
| 24 | +we need to create a `SharedCode/build.gradle.kts` file with the following content: |
| 25 | + |
| 26 | +```kotlin |
| 27 | +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget |
| 28 | + |
| 29 | +plugins { |
| 30 | + kotlin("multiplatform") |
| 31 | +} |
| 32 | + |
| 33 | +kotlin { |
| 34 | + //select iOS target platform depending on the Xcode environment variables |
| 35 | + val iOSTarget: (String, KotlinNativeTarget.() -> Unit) -> KotlinNativeTarget = |
| 36 | + if (System.getenv("SDK_NAME")?.startsWith("iphoneos") == true) |
| 37 | + ::iosArm64 |
| 38 | + else |
| 39 | + ::iosX64 |
| 40 | + |
| 41 | + iOSTarget("ios") { |
| 42 | + binaries { |
| 43 | + framework { |
| 44 | + baseName = "SharedCode" |
| 45 | + } |
| 46 | + } |
| 47 | + } |
| 48 | + |
| 49 | + jvm("android") |
| 50 | + |
| 51 | + sourceSets["commonMain"].dependencies { |
| 52 | + implementation("org.jetbrains.kotlin:kotlin-stdlib-common") |
| 53 | + } |
| 54 | + |
| 55 | + sourceSets["androidMain"].dependencies { |
| 56 | + implementation("org.jetbrains.kotlin:kotlin-stdlib") |
| 57 | + } |
| 58 | +} |
| 59 | + |
| 60 | +val packForXcode by tasks.creating(Sync::class) { |
| 61 | + val targetDir = File(buildDir, "xcode-frameworks") |
| 62 | + |
| 63 | + /// selecting the right configuration for the iOS |
| 64 | + /// framework depending on the environment |
| 65 | + /// variables set by Xcode build |
| 66 | + val mode = System.getenv("CONFIGURATION") ?: "DEBUG" |
| 67 | + val framework = kotlin.targets |
| 68 | + .getByName<KotlinNativeTarget>("ios") |
| 69 | + .binaries.getFramework(mode) |
| 70 | + inputs.property("mode", mode) |
| 71 | + dependsOn(framework.linkTask) |
| 72 | + |
| 73 | + from({ framework.outputDirectory }) |
| 74 | + into(targetDir) |
| 75 | + |
| 76 | + /// generate a helpful ./gradlew wrapper with embedded Java path |
| 77 | + doLast { |
| 78 | + val gradlew = File(targetDir, "gradlew") |
| 79 | + gradlew.writeText("#!/bin/bash\n" |
| 80 | + + "export 'JAVA_HOME=${System.getProperty("java.home")}'\n" |
| 81 | + + "cd '${rootProject.rootDir}'\n" |
| 82 | + + "./gradlew \$@\n") |
| 83 | + gradlew.setExecutable(true) |
| 84 | + } |
| 85 | +} |
| 86 | + |
| 87 | +tasks.getByName("build").dependsOn(packForXcode) |
| 88 | +``` |
| 89 | + |
| 90 | +We need to refresh the Gradle project to apply these changes. Click on the `Sync Now` link or |
| 91 | +use the *Gradle* tool window and click the refresh action from the context menu on the root Gradle project. |
| 92 | +The `packForXcode` Gradle task is used for Xcode project integration. We will discuss this later in the |
| 93 | +tutorial. |
| 94 | + |
| 95 | +## Adding Kotlin Sources |
| 96 | + |
| 97 | +The idea is to make every platform show similar text: `Kotlin Rocks on Android` and |
| 98 | +`Kotlin Rocks on iOS`, depending on the platform. We will reuse the way we generate the message. |
| 99 | +Let's create the file (and missing directories) `SharedCode/src/commonMain/kotlin/common.kt` with the following contents |
| 100 | +under the project root directory |
| 101 | + |
| 102 | +```kotlin |
| 103 | +package com.jetbrains.handson.mpp.mobile |
| 104 | + |
| 105 | +expect fun platformName(): String |
| 106 | + |
| 107 | +fun createApplicationScreenMessage() : String { |
| 108 | + return "Kotlin Rocks on ${platformName()}" |
| 109 | +} |
| 110 | + |
| 111 | +``` |
| 112 | + |
| 113 | +That is the common part. The code to generate the final message. It `expect`s the platform part |
| 114 | +to provide the platform-specific name from the `expect fun platformName(): String` function. We will use |
| 115 | +the `createApplicationScreenMessage` from both Android and iOS applications. |
| 116 | + |
| 117 | +Now we need to create the implementation file (and missing directories) for Android in the `SharedCode/src/androidMain/kotlin/actual.kt`: |
| 118 | +```kotlin |
| 119 | +package com.jetbrains.handson.mpp.mobile |
| 120 | + |
| 121 | +actual fun platformName(): String { |
| 122 | + return "Android" |
| 123 | +} |
| 124 | + |
| 125 | +``` |
| 126 | + |
| 127 | +We create a similar implementation file (and missing directories) for the iOS target in the `SharedCode/src/iosMain/kotlin/actual.kt`: |
| 128 | +```kotlin |
| 129 | +package com.jetbrains.handson.mpp.mobile |
| 130 | + |
| 131 | +import platform.UIKit.UIDevice |
| 132 | + |
| 133 | +actual fun platformName(): String { |
| 134 | + return UIDevice.currentDevice.systemName() + |
| 135 | + " " + |
| 136 | + UIDevice.currentDevice.systemVersion |
| 137 | +} |
| 138 | +``` |
| 139 | + |
| 140 | +Here we can use the [UIDevice](https://developer.apple.com/documentation/uikit/uidevice?language=objc) |
| 141 | +class from the Apple UIKit Framework, which is not available in Java, it is only usable in Swift and Objective-C. |
| 142 | +The Kotlin/Native compiler comes with a set of pre-imported frameworks, so we can use |
| 143 | +the UIKit Framework without having to do any additional steps. |
| 144 | +The Objective-C and Swift Interop is covered in detail in the [documentation](/docs/reference/native/objc_interop.html) |
| 145 | + |
| 146 | +## Multiplatform Gradle Project |
| 147 | + |
| 148 | +The `SharedCode/build.gradle.kts` file uses the `kotlin-multiplatform` plugin to implement |
| 149 | +what we need. |
| 150 | +In the file, we define several targets `common`, `android`, and `iOS`. Each |
| 151 | +target has its own platform. The `common` target contains the common Kotlin code |
| 152 | +which is included into every platform compilation. It is allowed to have `expect` declarations. |
| 153 | +Other targets provide `actual` implementations for all the `expect`-actions from the `common` target. |
| 154 | +A more detailed explanation of multiplatform projects can be found in the |
| 155 | +[Multiplatform Projects](/docs/reference/building-mpp-with-gradle.html) documentation. |
| 156 | + |
| 157 | +Let's summarize what we have in the table: |
| 158 | + |
| 159 | +| name | source folder | target | artifact | |
| 160 | +|---|---|---|---| |
| 161 | +| common | `SharedCode/commonMain/kotlin` | - | Kotlin metadata | |
| 162 | +| android | `SharedCode/androidMain/kotlin` | JVM 1.6 | `.jar` file or `.class` files | |
| 163 | +| iOS | `SharedCode/iosMain` | iOS arm64 or x86_64| Apple framework | |
| 164 | + |
| 165 | +Now it is again time to refresh the Gradle project in Android Studio. Click *Sync Now* on the yellow stripe |
| 166 | +or use the *Gradle* tool window and click the `Refresh` action in the context menu on the root Gradle project. |
| 167 | +The `:SharedCode` project should now be recognized by the IDE. |
| 168 | + |
| 169 | +We can use the `step-004` branch from the |
| 170 | +[github.com/kotlin-hands-on/mpp-ios-android](https://github.com/kotlin-hands-on/mpp-ios-android/tree/step-004) |
| 171 | +repository as a solution for the tasks that we've done above. We can also download the |
| 172 | +[archive](https://github.com/kotlin-hands-on/mpp-ios-android/archive/step-004.zip) from GitHub directly |
| 173 | +or check out the repository and select the branch. |
| 174 | + |
| 175 | +Let's use the `SharedCode` library from our Android and iOS applications. |
0 commit comments