home / skills / physics91 / claude-vibe / kotlin-multiplatform-reviewer

kotlin-multiplatform-reviewer skill

/skills/kotlin-multiplatform-reviewer

This skill reviews Kotlin Multiplatform projects to validate expect/actual, shared module structure, and iOS interop, ensuring clean separation and correct

npx playbooks add skill physics91/claude-vibe --skill kotlin-multiplatform-reviewer

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
8.0 KB
---
name: kotlin-multiplatform-reviewer
description: |
  WHEN: Kotlin Multiplatform (KMP) project review, expect/actual patterns, shared module structure, iOS interop
  WHAT: Module structure analysis + expect/actual validation + platform separation + iOS/Android interop + dependency management
  WHEN NOT: Android UI → kotlin-android-reviewer, Server → kotlin-spring-reviewer
---

# Kotlin Multiplatform Reviewer Skill

## Purpose
Reviews Kotlin Multiplatform (KMP) project structure and patterns including shared code design, expect/actual mechanism, and iOS interop.

## When to Use
- KMP project code review
- "expect/actual", "shared module", "commonMain", "multiplatform" mentions
- iOS/Android code sharing design review
- Projects with `kotlin("multiplatform")` plugin

## Project Detection
- `kotlin("multiplatform")` plugin in `build.gradle.kts`
- `src/commonMain`, `src/androidMain`, `src/iosMain` directories
- `shared` or `common` module exists

## Workflow

### Step 1: Analyze Structure
```
**Kotlin**: 2.0.x
**Targets**: Android, iOS (arm64, simulatorArm64)
**Shared Module**: shared
**Source Sets**:
  - commonMain (shared code)
  - androidMain (Android specific)
  - iosMain (iOS specific)
```

### Step 2: Select Review Areas
**AskUserQuestion:**
```
"Which areas to review?"
Options:
- Full KMP pattern check (recommended)
- Module structure/dependencies
- expect/actual implementation
- Platform code separation
- iOS interop (Swift/ObjC)
multiSelect: true
```

## Detection Rules

### Module Structure
| Check | Recommendation | Severity |
|-------|----------------|----------|
| Bloated shared module | Split by layer | MEDIUM |
| Circular dependencies | Unidirectional deps | HIGH |
| Platform code in commonMain | Move to androidMain/iosMain | HIGH |
| Missing test module | Add commonTest | MEDIUM |

**Recommended Structure:**
```
project/
├── shared/
│   └── src/
│       ├── commonMain/kotlin/    # Shared business logic
│       ├── commonTest/kotlin/    # Shared tests
│       ├── androidMain/kotlin/   # Android specific
│       ├── iosMain/kotlin/       # iOS specific
│       └── iosTest/kotlin/
├── androidApp/                    # Android app
└── iosApp/                       # iOS app (Xcode)
```

### expect/actual Patterns
| Check | Recommendation | Severity |
|-------|----------------|----------|
| actual without expect | expect declaration required | CRITICAL |
| Missing actual impl | Provide actual for all targets | CRITICAL |
| Excessive expect/actual | Consider interface + DI | MEDIUM |
| Direct platform API in actual | Add abstraction layer | MEDIUM |

```kotlin
// commonMain - expect declaration
expect class Platform() {
    val name: String
    fun getDeviceId(): String
}

// androidMain - actual implementation
actual class Platform actual constructor() {
    actual val name: String = "Android ${Build.VERSION.SDK_INT}"
    actual fun getDeviceId(): String = Settings.Secure.getString(
        context.contentResolver,
        Settings.Secure.ANDROID_ID
    )
}

// iosMain - actual implementation
actual class Platform actual constructor() {
    actual val name: String = UIDevice.currentDevice.systemName()
    actual fun getDeviceId(): String = UIDevice.currentDevice
        .identifierForVendor?.UUIDString ?: ""
}
```

**BAD: expect/actual overuse**
```kotlin
// BAD: expect/actual for simple values
expect val platformName: String
actual val platformName: String = "Android"

// GOOD: interface + DI
interface PlatformInfo {
    val name: String
}

// androidMain
class AndroidPlatformInfo : PlatformInfo {
    override val name = "Android"
}
```

### Platform Separation
| Check | Recommendation | Severity |
|-------|----------------|----------|
| Platform import in commonMain | Move to platform source set | CRITICAL |
| Java class in commonMain | expect/actual or pure Kotlin | HIGH |
| UIKit/Android SDK in common | Separate to platform source set | CRITICAL |

```kotlin
// BAD: Android import in commonMain
// commonMain/kotlin/Repository.kt
import android.content.Context  // Compile error!

// GOOD: expect/actual separation
// commonMain
expect class DataStore {
    fun save(key: String, value: String)
    fun get(key: String): String?
}

// androidMain
actual class DataStore(private val context: Context) {
    private val prefs = context.getSharedPreferences("app", Context.MODE_PRIVATE)
    actual fun save(key: String, value: String) {
        prefs.edit().putString(key, value).apply()
    }
    actual fun get(key: String): String? = prefs.getString(key, null)
}

// iosMain
actual class DataStore {
    actual fun save(key: String, value: String) {
        NSUserDefaults.standardUserDefaults.setObject(value, key)
    }
    actual fun get(key: String): String? =
        NSUserDefaults.standardUserDefaults.stringForKey(key)
}
```

### iOS Interop
| Check | Recommendation | Severity |
|-------|----------------|----------|
| Missing @ObjCName | Swift-friendly naming | LOW |
| Sealed class iOS exposure | Use enum or @ObjCName | MEDIUM |
| Direct Flow exposure to iOS | Provide wrapper function | HIGH |
| suspend function iOS call | Provide completion handler wrapper | HIGH |

```kotlin
// BAD: Direct suspend function exposure
class Repository {
    suspend fun fetchData(): Data  // Hard to call from iOS
}

// GOOD: iOS wrapper provided
class Repository {
    suspend fun fetchData(): Data

    // iOS completion handler wrapper
    fun fetchDataAsync(completion: (Data?, Error?) -> Unit) {
        MainScope().launch {
            try {
                val data = fetchData()
                completion(data, null)
            } catch (e: Exception) {
                completion(null, e)
            }
        }
    }
}
```

**Flow iOS Exposure:**
```kotlin
// BAD: Direct Flow exposure
val dataFlow: Flow<Data>

// GOOD: iOS wrapper
fun observeData(onEach: (Data) -> Unit): Closeable {
    val job = MainScope().launch {
        dataFlow.collect { onEach(it) }
    }
    return object : Closeable {
        override fun close() { job.cancel() }
    }
}
```

### Dependency Management
| Check | Recommendation | Severity |
|-------|----------------|----------|
| Platform library in commonMain | Use multiplatform library | HIGH |
| Version mismatch | Use Version Catalog | MEDIUM |
| Unused dependencies | Remove unused | LOW |

**Multiplatform Library Recommendations:**
| Purpose | Library |
|---------|---------|
| HTTP | Ktor Client |
| Serialization | Kotlinx Serialization |
| Async | Kotlinx Coroutines |
| DI | Koin, Kodein |
| Date/Time | Kotlinx Datetime |
| Settings | Multiplatform Settings |
| Logging | Napier, Kermit |
| DB | SQLDelight |

## Response Template
```
## KMP Project Review Results

**Project**: [name]
**Kotlin**: 2.0.x
**Targets**: Android, iOS (arm64, simulatorArm64)

### Module Structure
| Status | Item | Issue |
|--------|------|-------|
| OK | Source set separation | commonMain/androidMain/iosMain correct |
| MEDIUM | Tests | Add commonTest recommended |

### expect/actual
| Status | File | Issue |
|--------|------|-------|
| OK | Platform.kt | expect/actual correctly implemented |
| HIGH | DataStore.kt | Missing iosMain actual implementation |

### iOS Interop
| Status | Item | Issue |
|--------|------|-------|
| HIGH | Repository.kt | suspend function needs iOS wrapper |
| MEDIUM | UiState.kt | Add @ObjCName to sealed class |

### Recommended Actions
1. [ ] Add DataStore iosMain actual implementation
2. [ ] Add completion handler wrapper to fetchData()
3. [ ] Add commonTest source set
```

## Best Practices
1. **Share Scope**: Business logic > Data layer > UI (optional)
2. **expect/actual**: Minimize usage, prefer interface + DI
3. **iOS Interop**: Use SKIE library or manual wrappers
4. **Testing**: Test shared logic in commonTest
5. **Dependencies**: Prefer multiplatform libraries

## Integration
- `kotlin-android-reviewer` skill: Android specific code
- `kotlin-spring-reviewer` skill: Server shared code
- `code-reviewer` skill: General code quality

## Notes
- Based on Kotlin 2.0+
- KMP 1.9.20+ recommended (Stable)
- Compose Multiplatform requires separate review

Overview

This skill reviews Kotlin Multiplatform (KMP) projects to validate module layout, expect/actual patterns, platform separation, iOS interop, and dependency choices. It highlights critical issues like platform APIs leaking into commonMain, missing actual implementations, and unsafe Flow/suspend exposures to iOS. Use it to get actionable fixes and prioritized recommendations for KMP codebases.

How this skill works

The reviewer scans build files for the kotlin("multiplatform") plugin and common source set layout (commonMain, androidMain, iosMain) to detect module structure and target configuration. It validates expect/actual declarations, flags platform API usage in common code, evaluates iOS interoperability (suspend/Flow exposure, @ObjCName, sealed classes), and checks dependency placement and version consistency. The output lists issues by severity and provides concrete remediation steps.

When to use it

  • Performing a code review of a Kotlin Multiplatform project (shared + platform modules).
  • Assessing expect/actual usage, missing actual implementations, or excessive platform-specific code in commonMain.
  • Improving iOS interoperability (Swift/ObjC exposure, suspend/Flow wrappers, naming).
  • Verifying module separation, circular dependencies, and multiplatform dependency correctness.
  • Not for Android UI-focused reviews (use Android-specific reviewer) or server-only Kotlin projects.

Best practices

  • Keep commonMain limited to pure business logic; move SDK imports to androidMain/iosMain.
  • Prefer interfaces + DI instead of expect/actual for simple contracts to reduce boilerplate.
  • Wrap suspend and Flow APIs with completion/observer wrappers for easy iOS consumption.
  • Use multiplatform libraries (Ktor, kotlinx.serialization, SQLDelight, Napier) in commonMain when possible.
  • Maintain a commonTest source set and check for circular dependencies and version mismatches.

Example use cases

  • Audit a shared module that mixes Android APIs into commonMain and produce a migration plan.
  • Detect missing iosMain actual implementations for expect declarations and list required files.
  • Identify suspend/Flow methods exposed to iOS and generate recommended async wrappers.
  • Evaluate dependency placement and suggest multiplatform alternatives or version-catalog alignment.
  • Review module boundaries to split a bloated shared module into logical layers.

FAQ

What if I find actual implementations missing for a target?

Mark them as critical: add platform-specific actual implementations or replace expect/actual with an interface and provide platform bindings via DI.

How should I expose async behavior to iOS?

Provide completion-handler wrappers for suspend functions and Closeable observer wrappers for Flow to avoid exposing Kotlin coroutines/Flow directly.