Estructura del proyecto
Un proyecto KMP típico con Android e iOS tiene esta forma:
miapp-kmp/
├── androidApp/ # App Android — solo UI y Android-specific
│ ├── src/main/
│ └── build.gradle.kts
├── iosApp/ # App iOS — solo UI SwiftUI/UIKit
│ ├── iosApp.xcodeproj/
│ └── iosApp/
│ └── ContentView.swift
├── shared/ # Módulo compartido — el corazón del proyecto
│ ├── src/
│ │ ├── commonMain/ # Código Kotlin que compila en todas las plataformas
│ │ │ └── kotlin/
│ │ ├── androidMain/ # Código específico de Android (actual declarations)
│ │ │ └── kotlin/
│ │ ├── iosMain/ # Código específico de iOS
│ │ │ └── kotlin/
│ │ ├── commonTest/ # Tests que corren en todas las plataformas
│ │ ├── androidUnitTest/
│ │ └── iosTest/
│ └── build.gradle.kts
├── build.gradle.kts # raíz
├── gradle/libs.versions.toml
└── settings.gradle.kts
Gradle con el plugin de KMP
// shared/build.gradle.kts
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidLibrary)
alias(libs.plugins.kotlinxSerialization)
}
kotlin {
// Targets — plataformas para las que compilamos
androidTarget {
compilations.all {
kotlinOptions { jvmTarget = "17" }
}
}
// iOS targets — hay que declarar todas las arquitecturas
iosX64() // simulador en Mac Intel
iosArm64() // dispositivo físico iOS
iosSimulatorArm64() // simulador en Mac Apple Silicon
// XCFramework agrupa los tres targets iOS en un solo framework
val xcframeworkName = "Shared"
val xcf = XCFramework(xcframeworkName)
listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach {
it.binaries.framework {
baseName = xcframeworkName
xcf.add(this)
}
}
sourceSets {
// Código común — disponible en TODAS las plataformas
commonMain.dependencies {
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.serialization.kotlinx.json)
}
// Dependencias específicas de Android
androidMain.dependencies {
implementation(libs.ktor.client.okhttp) // engine OkHttp para Android
}
// Dependencias específicas de iOS
iosMain.dependencies {
implementation(libs.ktor.client.darwin) // engine Darwin para iOS
}
// Tests comunes
commonTest.dependencies {
implementation(libs.kotlin.test)
}
}
}
android {
namespace = "ar.pensa.miapp.shared"
compileSdk = 35
defaultConfig { minSdk = 24 }
}
# gradle/libs.versions.toml
[versions]
kotlin = "2.0.0"
ktor = "3.0.0"
kotlinxCoroutines = "1.9.0"
kotlinxSerialization = "1.7.0"
[libraries]
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
[plugins]
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
androidLibrary = { id = "com.android.library", version = "8.5.0" }
kotlinxSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
Source sets — dónde va cada cosa
# commonMain/kotlin/ar/pensa/app/shared/
# → Toda la lógica que no depende de ninguna plataforma
# → Puede importar: kotlin.*, kotlinx.*, librerías declaradas en commonMain.dependencies
# → NO puede importar: android.*, java.*, Foundation.* (iOS)
# androidMain/kotlin/ar/pensa/app/shared/
# → Implementaciones "actual" para Android
# → Puede importar todo lo que commonMain puede + android.*
# iosMain/kotlin/ar/pensa/app/shared/
# → Implementaciones "actual" para iOS
# → Puede importar todo lo de commonMain + platform.Foundation.*, platform.UIKit.*
# Regla práctica:
# Si el archivo no tiene ningún import de plataforma → va en commonMain
# Si necesita android.* o java.* → va en androidMain
# Si necesita platform.* (objetos de Objective-C/Swift) → va en iosMain
Targets disponibles en KMP
kotlin {
// JVM / Android
androidTarget() // para apps Android
jvm() // para desktop JVM, backend
// iOS / macOS / tvOS / watchOS (todos basados en LLVM)
iosX64()
iosArm64()
iosSimulatorArm64()
macosX64()
macosArm64()
tvosX64()
tvosArm64()
watchosArm64()
// JavaScript / WebAssembly
js(IR) { browser(); nodejs() }
wasmJs { browser() }
// Linux / Windows (para CLIs, herramientas)
linuxX64()
mingwX64() // Windows
// Para Android + iOS (el caso más común), solo necesitás:
// androidTarget(), iosX64(), iosArm64(), iosSimulatorArm64()
}
Crear el proyecto con el wizard de Android Studio
# Opción 1: Android Studio con el plugin KMP
# File → New → New Project → Kotlin Multiplatform App
# (requiere el plugin "Kotlin Multiplatform" instalado en Android Studio)
# Opción 2: KMP Wizard online — la más recomendada para empezar
# https://kmp.jetbrains.com
# → Elegir Android + iOS + shared module
# → Descargar el proyecto generado
# → Abrirlo en Android Studio
# Opción 3: agregar KMP a un proyecto Android existente
# → Crear un nuevo módulo shared en el proyecto existente
# → Mover gradualmente código del módulo app al shared
El primer build — lo que puede fallar
# Buildear el módulo compartido:
./gradlew :shared:build
# Buildear solo el framework para iOS (genera el XCFramework):
./gradlew :shared:assembleXCFramework
# El framework generado aparece en:
# shared/build/XCFrameworks/debug/Shared.xcframework
# Errores comunes en el primer build:
# Error 1: Kotlin version mismatch
# "The applied plugin 'kotlin-multiplatform' requires the 'org.jetbrains.kotlin:kotlin-gradle-plugin' version to be exactly 2.0.0"
# → Asegurarse de que todas las versiones de Kotlin sean consistentes en libs.versions.toml
# Error 2: Xcode Command Line Tools no instalados (para iOS)
# → xcode-select --install
# Error 3: El módulo shared no puede importar librerías de Android
# → Una librería en commonMain.dependencies no puede ser android-only
# → Librerías como Room, Retrofit, Glide no tienen versión KMP
# → Reemplazar por SQLDelight, Ktor, Coil KMP respectivamente