一键导入
kmp
Kotlin Multiplatform fundamentals - use for project setup, expect/actual patterns, source sets, and platform-specific code
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Kotlin Multiplatform fundamentals - use for project setup, expect/actual patterns, source sets, and platform-specific code
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
Automated UI verification loop: Marionette screenshot -> Figma reference -> pixel-diff -> difference list -> auto-fix iteration. Solves the false-positive verification problem flagged in insights reports (UI claimed fixed without actually being compared against the reference). Triggers (English): verify ui, ui compare, pixel diff, figma compare, automated ui verification, golden test fail. 自动 UI 验证:Marionette 截图 → Figma 参考图 → pixel-diff → 差异列表 → 自动修复循环。 触发关键字(中文):verify ui、ui 对比、pixel diff、figma 对比、自动验证
Structured visual QA verdict for screenshot-to-reference comparisons
Operational rules for driving Codex CLI from scripts: success-signal contract, diff-feeding semantics, worktree -C flag, and stdin vs argv. Triggers when invoking `codex exec` programmatically (not interactively) — script wrappers, ralph loops, cron pipelines, multi-CLI fan-out. Surfaces silent failure modes that exit 0 but produce no useful output. Triggers (English): codex exec, codex cli, codex review, codex rescue, codex fallthrough, agent script invocation, programmatic codex.
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
Socratic deep interview with mathematical ambiguity gating before autonomous execution
Use when creating or developing, before writing code or implementation plans - refines rough ideas into fully-formed designs through collaborative questioning, alternative exploration, and incremental validation. Don't use during clear 'mechanical' processes
基于 SOC 职业分类
| name | kmp |
| description | Kotlin Multiplatform fundamentals - use for project setup, expect/actual patterns, source sets, and platform-specific code |
Kotlin Multiplatform enables sharing code across Android, iOS, Desktop, Web (WASM), and Server.
your-project-admin/
├── build.gradle.kts # Root build config
├── settings.gradle.kts # Module includes
├── gradle/libs.versions.toml # Version catalog
│
├── core/
│ ├── common/ # Utilities, Result types, extensions
│ │ └── src/commonMain/kotlin/
│ ├── data/ # Data abstractions, DataStore
│ │ ├── src/commonMain/kotlin/
│ │ ├── src/androidMain/kotlin/
│ │ └── src/iosMain/kotlin/
│ ├── database/ # Room (Android/iOS/JVM only)
│ │ └── src/commonMain/kotlin/
│ ├── network/ # Ktor client
│ │ └── src/commonMain/kotlin/
│ └── ui/ # Design system, theme
│ └── src/commonMain/kotlin/
│
├── feature/
│ ├── auth/
│ │ ├── api/ # Public interfaces, models
│ │ │ └── src/commonMain/kotlin/
│ │ └── impl/ # Implementation, UI
│ │ └── src/commonMain/kotlin/
│ └── home/
│ ├── api/
│ └── impl/
│
├── composeApp/ # Platform entry points
│ ├── src/commonMain/ # App composition, DI graph
│ ├── src/androidMain/ # MainActivity
│ ├── src/iosMain/ # iOS entry
│ ├── src/jvmMain/ # Desktop main()
│ └── src/wasmJsMain/ # Web entry
│
└── iosApp/ # Xcode project
commonMain
├── androidMain
├── iosMain
│ ├── iosX64Main
│ ├── iosArm64Main
│ └── iosSimulatorArm64Main
├── jvmMain
├── wasmJsMain
└── jsMain (fallback)
plugins {
alias(libs.plugins.kotlinMultiplatform) apply false
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.androidLibrary) apply false
alias(libs.plugins.composeMultiplatform) apply false
alias(libs.plugins.composeCompiler) apply false
alias(libs.plugins.kotlinSerialization) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.room) apply false
alias(libs.plugins.metro) apply false
}
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
rootProject.name = "your-project-admin"
// Core modules
include(":core:common")
include(":core:data")
include(":core:database")
include(":core:network")
include(":core:ui")
// Feature modules
include(":feature:auth:api")
include(":feature:auth:impl")
include(":feature:home:api")
include(":feature:home:impl")
// App entry points
include(":composeApp")
[versions]
kotlin = "2.1.0"
agp = "8.7.3"
compose-multiplatform = "1.7.3"
ktor = "3.1.1"
room = "2.8.4"
datastore = "1.2.0"
decompose = "3.5.0"
metro = "0.1.1"
essenty = "2.5.0"
coroutines = "1.10.1"
serialization = "1.7.3"
ksp = "2.1.0-1.0.29"
[libraries]
# Kotlin
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
# Ktor
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
# Room (Android, iOS, JVM only)
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version = "2.6.2" }
# DataStore
datastore-preferences-core = { module = "androidx.datastore:datastore-preferences-core", version.ref = "datastore" }
# Decompose
decompose = { module = "com.arkivanov.decompose:decompose", version.ref = "decompose" }
decompose-compose = { module = "com.arkivanov.decompose:extensions-compose", version.ref = "decompose" }
essenty-lifecycle = { module = "com.arkivanov.essenty:lifecycle", version.ref = "essenty" }
[plugins]
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
androidApplication = { id = "com.android.application", version.ref = "agp" }
androidLibrary = { id = "com.android.library", version.ref = "agp" }
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
room = { id = "androidx.room", version.ref = "room" }
metro = { id = "dev.zacsweers.metro", version.ref = "metro" }
// core/common/build.gradle.kts
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidLibrary)
}
kotlin {
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = "17"
}
}
}
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "CoreCommon"
isStatic = true
}
}
jvm("desktop")
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
browser()
}
sourceSets {
commonMain.dependencies {
implementation(libs.kotlinx.coroutines.core)
}
androidMain.dependencies {
// Android-specific
}
iosMain.dependencies {
// iOS-specific
}
val desktopMain by getting {
dependencies {
// Desktop-specific
}
}
}
}
android {
namespace = "com.your-project.admin.core.common"
compileSdk = 35
defaultConfig {
minSdk = 24
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
// commonMain/kotlin/Platform.kt
expect class PlatformContext
expect fun getPlatformName(): String
expect fun createDataStorePath(context: PlatformContext): String
// androidMain/kotlin/Platform.android.kt
actual typealias PlatformContext = android.content.Context
actual fun getPlatformName(): String = "Android ${android.os.Build.VERSION.SDK_INT}"
actual fun createDataStorePath(context: PlatformContext): String {
return context.filesDir.resolve("datastore").absolutePath
}
// iosMain/kotlin/Platform.ios.kt
import platform.Foundation.NSDocumentDirectory
import platform.Foundation.NSFileManager
import platform.Foundation.NSUserDomainMask
actual class PlatformContext
actual fun getPlatformName(): String = "iOS"
actual fun createDataStorePath(context: PlatformContext): String {
val documentDir = NSFileManager.defaultManager.URLForDirectory(
NSDocumentDirectory,
NSUserDomainMask,
null,
false,
null
)
return "${documentDir?.path}/datastore"
}
// desktopMain/kotlin/Platform.jvm.kt
import java.io.File
actual class PlatformContext
actual fun getPlatformName(): String =
"${System.getProperty("os.name")} ${System.getProperty("os.version")}"
actual fun createDataStorePath(context: PlatformContext): String {
val home = System.getProperty("user.home")
return File(home, ".your-project-admin/datastore").absolutePath
}
// wasmJsMain/kotlin/Platform.wasmJs.kt
actual class PlatformContext
actual fun getPlatformName(): String = "Web (WASM)"
actual fun createDataStorePath(context: PlatformContext): String {
return "your-project-admin-datastore" // Uses localStorage
}
composeApp
├── feature:auth:impl
│ ├── feature:auth:api
│ ├── core:ui
│ └── core:network
├── feature:home:impl
│ ├── feature:home:api
│ ├── core:ui
│ └── core:database
├── core:ui
│ └── core:common
├── core:network
│ └── core:common
├── core:database
│ └── core:common
└── core:data
└── core:common
// feature/auth/api/build.gradle.kts
plugins {
alias(libs.plugins.kotlinMultiplatform)
}
kotlin {
// Targets...
sourceSets {
commonMain.dependencies {
// Only models and interfaces - no implementations
api(projects.core.common)
}
}
}
// feature/auth/impl/build.gradle.kts
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
}
kotlin {
sourceSets {
commonMain.dependencies {
implementation(projects.feature.auth.api)
implementation(projects.core.ui)
implementation(projects.core.network)
implementation(libs.decompose)
implementation(libs.decompose.compose)
}
}
}
// core/common/src/commonMain/kotlin/Result.kt
sealed class AppResult<out T> {
data class Success<T>(val data: T) : AppResult<T>()
data class Error(val message: String, val cause: Throwable? = null) : AppResult<Nothing>()
data object Loading : AppResult<Nothing>()
}
inline fun <T, R> AppResult<T>.map(transform: (T) -> R): AppResult<R> = when (this) {
is AppResult.Success -> AppResult.Success(transform(data))
is AppResult.Error -> this
is AppResult.Loading -> this
}
inline fun <T> AppResult<T>.onSuccess(action: (T) -> Unit): AppResult<T> {
if (this is AppResult.Success) action(data)
return this
}
inline fun <T> AppResult<T>.onError(action: (String, Throwable?) -> Unit): AppResult<T> {
if (this is AppResult.Error) action(message, cause)
return this
}
// feature/auth/api/src/commonMain/kotlin/AuthRepository.kt
interface AuthRepository {
suspend fun login(email: String, password: String): AppResult<User>
suspend fun logout(): AppResult<Unit>
fun observeAuthState(): Flow<AuthState>
}
data class User(
val id: String,
val email: String,
val name: String
)
sealed class AuthState {
data object Unauthenticated : AuthState()
data class Authenticated(val user: User) : AuthState()
}
// feature/auth/impl/src/commonMain/kotlin/AuthRepositoryImpl.kt
class AuthRepositoryImpl(
private val apiService: AuthApiService,
private val tokenStorage: TokenStorage
) : AuthRepository {
private val _authState = MutableStateFlow<AuthState>(AuthState.Unauthenticated)
override suspend fun login(email: String, password: String): AppResult<User> {
return try {
val response = apiService.login(LoginRequest(email, password))
tokenStorage.saveToken(response.token)
_authState.value = AuthState.Authenticated(response.user)
AppResult.Success(response.user)
} catch (e: Exception) {
AppResult.Error("Login failed: ${e.message}", e)
}
}
override suspend fun logout(): AppResult<Unit> {
tokenStorage.clearToken()
_authState.value = AuthState.Unauthenticated
return AppResult.Success(Unit)
}
override fun observeAuthState(): Flow<AuthState> = _authState.asStateFlow()
}
// When expect/actual is overkill, use runtime checks
enum class Platform {
Android, iOS, Desktop, Web
}
expect val currentPlatform: Platform
// androidMain
actual val currentPlatform: Platform = Platform.Android
// iosMain
actual val currentPlatform: Platform = Platform.iOS
// desktopMain
actual val currentPlatform: Platform = Platform.Desktop
// wasmJsMain
actual val currentPlatform: Platform = Platform.Web
// Usage
@Composable
fun AdaptiveComponent() {
when (currentPlatform) {
Platform.Android -> AndroidSpecificUI()
Platform.iOS -> IOSSpecificUI()
Platform.Desktop -> DesktopSpecificUI()
Platform.Web -> WebSpecificUI()
}
}
// core/common/src/commonTest/kotlin/ResultTest.kt
class ResultTest {
@Test
fun `map transforms success value`() {
val result: AppResult<Int> = AppResult.Success(5)
val mapped = result.map { it * 2 }
assertTrue(mapped is AppResult.Success)
assertEquals(10, (mapped as AppResult.Success).data)
}
@Test
fun `map preserves error`() {
val result: AppResult<Int> = AppResult.Error("test error")
val mapped = result.map { it * 2 }
assertTrue(mapped is AppResult.Error)
}
}
// androidTest - uses Robolectric or instrumented tests
// iosTest - runs on simulator
// jvmTest - standard JUnit
commonMaincommonTestImportant limitations when targeting WebAssembly:
localStorage or IndexedDB via expect/actual pattern// commonMain - interface only
expect class AppStorage {
fun getString(key: String): String?
fun putString(key: String, value: String)
}
// wasmJsMain - browser storage
actual class AppStorage {
actual fun getString(key: String): String? =
window.localStorage.getItem(key)
actual fun putString(key: String, value: String) {
window.localStorage.setItem(key, value)
}
}
Dispatchers.IO (use Dispatchers.Default)See What's inside for related categories — kmp is a native/cross-platform skill without natural composition partners in other skill domains.