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 web/wasm as possible future
By vitorpamplona · 1,448 installs
npx skills add vitorpamplona/amethyst --skill kotlin-multiplatform
Source repository · Upstream listing
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):
Real Examples from Codebase
Crypto → expect/actual:
Why: Each platform has different security APIs.
JSON parsing → jvmAndroid:
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.
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
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):
Classes (instantiable):
Functions (utilities):
See [references/expect actual catalog.md](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 ( 0.23.0 in libs.versions.toml ) for crypto
AndroidX libraries
Desktop JVM (jvmMain):
Uses Compose Desktop (Window, MenuBar, etc.)
secp256k1 kmp jni jvm (same 0.23.0 line) for crypto
Pure JVM libraries
iOS (iosMain):
Mature target — actively built and tested
Architecture targets: iosArm64, iosSimulatorArm64, iosX64 (plus macosArm64 for host tooling)
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:
1. Prefer pure Kotlin in commonMain
2. Use kotlinx. libraries:
kotlinx.serialization instead of Jackson
ktor instead of OkHttp (ktor supports web)
kotlinx.datetime instead of custom date handling
3. Avoid platform.posix for file operations
4. Test abstractions work without JVM assumptions
Example migration path:
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:
→ Invoke gradle expert for dependency conflict resolution.
Flags to Raise
Platform code in commonMain:
→ Flag: "Android API in commonMain won't compile on other platforms"
Duplicated business logic:
→ 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
Why: Navigation paradigms too different (Activity vs Window)
Fix: Keep platform specific, accept duplication
2. Under Sharing
Problem: Duplicating business logic across platforms
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
Fix: Use expect/actual or dependency injection
4. Premature Abstraction
Problem: Creating expect/actual before second platform needs it
Why: Wrong abstraction boundaries, wasted effort
Fix: Wait until iOS actually needs it, then abstract
5. Wrong Source Set
Problem: JVM libraries in commonMain
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) commonsUI/commonMain Cards, buttons, dialogs (Compose UI never goes in commons )
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](references/abstraction examples.md) Good/bad abstraction examples with rationale
[references/source set hierarchy.md](references/source set hierarchy.md) Visual hierarchy with Amethyst examples
[references/expect actual catalog.md](references/expect actual catalog.md) All 24 expect/actual pairs with "why abstracted"
[references/target compatibility.md](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.)