diff --git a/mpd.tree b/mpd.tree
index 5533e15d..189507e1 100644
--- a/mpd.tree
+++ b/mpd.tree
@@ -105,6 +105,7 @@
+
@@ -151,6 +152,7 @@
+
diff --git a/topics/compose-onboard/compose-multiplatform-modify-project.md b/topics/compose-onboard/compose-multiplatform-modify-project.md
index 5bb896c2..076d4c3a 100644
--- a/topics/compose-onboard/compose-multiplatform-modify-project.md
+++ b/topics/compose-onboard/compose-multiplatform-modify-project.md
@@ -69,6 +69,7 @@ To use the `kotlinx-datetime` library:
1. Open the `composeApp/src/commonMain/kotlin/App.kt` file and add the following function which returns a string containing the current date:
```kotlin
+ @OptIn(ExperimentalTime::class)
fun todaysDate(): String {
fun LocalDateTime.format() = toString().substringBefore('T')
diff --git a/topics/compose-onboard/compose-multiplatform-new-project.md b/topics/compose-onboard/compose-multiplatform-new-project.md
index a608123d..d67472d7 100644
--- a/topics/compose-onboard/compose-multiplatform-new-project.md
+++ b/topics/compose-onboard/compose-multiplatform-new-project.md
@@ -68,7 +68,8 @@ To get started, implement a new `App` composable:
When you run your application and click the button, the hardcoded time is displayed.
-3. Run the application on the desktop. It works, but the window is clearly too large for the UI:
+3. Run the application on the desktop (with the **composeApp [jvm]** run configuration).
+ It works, but the window is clearly too large for the UI:
{width=400}
@@ -77,7 +78,7 @@ To get started, implement a new `App` composable:
```kotlin
fun main() = application {
val state = rememberWindowState(
- size = DpSize(400.dp, 250.dp),
+ size = DpSize(400.dp, 350.dp),
position = WindowPosition(300.dp, 300.dp)
)
Window(
@@ -94,22 +95,22 @@ To get started, implement a new `App` composable:
Here, you set the title of the window and use the `WindowState` type to give the window an initial size and position on
the screen.
- > To see your changes in real time in the desktop app, use [Compose Hot Reload](compose-hot-reload.md):
- > 1. In the `main.kt` file, click the **Run** icon in the gutter.
- > 2. Select **Run 'composeApp [hotRunJvm]' with Compose Hot Reload (Beta)**.
- > {width=350}
- >
- > To see the app automatically update, save any modified files (⌘ S / Ctrl+S).
- >
- > Compose Hot Reload is currently in [Beta](https://kotlinlang.org/components-stability.html#stability-levels-explained) so its functionality is subject to change.
- >
- {style="tip"}
-
5. Follow the IDE's instructions to import the missing dependencies.
6. Run the desktop application again. Its appearance should improve:
{width=350}
+> To see your changes in real time in the desktop app, use [Compose Hot Reload](compose-hot-reload.md):
+> 1. In the `main.kt` file, click the **Run** icon in the gutter.
+> 2. Select **Run 'composeApp [hotRunJvm]' with Compose Hot Reload (Beta)**.
+ > {width=350}
+>
+> To see the app automatically update, save any modified files (⌘ S / Ctrl+S).
+>
+> Compose Hot Reload is currently in [Beta](https://kotlinlang.org/components-stability.html#stability-levels-explained) so its functionality is subject to change.
+>
+{style="tip"}
+
+
+The sample consists of a single Gradle module (`composeApp`) that contains all the shared code and KMP entry
+points,
+and the `iosApp` project with the iOS-specific code and configuration.
+
+To prepare for the AGP 9 migration, you will:
+
+* [Extract the Android app entry point](#android-app) into a separate `androidApp` module.
+* [Reconfigure the module with shared code](#configure-the-shared-module-to-use-the-android-kmp-library-plugin) (`composeApp`) to use the Android-KMP library plugin
+
+### Module for the Android app entry point {id="android-app"}
+
+#### Create and configure the Android app module
+
+To create a desktop app module (`androidApp`):
+
+1. Create the `androidApp` directory at the root of the project.
+2. Inside that directory, create an empty `build.gradle.kts` file and the `src` directory.
+3. Add the new module to project settings in the `settings.gradle.kts` file by adding this line at the end of the file:
+
+ ```kotlin
+ include(":androidApp")
+ ```
+4. Select **Build | Sync Project with Gradle Files** in the main menu, or click the Gradle refresh button in the
+ editor.
+
+#### Configure the build script for the Android app
+
+Configure the Gradle build script for the new module:
+
+1. In the `gradle/libs.versions.toml` file, add the Kotlin Android Gradle plugin to your version catalog:
+
+ ```text
+ [plugins]
+ kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+ ```
+
+2. In the `androidApp/build.gradle.kts` file, specify the plugins necessary for the Android app module:
+
+ ```kotlin
+ plugins {
+ alias(libs.plugins.kotlinAndroid)
+ alias(libs.plugins.androidApplication)
+ alias(libs.plugins.composeMultiplatform)
+ alias(libs.plugins.composeCompiler)
+ }
+ ```
+
+3. Make sure all of these plugins are mentioned in the **root** `build.gradle.kts` file:
+
+ ```kotlin
+ plugins {
+ alias(libs.plugins.kotlinAndroid) apply false
+ alias(libs.plugins.androidApplication) apply false
+ alias(libs.plugins.composeMultiplatform) apply false
+ alias(libs.plugins.composeCompiler) apply false
+ // ...
+ }
+ ```
+
+4. To add the necessary dependencies, copy existing dependencies from the `androidMain.dependencies {}` block
+ of the `composeApp` build script, and add the dependency on the `composeApp` itself.
+ In this example the end result should look like this:
+
+ ```kotlin
+ kotlin {
+ dependencies {
+ implementation(projects.composeApp)
+ implementation(libs.androidx.activity.compose)
+ implementation(compose.components.uiToolingPreview)
+ }
+ }
+ ```
+
+5. Copy the entire `android {}` block with Android-specific configuration from the `composeApp/build.gradle.kts`
+ file to the `androidApp/build.gradle.kts` file.
+6. To avoid the `Inconsistent JVM Target Compatibility Between Java and Kotlin Tasks` Gradle error, make sure that
+ your Gradle JVM is in sync with the Java version specified in the `android.compileOptions {}` block.
+ For example, if your Gradle JVM is set to JetBrains Runtime 21, adjust the version accordingly in the `androidApp/build.gradle.kts` file:
+
+ ```kotlin
+ android {
+ // ...
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_21
+ targetCompatibility = JavaVersion.VERSION_21
+ }
+ }
+ ```
+7. Change the configuration of the `composeApp` module from an Android application to an Android library,
+ since that's what it effectively becomes. In `composeApp/build.gradle.kts`:
+ * Change the reference to the Gradle plugin:
+
+
+
+ alias(libs.plugins.androidApplication)
+
+
+ alias(libs.plugins.androidLibrary)
+
+
+
+ * Remove the application property lines from the `android.defaultConfig {}` block:
+
+
+
+ defaultConfig {
+ applicationId = "com.jetbrains.demo"
+ minSdk = libs.versions.android.minSdk.get().toInt()
+ targetSdk = libs.versions.android.targetSdk.get().toInt()
+ versionCode = 1
+ versionName = "1.0"
+ }
+
+
+ defaultConfig {
+ minSdk = libs.versions.android.minSdk.get().toInt()
+ }
+
+
+
+8. Select **Build | Sync Project with Gradle Files** in the main menu, or click the Gradle refresh button in the
+ editor.
+
+#### Move the code and run the Android app
+
+1. Move the `composeApp/src/androidMain` directory into the `androidApp/src/` directory.
+2. Rename the `androidApp/src/androidMain` directory to `main`.
+3. If everything is configured correctly, the imports in the `androidApp/src/main/.../MainActivity.kt` file are working
+ and the code is compiling.
+4. When you're using IntelliJ IDEA or Android Studio, the IDE recognizes the new module and automatically creates a new
+ run configuration, **androidApp**.
+ If that doesn't happen, modify the **composeApp** Android run configuration manually:
+ 1. In the run configuration dropdown, select **Edit Configurations**.
+ 2. Find the **composeApp** configuration in the **Android** category.
+ 3. In the **General | Module** field, change `demo.composeApp` to `demo.androidApp`.
+5. Start the new run configuration to make sure that the app runs as expected.
+6. If everything works correctly, in the `composeApp/build.gradle.kts` file, remove the `kotlin.sourceSets.androidMain.dependencies {}` block.
+
+You have extracted the Android entry point to a separate module.
+Now update the common code module to use the new Android-KMP library plugin.
+
+### Configure the shared module to use the Android-KMP library plugin
+
+To simply extract the Android entry point, you applied the `com.android.library` plugin for the shared `composeApp` module.
+Now migrate to the new multiplatform library plugin:
+
+1. In `gradle/libs.versions.toml`,
+ add the Android-KMP library plugin to your version catalog:
+
+ ```text
+ [plugins]
+ androidMultiplatformLibrary = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" }
+ ```
+
+2. In the `composeApp/build.gradle.kts` file, swap the old Android library plugin for the new one:
+
+
+
+ alias(libs.plugins.androidLibrary)
+
+
+ alias(libs.plugins.androidMultiplatformLibrary)
+
+
+3. In the root `build.gradle.kts` file, add the following line to avoid conflicts in applying the plugin:
+
+ ```kotlin
+ alias(libs.plugins.androidMultiplatformLibrary) apply false
+ ```
+4. In the `composeApp/build.gradle.kts` file, instead of the `kotlin.androidTarget {}` block add a `kotlin.androidLibrary {}` block:
+
+ ```kotlin
+ androidLibrary {
+ namespace = "compose.project.demo.composedemo"
+ compileSdk = libs.versions.android.compileSdk.get().toInt()
+
+ compilerOptions {
+ jvmTarget = JvmTarget.JVM_11
+ }
+
+ androidResources {
+ enable = true
+ }
+ }
+ ```
+5. Remove both the `android {}` and the `dependencies {}` block from the `composeApp/build.gradle.kts` file.
+ * The `android {}` block is replaced with the `kotlin.androidLibrary {}` configuration.
+ * The only root dependency (`debugImplementation(compose.uiTooling)`) conflicts with the new plugin that doesn't
+ support build variants.
+6. Select **Build | Sync Project with Gradle Files** in the main menu, or click the Gradle refresh button in the
+ editor.
+7. Check that the Android app is running as expected.
+
+### Update the Android Gradle plugin version
+
+When all code is working with the new configuration:
+
+1. If you followed the instructions, you have working run configurations for the new app modules.
+ You can delete obsolete run configurations associated with the `composeApp` module.
+2. In the `gradle/libs.versions.toml` file, update the AGP to a 9.* version, for example:
+
+ ```text
+ [versions]
+ agp = "9.0.0-beta03"
+ ```
+3. Up the Gradle version in the `gradle/wrapper/gradle-wrapper.properties` file to at least 9.1.0:
+
+ ```text
+ distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
+ ```
+4. Remove this line from the `androidApp/build.gradle.kts` file, since [Kotlin support is built-in with AGP 9](https://developer.android.com/build/migrate-to-built-in-kotlin)
+ and applying the Kotlin Android plugin is no longer necessary:
+
+ ```kotlin
+ alias(libs.plugins.kotlinAndroid)
+ ```
+5. In the `composeApp/build.gradle.kts` file update the namespace in the `kotlin.androidLibrary {}` block
+ so that it doesn't conflict with the app's namespace, for example:
+
+ ```kotlin
+ kotlin {
+ androidLibrary {
+ namespace = "compose.project.demo.composedemolibrary"
+ // ...
+ ```
+
+6. Select **Build | Sync Project with Gradle Files** in the main menu, or click the Gradle refresh button in the
+ build script editor.
+
+7. Check that your app builds and runs with the new AGP version.
+
+Congratulations! You have upgraded your project to be compatible with AGP 9.
+
+## What's next
+
+Check out the [recommended project structure](multiplatform-project-recommended-structure.md)
+which follows the logic of separating entry points for any app target you might have.
\ No newline at end of file
diff --git a/topics/development/multiplatform-project-recommended-structure.md b/topics/development/multiplatform-project-recommended-structure.md
new file mode 100644
index 00000000..b9e4ae9d
--- /dev/null
+++ b/topics/development/multiplatform-project-recommended-structure.md
@@ -0,0 +1,618 @@
+[//]: # (title: Recommended Kotlin Multiplatform project structure)
+
+
+The overviews of [basic](multiplatform-discover-project.md) and [advanced](multiplatform-advanced-project-structure.md)
+project structure concepts should give you and understanding of source sets and dependency management.
+What about modules which organize the source sets and rely on the dependencies?
+
+> The article talks specifically about KMP projects.
+> For a general understanding of modularization decision-making, see the [Android introduction to modularization](https://developer.android.com/topic/modularization).
+
+## Optimal module structure
+
+The optimal module structure can vary depending on your goals and necessary targets.
+You can analyze the output of the [KMP IDE plugin wizard]() with different configurations and sets of targets
+to see how we organize projects by default.
+
+The general approach can be outlined as follows:
+* The entry points for your apps should be contained in separate modules, each of which depends on necessary shared code modules.
+* The shared code is generally divided into business logic and UI, and the strategy is to avoid unnecessary dependencies:
+ * If all of your apps produced by the KMP project are using shared UI code as well as shared business logic,
+ a single `shared` module for all of your shared code can be sufficient.
+ * If UI for any one of your apps is written using native code (for example, you implemented the iOS UI in pure Swift),
+ it makes sense to separate UI code from business logic to avoid Compose Multiplatform dependencies where they are not needed.
+ So you can have a `sharedLogic` and a `sharedUI` module, and add them as dependencies to entry point modules as needed.
+* If your project includes server code which should share logic with client apps, the recommended way to structure it is:
+ * An `app` folder with entry point modules and client common code modules organized as described above.
+ * A `server` module with the server-specific code.
+ * A `core` module for code shared between the server and clients, such as models and validation.
+
+If your project uses older structure, with app entry points and shared code contained in a single module,
+you can follow guides below to extract the entry points into separate modules.
+
+> It is mandatory to separate your Android app entry points from common code if you intend to use Android Gradle Plugin 9 or newer.
+> See our [AGP 9 migration article](multiplatform-project-agp-9-migration.md) for details.
+>
+{style="note"}
+
+## Creating separate modules for app entry points
+
+The example project that we will use to illustrate for a transition to the recommended structure is an older Compose Multiplatform sample
+which can be found in the [old-project-structure](https://github.com/kotlin-hands-on/get-started-with-cm/tree/old-project-structure)
+branch of the sample repository.
+
+The example consists of a single Gradle module (`composeApp`) that contains all the shared code and KMP entry points,
+and the `iosApp` folder with the iOS project code and configuration.
+
+To extract an entry point to its own module, you need to create the module, move the code, and adjust
+configurations accordingly both for the new module and the common code module.
+
+
+
+### Desktop JVM app
+
+#### Create and configure the desktop app module
+
+To create a desktop app module (`desktopApp`):
+
+1. Create the `desktopApp` directory at the root of the project.
+2. Inside that directory, create an empty `build.gradle.kts` file and the `src` directory.
+3. Add the new module to project settings in the `settings.gradle.kts` file by adding this line:
+
+ ```kotlin
+ include(":desktopApp")
+ ```
+
+#### Configure the build script for the desktop app
+
+To make the desktop app build script work:
+
+1. In the `gradle/libs.versions.toml` file, add the Kotlin JVM Gradle plugin to your version catalog:
+
+ ```text
+ [plugins]
+ kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
+ ```
+
+2. In the `desktopApp/build.gradle.kts` file, specify the plugins necessary for the shared UI module:
+
+ ```kotlin
+ plugins {
+ alias(libs.plugins.kotlinJvm)
+ alias(libs.plugins.composeMultiplatform)
+ alias(libs.plugins.composeCompiler)
+ }
+ ```
+
+3. Make sure all of these plugins are mentioned in the **root** `build.gradle.kts` file:
+
+ ```kotlin
+ plugins {
+ alias(libs.plugins.kotlinJvm) apply false
+ alias(libs.plugins.composeMultiplatform) apply false
+ alias(libs.plugins.composeCompiler) apply false
+ // ...
+ }
+ ```
+
+4. To add the necessary dependencies on other modules, copy existing dependencies from the
+ `commonMain.dependencies {}` and `jvmMain.dependencies {}` blocks
+ of the `composeApp` build script. In this example the end result should look like this:
+
+ ```kotlin
+ kotlin {
+ dependencies {
+ implementation(projects.sharedLogic)
+ implementation(projects.sharedUI)
+ implementation(compose.desktop.currentOs)
+ implementation(libs.kotlinx.coroutinesSwing)
+ }
+ }
+ ```
+
+5. Copy the `compose.desktop {}` block with desktop-specific configuration from the `composeApp/build.gradle.kts`
+ file to the `desktopApp/build.gradle.kts` file:
+
+ ```kotlin
+ compose.desktop {
+ application {
+ mainClass = "compose.project.demo.MainKt"
+
+ nativeDistributions {
+ targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
+ packageName = "compose.project.demo"
+ packageVersion = "1.0.0"
+ }
+ }
+ }
+ ```
+6. Select **Build | Sync Project with Gradle Files** in the main menu, or click the Gradle refresh button in the
+ editor.
+
+#### Move the code and run the desktop app
+
+After the configuration is complete, move the code of the desktop app to the new directory:
+
+1. In the `desktopApp/src` directory, create a new `main` directory.
+2. Move the `composeApp/src/jvmMain/kotlin` directory into the `desktopApp/src/main/` directory:
+ It's important that the package coordinates are aligned with the `compose.desktop {}` configuration.
+3. If everything is configured correctly, the imports in the `desktopApp/src/main/.../main.kt` file are working
+ and the code is compiling.
+4. To run your desktop app, modify the **composeApp [jvm]** run configuration:
+ 1. In the run configuration dropdown, select **Edit Configurations**.
+ 2. Find the **composeApp [jvm]** configuration in the **Gradle** category.
+ 3. In the **Gradle project** field, change `ComposeDemo:composeApp` to `ComposeDemo:desktopApp`.
+5. Start the updated configuration to make sure that the app runs as expected.
+6. If everything works correctly:
+ * Delete the `composeApp/src/jvmMain` directory.
+ * In the `composeApp/build.gradle.kts` file, remove the desktop-related code:
+ * the `compose.desktop {}` block,
+ * the `jvmMain.dependencies {}` block inside the Kotlin `sourceSets {}` block,
+ * the `jvm()` target declaration inside the `kotlin {}` block.
+
+### Web app
+
+#### Create and configure the web app module
+
+To create a desktop app module (`webApp`):
+
+1. Create the `webApp` directory at the root of the project.
+2. Inside that directory, create an empty `build.gradle.kts` file and the `src` directory.
+3. Add the new module to project settings in the `settings.gradle.kts` file by adding this line at the end of the file:
+
+ ```kotlin
+ include(":webApp")
+ ```
+
+#### Configure the build script for the web app
+
+To make the desktop app build script work:
+
+1. In the `webApp/build.gradle.kts` file, specify the plugins necessary for the shared UI module:
+
+ ```kotlin
+ plugins {
+ alias(libs.plugins.kotlinMultiplatform)
+ alias(libs.plugins.composeMultiplatform)
+ alias(libs.plugins.composeCompiler)
+ }
+ ```
+
+2. Make sure all of these plugins are mentioned in the **root** `build.gradle.kts` file:
+
+ ```kotlin
+ plugins {
+ alias(libs.plugins.kotlinMultiplatform) apply false
+ alias(libs.plugins.composeMultiplatform) apply false
+ alias(libs.plugins.composeCompiler) apply false
+ // ...
+ }
+ ```
+
+3. Copy the JavaScript and Wasm target declarations from the `composeApp/build.gradle.kts` file into the `kotlin {}` block
+ in the `webApp/build.gradle.kts` file:
+
+ ```kotlin
+ kotlin {
+ js {
+ browser()
+ binaries.executable()
+ }
+
+ @OptIn(ExperimentalWasmDsl::class)
+ wasmJs {
+ browser()
+ binaries.executable()
+ }
+ }
+ ```
+
+4. Add the necessary dependencies on other modules:
+
+ ```kotlin
+ kotlin {
+ sourceSets {
+ commonMain.dependencies {
+ implementation(projects.sharedLogic)
+ // Provides the necessary entry point API
+ implementation(compose.ui)
+ }
+ }
+ }
+ ```
+
+5. Select **Build | Sync Project with Gradle Files** in the main menu, or click the Gradle refresh button in the
+ editor.
+
+#### Move the code and run the web app
+
+After the configuration is complete, move the code of the web app to the new directory:
+
+1. Move the entire `composeApp/src/webMain` directory into the `webApp/src` directory.
+ If everything is configured correctly, the imports in the `webApp/src/webMain/.../main.kt` file are working
+ and the code is compiling.
+2. In the `webApp/src/webMain/resources/index.html` file update the script name: from `composeApp.js` to `webApp.js`.
+3. To run your web app, modify the **composeApp [wasmJs]** run configuration:
+ 1. In the run configuration dropdown, select **Edit Configurations**.
+ 2. Find the **composeApp [wasmJs]** configuration in the **Gradle** category.
+ 3. In the **Gradle project** field, change `ComposeDemo:composeApp` to `ComposeDemo:webApp`.
+4. Repeat for **composeApp [js]** to be able to run the JavaScript version, too.
+5. Start the run configurations to make sure that the app runs as expected.
+6. If everything works correctly:
+ * Delete the `composeApp/src/webMain` directory.
+ * In the `composeApp/build.gradle.kts` file, remove the web-related code:
+ * the `webMain.dependencies {}` block inside the Kotlin `sourceSets {}` block,
+ * the `js {}` and `wasmJs {}` target declarations inside the `kotlin {}` block.
+
+### Configure the shared module
+
+In the example app, both UI and business logic code are being shared, so it only needs a single shared module
+to hold all common code: you can simply repurpose `composeApp` as the common code module.
+
+For an overview of other project configurations and ways of dealing with them, see our blogpost about
+the new recommended project structure TODO link
+
+The only thing you need to adjust in the Gradle configuration that is not related to connections with entry point modules
+is the new Android Library Gradle plugin.
+The new plugin is built specifically for multiplatform projects and is required to use AGP 9 and newer.
+
+Here are the necessary changes:
+
+1. In `gradle/libs.versions.toml`,
+ add the Android-KMP library plugin to your version catalog:
+
+ ```text
+ [plugins]
+ androidMultiplatformLibrary = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" }
+ ```
+
+2. In the `composeApp/build.gradle.kts` file, add the plugin the plugins necessary for the shared UI module:
+
+ ```kotlin
+ plugins {
+ alias(libs.plugins.kotlinMultiplatform)
+ alias(libs.plugins.androidMultiplatformLibrary)
+ alias(libs.plugins.composeMultiplatform)
+ alias(libs.plugins.composeCompiler)
+ }
+ ```
+3. In the root `build.gradle.kts` file, add the following line to avoid conflicts in applying the plugin:
+
+ ```kotlin
+ alias(libs.plugins.androidMultiplatformLibrary) apply false
+ ```
+4. In the `composeApp/build.gradle.kts` file, instead of the `kotlin.androidTarget {}` block add a `kotlin.androidLibrary {}` block:
+
+ ```kotlin
+ androidLibrary {
+ namespace = "compose.project.demo.composedemo"
+ compileSdk = libs.versions.android.compileSdk.get().toInt()
+
+ compilerOptions {
+ jvmTarget = JvmTarget.JVM_11
+ }
+
+ androidResources {
+ enable = true
+ }
+ }
+ ```
+5. Remove the root `android {}` block from the `composeApp/build.gradle.kts` file.
+6. Remove the `androidMain` dependencies, since all code was moved to the app module:
+ delete the `kotlin.sourceSets.androidMain.dependencies {}` block.
+7. Check that the Android app is running as expected.
+
+### (Optional) Separate shared logic and shared UI {collapsible="true"}
+
+If some of the targets in your project implement native UI, it may be a good idea to separate common code
+into `sharedLogic` and a `sharedUI` modules, so that app modules with native UI don't need to depend on Compose Multiplatform
+to use shared code.
+
+Below is an example of how you can approach this, based on the same sample app.
+
+#### Create a shared logic module
+
+Before actually creating a module, you need to decide on what is business logic, which code is both UI- and platform-independent.
+In this example, the only candidate is the `currentTimeAt()` function, which returns the exact time for a pair of
+location and time zone.
+By contrast, the `Country` data class relies on `DrawableResource` from Compose Multiplatform and cannot be separated from UI code.
+
+> If your project already has a `shared` module, for example, because you don't share all UI code,
+> then you can use this module instead of `sharedLogic`.
+> It may be nice to rename it to clearer distinguish shared logic from UI.
+>
+{style="note"}
+
+Isolate the corresponding code in a `sharedLogic` module:
+
+1. Create the `sharedLogic` directory at the root of the project.
+2. Inside that directory, create an empty `build.gradle.kts` file and the `src` directory.
+3. Add the new module to `settings.gradle.kts` by adding this line at the end of the file:
+
+ ```kotlin
+ include(":sharedLogic")
+ ```
+4. Configure the Gradle build script for the new module.
+
+ 1. In the `gradle/libs.versions.toml` file, add the Android-KMP library plugin to your version catalog:
+
+ ```text
+ [plugins]
+ androidMultiplatformLibrary = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" }
+ ```
+
+ 2. In the `sharedLogic/build.gradle.kts` file, specify the plugins necessary for the shared logic module:
+
+ ```kotlin
+ plugins {
+ alias(libs.plugins.kotlinMultiplatform)
+ alias(libs.plugins.androidMultiplatformLibrary)
+ }
+ ```
+ 3. Make sure these plugins are mentioned in the **root** `build.gradle.kts` file:
+
+ ```kotlin
+ plugins {
+ alias(libs.plugins.androidMultiplatformLibrary) apply false
+ alias(libs.plugins.kotlinMultiplatform) apply false
+ // ...
+ }
+ ```
+ 4. In the `sharedLogic/build.gradle.kts` file, specify the targets that the common module should support in this example:
+
+ ```kotlin
+ kotlin {
+ // There's no need for iOS framework configuration since sharedLogic
+ // is not going to be exported as a framework, only 'sharedUI' is.
+ iosArm64()
+ iosSimulatorArm64()
+
+ jvm()
+
+ js {
+ browser()
+ }
+
+ @OptIn(ExperimentalWasmDsl::class)
+ wasmJs {
+ browser()
+ }
+ }
+ ```
+ 5. For Android, instead of the `androidTarget {}` block, add the `androidLibrary {}` configuration to the
+ `kotlin {}` block:
+
+ ```kotlin
+ kotlin {
+ // ...
+ androidLibrary {
+ namespace = "com.jetbrains.greeting.demo.sharedLogic"
+ compileSdk = libs.versions.android.compileSdk.get().toInt()
+ minSdk = libs.versions.android.minSdk.get().toInt()
+
+ compilerOptions {
+ jvmTarget = JvmTarget.JVM_11
+ }
+ }
+ }
+ ```
+ 6. Add the necessary time dependencies for the common and JavaScript source sets in the same
+ way they are declared for `composeApp`:
+
+ ```kotlin
+ kotlin {
+ sourceSets {
+ commonMain.dependencies {
+ implementation("org.jetbrains.kotlinx:kotlinx-datetime:%dateTimeVersion%")
+ }
+ webMain.dependencies {
+ implementation(npm("@js-joda/timezone", "2.22.0"))
+ }
+ }
+ }
+ ```
+ 7. Select **Build | Sync Project with Gradle Files** in the main menu, or click the Gradle refresh button in the
+ editor.
+
+5. Move the business logic code identified in the beginning:
+ 1. Create a `commonMain/kotlin` directory inside `sharedLogic/src`.
+ 2. Inside `commonMain/kotlin`, create the `CurrentTime.kt` file.
+ 3. Move the `currentTimeAt` function from the original `App.kt` to `CurrentTime.kt`.
+6. Make the function available to the `App()` composable at its new place.
+ To do that, declare the dependency between `composeApp` and `sharedLogic` in the `composeApp/build.gradle.kts` file:
+
+ ```kotlin
+ commonMain.dependencies {
+ implementation(projects.sharedLogic)
+ }
+ ```
+7. Run **Build | Sync Project with Gradle Files** again to apply the changes.
+8. In the `composeApp/commonMain/.../App.kt` file, import the `currentTimeAt()` function to fix the code.
+9. Run the application to make sure that your new module functions properly.
+
+You have successfully isolated the shared logic into a separate module and used it cross-platform.
+Next step: creating a shared UI module.
+
+#### Create a shared UI module
+
+Extract shared code implementing common UI elements in the `sharedUI` module:
+
+1. Create the `sharedUI` directory at the root of the project.
+2. Inside that directory, create an empty `build.gradle.kts` file and the `src` directory.
+3. Add the new module to `settings.gradle.kts` by adding this line at the end of the file:
+
+ ```kotlin
+ include(":sharedUI")
+ ```
+4. Configure the Gradle build script for the new module:
+
+ 1. If you haven't done this for the `sharedLogic` module, in `gradle/libs.versions.toml`,
+ add the Android-KMP library plugin to your version catalog:
+
+ ```text
+ [plugins]
+ androidMultiplatformLibrary = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" }
+ ```
+
+ 2. In the `sharedUI/build.gradle.kts` file, specify the plugins necessary for the shared UI module:
+
+ ```kotlin
+ plugins {
+ alias(libs.plugins.kotlinMultiplatform)
+ alias(libs.plugins.androidMultiplatformLibrary)
+ alias(libs.plugins.composeMultiplatform)
+ alias(libs.plugins.composeCompiler)
+ }
+ ```
+
+ 3. Make sure all of these plugins are mentioned in the **root** `build.gradle.kts` file:
+
+ ```kotlin
+ plugins {
+ alias(libs.plugins.androidMultiplatformLibrary) apply false
+ alias(libs.plugins.composeMultiplatform) apply false
+ alias(libs.plugins.composeCompiler) apply false
+ alias(libs.plugins.kotlinMultiplatform) apply false
+ // ...
+ }
+ ```
+
+ 4. In the `kotlin {}` block, specify the targets that the shared UI module should support in this example:
+
+ ```kotlin
+ kotlin {
+ listOf(
+ iosArm64(),
+ iosSimulatorArm64()
+ ).forEach { iosTarget ->
+ iosTarget.binaries.framework {
+ // This is the name of the iOS framework you're going
+ // to import in your Swift code.
+ baseName = "sharedUI"
+ isStatic = true
+ }
+ }
+
+ jvm()
+
+ js {
+ browser()
+ binaries.executable()
+ }
+
+ @OptIn(ExperimentalWasmDsl::class)
+ wasmJs {
+ browser()
+ binaries.executable()
+ }
+ }
+ ```
+
+ 5. For Android, instead of the `androidTarget {}` block, add the `androidLibrary {}` configuration to the
+ `kotlin {}` block:
+
+ ```kotlin
+ kotlin {
+ // ...
+ androidLibrary {
+ namespace = "com.jetbrains.greeting.demo.sharedUI"
+ compileSdk = libs.versions.android.compileSdk.get().toInt()
+ minSdk = libs.versions.android.minSdk.get().toInt()
+
+ compilerOptions {
+ jvmTarget = JvmTarget.JVM_11
+ }
+
+ // Enables Compose Multiplatform resources to be used in the Android app
+ androidResources {
+ enable = true
+ }
+ }
+ }
+ ```
+
+ 6. Add the necessary dependencies for the shared UI in the same way they are declared for `composeApp`:
+
+ ```kotlin
+ kotlin {
+ sourceSets {
+ commonMain.dependencies {
+ implementation(projects.sharedLogic)
+ implementation(compose.runtime)
+ implementation(compose.foundation)
+ implementation(compose.material3)
+ implementation(compose.ui)
+ implementation(compose.components.resources)
+ implementation(compose.components.uiToolingPreview)
+ implementation(libs.androidx.lifecycle.viewmodelCompose)
+ implementation(libs.androidx.lifecycle.runtimeCompose)
+ implementation("org.jetbrains.kotlinx:kotlinx-datetime:%dateTimeVersion%")
+ }
+ }
+ }
+ ```
+ 7. Select **Build | Sync Project with Gradle Files** in the main menu, or click the Gradle refresh button in the
+ editor.
+5. Create a new `commonMain/kotlin` directory inside `sharedUI/src`.
+6. Move resource files to the `sharedUI` module: the entire directory of `composeApp/commonMain/composeResources` should
+ be relocated to `sharedUI/commonMain/composeResources`.
+7. In the `sharedUI/src/commonMain/kotlin directory`, create a new `App.kt` file.
+8. Copy the entire contents of the original `composeApp/src/commonMain/.../App.kt` to the new `App.kt` file.
+9. Temporarily comment out all code in the old `App.kt` file.
+ This will allow you to test whether the shared UI module is working before removing the old code completely.
+10. The new `App.kt` file should work as expected, except for resource imports, which are now located in a
+ different package.
+ Reimport the `Res` object and all drawable resources with the correct path, for example:
+
+
+
+ import demo.composeapp.generated.resources.mx
+
+
+ import demo.sharedui.generated.resources.mx
+
+
+11. To make the new `App()` composable available to the entry points in your app modules that rely on it,
+ add the dependency to the corresponding `build.gradle.kts` files:
+
+ ```kotlin
+ kotlin {
+ sourceSets {
+ commonMain.dependencies {
+ implementation(projects.sharedUI)
+ // ...
+ }
+ }
+ }
+ ```
+12. Run your apps to check that the new module works to supply app entry points with shared UI code.
+13. Remove the `composeApp/src/commonMain/.../App.kt` file.
+
+You have successfully moved the cross-platform UI code into a dedicated module.
+
+### Update the iOS integration
+
+Since the iOS app entry point is not built as a separate Gradle module, you can embed the source code into any module.
+In this example, you can leave it inside `shared`:
+
+1. Move the `composeApp/src/iosMain` directory into the `shared/src` directory.
+2. Configure the Xcode project to consume the framework produced by the `shared` module:
+ 1. Select the **File | Open Project in Xcode** menu item.
+ 2. Click the **iosApp** project in the **Project navigator** tool window, then select the **Build Phases** tab.
+ 3. Find the **Compile Kotlin Framework** phase.
+ 4. Find the line starting with `./gradlew` and replace `composeApp` with `sharedUi`:
+
+ ```text
+ ./gradlew :shared:embedAndSignAppleFrameworkForXcode
+ ```
+
+ 5. Note that the import in the `ContentView.swift` file needs to stay the same, because it matches the `baseName` parameter from
+ Gradle configuration of the iOS target,
+ not actual name of the module.
+ If you change the framework name in the `shared/build.gradle.kts` file, you need to change the import directive accordingly.
+
+3. Run the app from Xcode or using the **iosApp** run configuration in IntelliJ IDEA
+
+## What's next
+
+TODO up for suggestions here — the first thought is to link platform-specific guidance.
\ No newline at end of file