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.
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.
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 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.