Skip to content
Development
Skill

/kotlin-multiplatform

Platform abstraction decision-making for Amethyst KMP project. Guides when to abstract vs keep platform-specific, source set placement (commonMain, jvmAndroid, platform-specific), expect/actual patterns. Covers primary targets (Android, JVM/Desktop, iOS — all mature) with

From plugin
amethyst
1.6k30 skills3 commands
Install
$ npx -y skills add vitorpamplona/amethyst --skill kotlin-multiplatform --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/kotlin-multiplatform

Context preview

The summary Claude sees to decide when to auto-load this skill.

Platform abstraction decision-making for Amethyst KMP project. Guides when to abstract vs keep platform-specific, source set placement (commonMain, jvmAndroid, platform-specific), expect/actual patterns. Covers primary targets (Android, JVM/Desktop, iOS — all mature) with

SKILL.md

kotlin-multiplatform.SKILL.md
name: kotlin-multiplatform
description: |
  Platform abstraction decision-making for Amethyst KMP project. Guides when to abstract vs keep platform-specific,
  source set placement (commonMain, jvmAndroid, platform-specific), expect/actual patterns. Covers primary targets
  (Android, JVM/Desktop, iOS — all mature) with web/wasm as possible future targets. Integrates with gradle-expert for dependency issues.
  Triggers on: abstraction decisions ("should I share this?"), source set placement questions, expect/actual creation,
  build.gradle.kts work, incorrect placement detection, KMP dependency suggestions.

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          │  └────┬─────────────┘
   └───┬───────────┬───┘       │
       │           │           │
       ▼           ▼           ├─→ 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 layo
Read more
Ships withamethyst

Nostr client for Android

Get the whole plugin

Other skills on amethyst.