Android to kmp
Skill almasumdev/awesome-kotlin-multiplatform-agent-skills/.github/skills/migration/android-to-kmp
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.From its SKILL.md
npx -y skills add almasumdev/awesome-kotlin-multiplatform-agent-skills --skill android-to-kmpAssembled 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.
SKILL.md
5.1 KB, ~1.2k tokens by cl100k_base, 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 library | KMP replacement |
|---|---|
Retrofit + OkHttp | Ktor client (OkHttp engine on Android) |
Room | SQLDelight |
WorkManager | Keep on Android; expose a shared interface |
SharedPreferences | Multiplatform-Settings or a KeyValueStore IF |
Moshi / Gson | kotlinx.serialization |
Coil | Coil 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
commonMainbefore the domain/data layers are stable there. - Don't ship a partial migration where the same logic exists in both
:appand:shared. - Don't enable iOS targets just to "see if it works" — every unnecessary target multiplies CI time.
Checklist
-
:sharedis Android-only until an iOS consumer exists. - No
java.*/android.*imports incommonMain. - 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.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.