agentsclimarketplace

Android to kmp

Skill almasumdev/awesome-kotlin-multiplatform-agent-skills/.github/skills/migration/android-to-kmp

Curated agent skills, conventions, and workflows for building Kotlin Multiplatform (KMP) apps with AI coding agents.

Install
npx -y skills add almasumdev/awesome-kotlin-multiplatform-agent-skills --skill android-to-kmp

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Incrementally extracting an existing Android (Kotlin + Jetpack) codebase into Kotlin Multiplatform `commonMain`, without forcing a big-bang rewrite. Use when introducing KMP into a shipping Android app.

SKILL.md

5.1 KB, as published. Nobody here has run it

Migrating an Android App to KMP

Instructions

The goal is to share code without stopping feature delivery. Migrate bottom-up: pure logic first, frameworks last. UI usually stays Jetpack Compose on Android until commonMain is proven.

1. Add a :shared module to the existing project

// shared/build.gradle.kts
plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.androidLibrary)
}

kotlin {
    androidTarget()
    // Add iOS later — don't enable until there's an iOS consumer.
    sourceSets {
        commonMain.dependencies {
            implementation(libs.kotlinx.coroutines.core)
            implementation(libs.kotlinx.serialization.json)
        }
    }
}

android { namespace = "com.example.shared"; compileSdk = 35; defaultConfig.minSdk = 24 }

Add :shared to settings.gradle.kts and have :app depend on it. Existing Android code keeps running; :shared is just an extra module.

2. Start with a pure-Kotlin layer

Pick code that has no Android imports today: domain models, validation, pricing rules, date math. Move the files from :app to :shared/src/commonMain/kotlin/... unchanged. Confirm ./gradlew :app:assembleDebug still builds.

// was app/src/main/kotlin/com/example/pricing/Discount.kt
// now shared/src/commonMain/kotlin/com/example/pricing/Discount.kt
data class Discount(val code: String, val percent: Int) {
    fun apply(cents: Long): Long = cents - cents * percent / 100
}

3. Replace java.time / java.util.Date

commonMain cannot import java.*. Swap for kotlinx-datetime:

// before
val now: LocalDateTime = LocalDateTime.now(ZoneId.of("UTC"))
// after
val now: LocalDateTime = Clock.System.now().toLocalDateTime(TimeZone.UTC)

For JSON, replace Moshi/Gson with kotlinx.serialization:

@Serializable data class UserDto(val id: Long, val name: String)

4. Replace frameworks incrementally

Android libraryKMP replacement
Retrofit + OkHttpKtor client (OkHttp engine on Android)
RoomSQLDelight
WorkManagerKeep on Android; expose a shared interface
SharedPreferencesMultiplatform-Settings or a KeyValueStore IF
Moshi / Gsonkotlinx.serialization
CoilCoil 3 (KMP)

Where no KMP equivalent exists, define an interface in commonMain and keep the Android implementation in androidMain.

5. Migrate a repository

// commonMain — new
interface ArticleRepository {
    suspend fun latest(): List<Article>
    fun observeLatest(): Flow<List<Article>>
}
// commonMain — move the implementation here
class ArticleRepositoryImpl(
    private val api: ArticleApi,        // Ktor
    private val dao: ArticleDao,        // SQLDelight
) : ArticleRepository { /* … */ }

Replace the Retrofit interface + Room DAO usage inside the impl step by step. Keep the ViewModel on Android pointing at ArticleRepository the whole time.

6. Move ViewModels (optional, later)

Once androidx.lifecycle:lifecycle-viewmodel is on KMP (2.8+), ViewModels can live in commonMain:

class FeedViewModel(private val repo: ArticleRepository) : ViewModel() {
    val state = repo.observeLatest()
        .map { FeedState(it) }
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), FeedState())
}

On Android, keep using by viewModels(). Don't introduce iOS until you've validated the shared ViewModel on Android.

7. Adding iOS

Only when :shared builds cleanly for androidTarget() and all dependencies have iOS counterparts, add:

listOf(iosArm64(), iosSimulatorArm64()).forEach {
    it.binaries.framework { baseName = "Shared"; isStatic = true }
}
sourceSets.iosMain.dependencies { implementation(libs.ktor.client.darwin) }

Generate the XCFramework, consume from a new Xcode project, and build the iOS UI afresh.

8. Anti-patterns

  • Don't move Compose screens to commonMain before the domain/data layers are stable there.
  • Don't ship a partial migration where the same logic exists in both :app and :shared.
  • Don't enable iOS targets just to "see if it works" — every unnecessary target multiplies CI time.

Checklist

  • :shared is Android-only until an iOS consumer exists.
  • No java.* / android.* imports in commonMain.
  • Each migrated file has been deleted from :app (no duplicates).
  • Android app still builds and tests pass after every migration step.
  • kotlinx-datetime, kotlinx.serialization, Ktor, SQLDelight chosen as replacements.
  • ViewModels moved only after the underlying repositories are in commonMain.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.