Skill

kmp-patterns

Build Kotlin Multiplatform (KMP) applications — shared business logic across Android, iOS, desktop, and web. Covers project structure, expect/actual declarations, Ktor networking, SQLDelight database, Koin DI, and Compose Multiplatform UI. Use when building KMP apps, sharing code between platforms, or implementing cross-platform features. Trigger on "KMP", "Kotlin Multiplatform", "Compose Multiplatform", "shared module", "expect actual".

From project-orchestrator
Install
1
Run in your terminal
$
npx claudepluginhub vivekmano27/agent-orchestrator --plugin project-orchestrator
Tool Access

This skill is limited to using the following tools:

ReadWriteEditBashGrepGlob
Skill Content

Kotlin Multiplatform (KMP) Patterns Skill

Build cross-platform applications sharing business logic.

Project Structure

project/
├── shared/                       → Shared KMP module
│   ├── src/
│   │   ├── commonMain/           → Shared code (all platforms)
│   │   │   └── kotlin/
│   │   │       ├── domain/       → Entities, use cases
│   │   │       ├── data/         → Repositories, DTOs
│   │   │       ├── network/      → Ktor API client
│   │   │       ├── database/     → SQLDelight queries
│   │   │       └── di/           → Koin modules
│   │   ├── androidMain/          → Android-specific implementations
│   │   ├── iosMain/              → iOS-specific implementations
│   │   └── commonTest/           → Shared tests
│   └── build.gradle.kts
├── androidApp/                   → Android application
│   └── src/main/
│       └── kotlin/               → Android UI (Compose)
├── iosApp/                       → iOS application (SwiftUI wrapper)
│   └── iosApp/
│       ├── ContentView.swift
│       └── iOSApp.swift
├── desktopApp/                   → Desktop (optional)
└── build.gradle.kts

Expect/Actual Pattern

// commonMain — declare expected interface
expect class PlatformContext

expect fun getPlatformName(): String

expect class SecureStorage(context: PlatformContext) {
    fun save(key: String, value: String)
    fun get(key: String): String?
    fun delete(key: String)
}

// androidMain — Android implementation
actual typealias PlatformContext = Context

actual fun getPlatformName(): String = "Android ${Build.VERSION.SDK_INT}"

actual class SecureStorage actual constructor(context: PlatformContext) {
    private val prefs = EncryptedSharedPreferences.create(/*...*/)
    actual fun save(key: String, value: String) { prefs.edit().putString(key, value).apply() }
    actual fun get(key: String): String? = prefs.getString(key, null)
    actual fun delete(key: String) { prefs.edit().remove(key).apply() }
}

// iosMain — iOS implementation
actual typealias PlatformContext = Unit

actual fun getPlatformName(): String = UIDevice.currentDevice.systemName

actual class SecureStorage actual constructor(context: PlatformContext) {
    actual fun save(key: String, value: String) { /* Keychain */ }
    actual fun get(key: String): String? { /* Keychain */ }
    actual fun delete(key: String) { /* Keychain */ }
}

Ktor Networking (Shared)

// commonMain
class ApiClient(private val httpClient: HttpClient) {
    suspend fun getUsers(): List<UserDto> =
        httpClient.get("https://api.example.com/users").body()
    
    suspend fun createUser(dto: CreateUserDto): UserDto =
        httpClient.post("https://api.example.com/users") {
            contentType(ContentType.Application.Json)
            setBody(dto)
        }.body()
}

// Shared HttpClient config
val httpClient = HttpClient {
    install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) }
    install(Logging) { level = LogLevel.INFO }
    install(Auth) {
        bearer {
            loadTokens { BearerTokens(tokenStorage.getAccessToken(), tokenStorage.getRefreshToken()) }
            refreshTokens { /* refresh logic */ }
        }
    }
}

SQLDelight (Shared Database)

-- shared/src/commonMain/sqldelight/com/example/db/User.sq
CREATE TABLE User (
    id TEXT NOT NULL PRIMARY KEY,
    email TEXT NOT NULL UNIQUE,
    name TEXT NOT NULL,
    created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
);

getAll:
SELECT * FROM User ORDER BY created_at DESC;

getById:
SELECT * FROM User WHERE id = ?;

insert:
INSERT OR REPLACE INTO User (id, email, name) VALUES (?, ?, ?);

deleteById:
DELETE FROM User WHERE id = ?;

Compose Multiplatform UI (Shared)

@Composable
fun UserListScreen(viewModel: UserListViewModel = koinViewModel()) {
    val state by viewModel.state.collectAsState()
    
    when (val s = state) {
        is UiState.Loading -> CircularProgressIndicator()
        is UiState.Error -> ErrorMessage(s.message, onRetry = viewModel::refresh)
        is UiState.Success -> LazyColumn {
            items(s.users) { user -> UserCard(user) }
        }
    }
}
Stats
Parent Repo Stars0
Parent Repo Forks0
Last CommitMar 15, 2026