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
Stats
1,599
Stars
221
Forks
Active
Maintenance
Kotlin
Language
MIT
License
8h ago
Last commit
3y ago
Created

Repo: vitorpamplona/amethyst

Other skills on amethyst.