From chrisbanes-skills
Guides design of Kotlin Multiplatform expect/actual and interface boundaries for platform services, native SDKs, Compose Multiplatform UI, permissions, files, settings, sensors, or platform interop.
How this skill is triggered — by the user, by Claude, or both
Slash command
/chrisbanes-skills:kotlin-multiplatform-expect-actualThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Keep common APIs semantic and stable. Put platform mechanics behind small `expect`/`actual` declarations or interfaces, and keep Android/iOS/Desktop details out of `commonMain`.
Keep common APIs semantic and stable. Put platform mechanics behind small expect/actual declarations or interfaces, and keep Android/iOS/Desktop details out of commonMain.
Use this when common code needs:
expect/actual, dependency injection, interfaces, or separate platform code.| Situation | Prefer |
|---|---|
| Simple compile-time platform specialization | expect/actual function, value, typealias, or leaf composable |
| Implementation needs injected dependencies, lifecycle ownership, runtime choice, or test fakes | Common interface plus platform binding |
| UI is mostly shared, one leaf differs | Common composable calling an expect leaf |
| Entire screen differs by platform | Separate platform screens behind a common navigation contract |
| Only constants/resources differ | Common API exposing semantic values, actual values per platform |
Common code should describe what the product needs, not how the platform does it:
// GOOD: common API is semantic
expect fun currentRegion(): Region
// BAD: common API leaks Android implementation
expect fun currentRegionFromAndroidLocale(context: Context): Region
The Android actual can use Locale APIs. The iOS actual can use Foundation APIs. Callers should not know.
Actual implementations should translate the semantic API into platform calls. If the operation needs an Activity, view controller, lifecycle owner, DI, or fakes, prefer an interface supplied by platform code instead of an expect class:
// commonMain
interface ShareSheet {
suspend fun shareText(text: String)
}
// androidMain
class AndroidShareSheet(
private val activity: Activity,
) : ShareSheet {
override suspend fun shareText(text: String) {
val intent = Intent(Intent.ACTION_SEND)
.setType("text/plain")
.putExtra(Intent.EXTRA_TEXT, text)
activity.startActivity(Intent.createChooser(intent, null))
}
}
The Android implementation is explicitly Activity-owned. A generic Context may need FLAG_ACTIVITY_NEW_TASK and usually hides the UI lifecycle requirement. Define what suspend means: for many platform UI actions it means "the sheet was launched", not "the user completed sharing."
If the actual starts accumulating business rules, move those rules back to common code and leave only platform translation in the actual.
Use expect/actual for simple compile-time platform APIs. Use interfaces when common code needs fakes, multiple implementations, runtime selection, or lifecycle ownership:
interface Clipboard {
suspend fun setText(text: String)
}
Platform modules bind Clipboard to Android/iOS implementations. Common tests use a fake.
Modifier through every expected Composable that emits UI.commonMain signatures (Context, Activity, Android resource IDs, Uri, Bundle, UIViewController, NSBundle, platform permission enums, etc.).AndroidView, UIKitView, etc.).remember, LaunchedEffect, DisposableEffect, and stable keys inside actual Composables just as you would in common Compose code.| Mistake | Fix |
|---|---|
commonMain API exposes Android/iOS types | Replace with semantic common types |
expect function has parameters for one platform only | Move those details into the actual |
| Business branching duplicated in each actual | Move business rules to common code |
One huge Platform expect object | Split by capability: Clipboard, ShareSheet, Haptics |
| Platform UI leaks high in the tree | Push platform-specific Composable to a leaf |
| No fakeable boundary for common tests | Use an interface instead of direct expect call |
Stay focused on platform boundaries in this skill; wire shared UI like any other Compose target:
compose-state-holder-ui-split — shared plain UI composables vs state-holder wiring.compose-side-effects — effect keys and cleanup in actual composables (LaunchedEffect, DisposableEffect, etc.).compose-modifier-and-layout-style and compose-slot-api-pattern — reusable shared Compose APIs (modifiers, slots).npx claudepluginhub chrisbanes/skills --plugin chrisbanes-skillsImplements Kotlin Multiplatform expect/actual for shared APIs with Android/iOS-specific code for platform info, file systems, and date/time. Useful for KMP cross-platform development.
Provides patterns for shared UI in Compose Multiplatform across Android, iOS, Desktop, and Web: state management with ViewModels/StateFlow, navigation, theming, and performance.
Provides Compose Multiplatform and Jetpack Compose patterns for state management, navigation, theming, and performance optimization in KMP projects.