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
Inherits all available tools
Additional assets for this skill
This skill inherits all available tools. When active, it can use any tool Claude has access to.
Reviews Kotlin Multiplatform (KMP) project structure and patterns including shared code design, expect/actual mechanism, and iOS interop.
kotlin("multiplatform") pluginkotlin("multiplatform") plugin in build.gradle.ktssrc/commonMain, src/androidMain, src/iosMain directoriesshared or common module exists**Kotlin**: 2.0.x
**Targets**: Android, iOS (arm64, simulatorArm64)
**Shared Module**: shared
**Source Sets**:
- commonMain (shared code)
- androidMain (Android specific)
- iosMain (iOS specific)
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
| 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)
| 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 |
// 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
// 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"
}
| 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 |
// 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)
}
| 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 |
// 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:
// 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() }
}
}
| 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 |
## 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
kotlin-android-reviewer skill: Android specific codekotlin-spring-reviewer skill: Server shared codecode-reviewer skill: General code quality