kotlin-multiplatform
Provides expert guidance for Kotlin Multiplatform (KMP) architecture, determining shared or platform-specific code.
npx skills add vitorpamplona/amethyst --skill kotlin-multiplatformBefore / After Comparison
1 组In Kotlin Multiplatform projects, it's difficult to accurately determine which code should be shared and which should be platform-specific, leading to architectural confusion and increased development and maintenance costs.
Receive expert guidance on KMP architecture to clearly distinguish between shared and platform-specific code, optimize project structure, and significantly improve development efficiency and code reusability.
description SKILL.md
kotlin-multiplatform
Kotlin Multiplatform: Platform Abstraction Decisions Expert guidance for KMP architecture in Amethyst - deciding what to share vs keep platform-specific. When to Use This Skill Making platform abstraction decisions: "Should I create expect/actual or keep Android-only?" "Can I share this ViewModel logic?" "Where does this crypto/JSON/network implementation belong?" "This uses Android Context - can it be abstracted?" "Is this code in the wrong module?" Preparing for iOS/web/wasm targets Detecting incorrect placements Abstraction Decision Tree Central question: "Should this code be reused across platforms?" Follow this decision path (< 1 minute): Q: Is it used by 2+ platforms? ├─ NO → Keep platform-specific │ Example: Android-only permission handling │ └─ YES → Continue ↓ Q: Is it pure Kotlin (no platform APIs)? ├─ YES → commonMain │ Example: Nostr event parsing, business rules │ └─ NO → Continue ↓ Q: Does it vary by platform or by JVM vs non-JVM? ├─ By platform (Android ≠ iOS ≠ Desktop) │ → expect/actual │ Example: Secp256k1Instance (uses different security APIs) │ ├─ By JVM (Android = Desktop ≠ iOS/web) │ → jvmAndroid │ Example: Jackson JSON parsing (JVM library) │ └─ Complex/UI-related → Keep platform-specific Example: Navigation (Activity vs Window too different) Final check: Q: Maintenance cost of abstraction < duplication cost? ├─ YES → Proceed with abstraction └─ NO → Duplicate (simpler) Real Examples from Codebase Crypto → expect/actual: // commonMain - expect declaration expect object Secp256k1Instance { fun signSchnorr(data: ByteArray, privKey: ByteArray): ByteArray } // androidMain - uses Android Keystore // jvmMain - uses Desktop JVM crypto // iosMain - uses iOS Security framework Why: Each platform has different security APIs. JSON parsing → jvmAndroid: // quartz/build.gradle.kts val jvmAndroid = create("jvmAndroid") { api(libs.jackson.module.kotlin) } Why: Jackson is JVM-only, works on Android + Desktop, not iOS/web. Navigation → platform-specific: Android: MainActivity (Activity + Compose Navigation) Desktop: Window + sidebar + MenuBar Why: UI paradigms fundamentally different. Mental Model: Source Sets as Dependency Graph Think of source sets as a dependency graph, not folders. ┌─────────────────────────────────────────────┐ │ commonMain = Contract (pure Kotlin) │ │ - Business logic, protocol, data models │ │ - No platform APIs │ └────────────┬────────────────────────────────┘ │ ├──────────────────────┬──────────────────── │ │ ▼ ▼ ┌───────────────────┐ ┌──────────────────┐ │ jvmAndroid │ │ iosMain │ │ JVM libs shared │ │ iOS common │ │ - Jackson │ │ │ │ - OkHttp │ └────┬─────────────┘ └───┬───────────┬───┘ │ │ │ ├─→ iosX64Main ▼ ▼ ├─→ iosArm64Main ┌─────────┐ ┌──────────┐ └─→ iosSimulatorArm64Main │android │ │jvmMain │ │Main │ │(Desktop) │ └─────────┘ └──────────┘ Future: jsMain, wasmMain Key insight: jvmAndroid is NOT a platform - it's a shared JVM layer. The jvmAndroid Pattern Unique to Amethyst. Shares JVM libraries between Android + Desktop. When to Use jvmAndroid Use jvmAndroid when: ✅ JVM-specific libraries (Jackson, OkHttp, url-detector) ✅ Android implementation = Desktop implementation (same JVM) ✅ Library doesn't work on iOS/web Do NOT use jvmAndroid for: ❌ Pure Kotlin code (use commonMain) ❌ Platform-specific APIs (use androidMain/jvmMain) ❌ Code that should work on all platforms Example from quartz/build.gradle.kts // Must be defined BEFORE androidMain and jvmMain val jvmAndroid = create("jvmAndroid") { dependsOn(commonMain.get()) dependencies { api(libs.jackson.module.kotlin) // JSON parsing - JVM only api(libs.url.detector) // URL extraction - JVM only implementation(libs.okhttp) // HTTP client - JVM only } } // Both depend on jvmAndroid jvmMain { dependsOn(jvmAndroid) } androidMain { dependsOn(jvmAndroid) } Why Jackson in jvmAndroid, not commonMain? Jackson is JVM-specific library Works on Android (runs on JVM) Works on Desktop (runs on JVM) Does NOT work on iOS (not JVM) or web (not JVM) Web/wasm consideration: For future web support, consider migrating from Jackson → kotlinx.serialization (see Target-Specific Guidance). What to Abstract vs Keep Platform-Specific Quick decision guidelines based on codebase patterns: Always Abstract Crypto (Secp256k1, encryption, signing) Core protocol logic (Nostr events, NIPs) Why: Needed everywhere, platform security APIs vary Often Abstract I/O operations (file reading, caching) Logging (platform logging systems differ) Serialization (if using kotlinx.serialization) Why: Commonly reused, platform implementations available Sometimes Abstract Business logic: YES - state machines, data processing ViewModels: YES - state + business logic shareable (StateFlow/SharedFlow) Screen layouts: NO - platform-native (Window vs Activity) Why: ViewModels contain platform-agnostic state; Screens render differently per platform Rarely Abstract Complex UI components (composables with heavy platform dependencies) Why: Platform paradigms can differ significantly Never Abstract Navigation (Activity vs Window fundamentally different) Permissions (Android vs iOS APIs incompatible) Platform UX patterns Why: Too platform-specific, abstraction creates leaky APIs Evidence from shared-ui-analysis.md Component Shared? Rationale PubKeyFormatter, ZapFormatter ✅ YES Pure Kotlin, no platform APIs TimeAgoFormatter ⚠️ ABSTRACTED Needs StringProvider for localized strings ViewModels (state + logic) ✅ YES StateFlow/SharedFlow platform-agnostic, Compose Multiplatform lifecycle compatible Screen layouts (Scaffold, nav) ❌ NO Window vs Activity, sidebar vs bottom nav fundamentally different Image loading (Coil) ⚠️ ABSTRACTED Coil 3.x supports KMP, needs expect/actual wrapper expect/actual Mechanics When to use: Code needed by 2+ platforms, varies by platform. Pattern Categories from Codebase Objects (singletons): // 24 expect declarations found, common pattern: expect object Secp256k1Instance { ... } expect object Log { ... } expect object LibSodiumInstance { ... } Classes (instantiable): expect class AESCBC { ... } expect class DigestInstance { ... } Functions (utilities): expect fun platform(): String expect fun currentTimeSeconds(): Long See references/expect-actual-catalog.md for complete catalog with rationale. Target-Specific Guidance Android, JVM (Desktop), iOS - Current Primary Targets Status: Mature patterns, stable APIs Android (androidMain): Uses Android framework (Activity, Context, etc.) secp256k1-kmp-jni-android for crypto AndroidX libraries Desktop JVM (jvmMain): Uses Compose Desktop (Window, MenuBar, etc.) secp256k1-kmp-jni-jvm for crypto Pure JVM libraries iOS (iosMain): Active development, framework configured Architecture targets: iosX64Main, iosArm64Main, iosSimulatorArm64Main Platform APIs via platform.posix, Security framework Web, wasm - Future Targets Status: Not yet implemented, consider for future-proofing Constraints to know: ❌ No platform.posix (file I/O different) ❌ No JVM libraries (Jackson, OkHttp won't work) ❌ Different async model (JS event loop vs threads) Future-proofing tips: Prefer pure Kotlin in commonMain Use kotlinx.* libraries: kotlinx.serialization instead of Jackson ktor instead of OkHttp (ktor supports web) kotlinx.datetime instead of custom date handling Avoid platform.posix for file operations Test abstractions work without JVM assumptions Example migration path: // Current: jvmAndroid (JVM-only) api(libs.jackson.module.kotlin) // Future: commonMain (all platforms) api(libs.kotlinx.serialization.json) Integration: When to Invoke Other Skills Invoke gradle-expert Trigger gradle-expert skill when encountering: Dependency conflicts (e.g., secp256k1-android vs secp256k1-jvm version mismatch) Build errors related to source sets Version catalog issues (libs.versions.toml) "Duplicate class" errors Performance/build time issues Example trigger: Error: Duplicate class found: fr.acinq.secp256k1.Secp256k1 → Invoke gradle-expert for dependency conflict resolution. Flags to Raise Platform code in commonMain: // ❌ INCORRECT - Android API in commonMain expect fun getContext(): Context // Context is Android-only! → Flag: "Android API in commonMain won't compile on other platforms" Duplicated business logic: // ❌ INCORRECT - Same logic in both // androidMain/.../CryptoUtils.kt fun validateSignature(...) { ... } // jvmMain/.../CryptoUtils.kt fun validateSignature(...) { ... } // Duplicated! → Flag: "Business logic duplicated, should be in commonMain or expect/actual" Reinventing wheel - suggest KMP alternatives: Custom date/time → kotlinx.datetime OkHttp → ktor (supports web) Jackson → kotlinx.serialization Custom UUID → kotlinx.uuid (when stable) Common Pitfalls 1. Over-Abstraction Problem: Creating expect/actual for UI components // ❌ BAD expect fun NavigationComponent(...) Why: Navigation paradigms too different (Activity vs Window) Fix: Keep platform-specific, accept duplication 2. Under-Sharing Problem: Duplicating business logic across platforms // ❌ BAD - duplicated in androidMain and jvmMain fun parseNostrEvent(json: String): Event { ... } Why: Bug fixes need to be applied twice, tests duplicated Fix: Move to commonMain (pure Kotlin) or create expect/actual 3. Leaky Abstractions Problem: Platform code in commonMain // commonMain - ❌ BAD import android.content.Context // Won't compile on iOS! Fix: Use expect/actual or dependency injection 4. Premature Abstraction Problem: Creating expect/actual before second platform needs it // ❌ BAD - only used on Android currently expect fun showNotification(...) Why: Wrong abstraction boundaries, wasted effort Fix: Wait until iOS actually needs it, then abstract 5. Wrong Source Set Problem: JVM libraries in commonMain // commonMain - ❌ BAD import com.fasterxml.jackson.databind.ObjectMapper Why: Jackson won't compile on iOS/web Fix: Move to jvmAndroid or migrate to kotlinx.serialization Quick Reference Code Type Recommended Location Reason Pure Kotlin business logic commonMain Works everywhere Nostr protocol, NIPs commonMain Core logic, no platform APIs JVM libs (Jackson, OkHttp) jvmAndroid Android + Desktop only Crypto (varies by platform) expect in commonMain, actual in platforms Different security APIs per platform I/O, logging expect in commonMain, actual in platforms Platform implementations differ State (business logic) commonMain or commons/jvmAndroid Reusable StateFlow patterns ViewModels commons/commonMain/viewmodels/ StateFlow/SharedFlow + logic shareable, Compose MP lifecycle compatible UI formatters (pure) commons/commonMain Reusable, no dependencies UI components (simple) commons/commonMain Cards, buttons, dialogs Screen layouts Platform-specific Window vs Activity, sidebar vs bottom nav Navigation Platform-specific only Activity vs Window too different Permissions Platform-specific only APIs incompatible Platform UX (menus, etc.) Platform-specific only Native feel required See Also references/abstraction-examples.md - Good/bad abstraction examples with rationale references/source-set-hierarchy.md - Visual hierarchy with Amethyst examples references/expect-actual-catalog.md - All 24 expect/actual pairs with "why abstracted" references/target-compatibility.md - Platform constraints and future-proofing Scripts scripts/validate-kmp-structure.sh - Detect incorrect placements, validate source sets scripts/suggest-kmp-dependency.sh - Suggest KMP library alternatives (ktor, kotlinx.serialization, etc.) Weekly Installs187Repositoryvitorpamplona/amethystGitHub Stars1.5KFirst SeenJan 21, 2026Security AuditsGen Agent Trust HubPassSocketPassSnykWarnInstalled onopencode160gemini-cli155codex151github-copilot144claude-code130amp127
forumUser Reviews (0)
Write a Review
No reviews yet
Statistics
User Rating
Rate this Skill