AgentSkillsCN

Kotlin Multiplatform Development

提供 Kotlin 多平台(KMP)expect/actual 声明、源码集、Gradle 任务以及 Kotlin/Native 构建的相关模式。适用于开发 KMP 项目、在 Android、iOS、JVM、JS 和 Wasm 之间共享代码,或配置多平台依赖项时使用。

SKILL.md
--- frontmatter
name: Kotlin Multiplatform Development
description: Provides patterns for Kotlin Multiplatform (KMP) expect/actual declarations, source sets, Gradle tasks, and Kotlin/Native builds. Use when developing KMP projects, sharing code across Android, iOS, JVM, JS, and Wasm, or configuring multiplatform dependencies.

Gradle Tasks

  • NEVER run build gradle task, ALWAYS use target-specific smaller gradle tasks.
  • Run ./gradlew tasks to list available tasks
  • For iOS, prefer a single target (e.g., iosSimulatorArm64) unless all are needed

File Naming

Use platform suffixes to distinguish files with the same name across platforms:

  • FileManager.kt → common expect
  • FileManager.android.kt → Android actual
  • FileManager.ios.kt → iOS actual

Expect/Actual Patterns

Factory Function (for class instantiation in common code)

kotlin
// Common
expect class Platform()
expect fun createPlatform(): Platform

// Android
actual class Platform actual constructor()
actual fun createPlatform(): Platform = Platform()

// iOS
actual class Platform actual constructor()
actual fun createPlatform(): Platform = Platform()

Function

kotlin
// Common
expect fun getPlatformName(): String

// Android
actual fun getPlatformName(): String = "Android ${Build.VERSION.SDK_INT}"

// iOS
actual fun getPlatformName(): String = UIDevice.currentDevice.systemName + " " + UIDevice.currentDevice.systemVersion

Object (singleton)

kotlin
// Common
expect object Logger {
    fun log(message: String)
}

// Android
actual object Logger {
    actual fun log(message: String) = Log.d("App", message)
}

// iOS
actual object Logger {
    actual fun log(message: String) = NSLog(message)
}

Property

kotlin
// Common
expect val isDebug: Boolean

// Android
actual val isDebug: Boolean = BuildConfig.DEBUG

// iOS
actual val isDebug: Boolean = Platform.isDebugBinary

Typealias (wrap platform types)

kotlin
// Common
expect class NativeDate

// Android
actual typealias NativeDate = java.util.Date

// iOS
actual typealias NativeDate = platform.Foundation.NSDate

Interface + Factory (preferred for complex APIs)

kotlin
// Common
interface DeviceInfo {
    val platformName: String
    val osVersion: String
}
expect fun createDeviceInfo(): DeviceInfo

// Android
class AndroidDeviceInfo : DeviceInfo {
    override val platformName = "Android"
    override val osVersion = Build.VERSION.RELEASE
}
actual fun createDeviceInfo(): DeviceInfo = AndroidDeviceInfo()

// iOS
class IosDeviceInfo : DeviceInfo {
    override val platformName = "iOS"
    override val osVersion = UIDevice.currentDevice.systemVersion
}
actual fun createDeviceInfo(): DeviceInfo = IosDeviceInfo()

Choosing a Pattern

PatternUse When
Interface + FactoryDefault choice. Flexible, testable, multiple implementations per platform
Expect/actual funSimple factory functions or standalone platform-specific logic
Expect/actual objectSingletons with a fixed API
Expect/actual typealiasWrapping existing platform types
Expect/actual classOnly when inheriting from platform-specific base classes

Common Mistakes

  • Mismatched packages between expect/actual declarations
  • Using expect/actual class when an interface would suffice

Compilation

PatternExamples
compileKotlin<Target>compileKotlinJvm, compileKotlinJs, compileKotlinIosArm64, compileKotlinIosSimulatorArm64, compileKotlinWasmJs
compile<BuildType>KotlinAndroidcompileDebugKotlinAndroid, compileReleaseKotlinAndroid
compile<SourceSet>KotlinMetadatacompileCommonMainKotlinMetadata, compileAppleMainKotlinMetadata, compileIosMainKotlinMetadata

Linking (Kotlin/Native)

PatternExamples
link<BuildType>Framework<Target>linkDebugFrameworkIosArm64, linkReleaseFrameworkIosSimulatorArm64, linkDebugFrameworkIosX64
linkDebugTest<Target>linkDebugTestIosArm64, linkDebugTestIosSimulatorArm64

Testing

PatternExamples
allTestsallTests (runs all targets)
<target>TestiosSimulatorArm64Test, jvmTest, jsTest, jsBrowserTest, wasmJsTest
test<BuildType>UnitTesttestDebugUnitTest, testReleaseUnitTest
connected<BuildType>AndroidTestconnectedDebugAndroidTest

Source Set Hierarchy

Exact source sets depend on your configured targets. The plugin automatically creates intermediate source sets based on declared targets.

code
commonMain
├── androidMain
├── jvmMain
├── webMain
│   ├── jsMain
│   └── wasmMain
│       ├── wasmJsMain
│       └── wasmWasiMain
└── nativeMain
    ├── appleMain
    │   ├── iosMain
    │   │   ├── iosArm64Main
    │   │   └── iosSimulatorArm64Main
    │   ├── macosMain
    │   │   ├── macosX64Main
    │   │   └── macosArm64Main
    │   ├── tvosMain
    │   └── watchosMain
    ├── linuxX64Main
    └── mingwX64Main

Intermediate source sets for shared logic:

  • webMain → JS and Wasm targets (Kotlin 2.2.20+)
  • nativeMain → all Kotlin/Native targets
  • appleMain → iOS, macOS, watchOS, tvOS
  • iosMain → all iOS targets (iosArm64, iosSimulatorArm64)

Custom Source Sets

Create custom intermediate source sets with dependsOn:

kotlin
kotlin {
    applyDefaultHierarchyTemplate()

    sourceSets {
        val jvmAndroidMain by creating {
            dependsOn(commonMain.get())
        }
        androidMain.get().dependsOn(jvmAndroidMain)
        jvmMain.get().dependsOn(jvmAndroidMain)
    }
}

Dependencies

Add dependencies per source set. Dependencies in parent source sets propagate to children.

kotlin
kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation("group:artifact:version")
            api("group:exposed-artifact:version")
        }

        // Platform-specific (artifact suffix varies by library)
        androidMain.dependencies {
            implementation("group:artifact-<android>:version")
        }
        iosMain.dependencies {
            implementation("group:artifact-<ios>:version")
        }

        // Custom source sets
        // - "by getting": reference existing source sets
        // - "by creating": create new custom source sets
        val androidMain by getting
        val jvmMain by getting

        val jvmAndroidMain by creating {
            dependsOn(commonMain.get())
        }
        jvmAndroidMain.dependencies {
            implementation("group:artifact-<jvm-android>:version")
        }
        androidMain.dependsOn(jvmAndroidMain)
        jvmMain.dependsOn(jvmAndroidMain)
    }
}