Kotlin: Introdução ao Kotlin Multiplatform
Última atualização: 2026-08-26
KMP é a visão definitiva do Kotlin — o OrderValidator do Charlie é escrito uma vez e reutilizado no backend JVM, app iOS, frontend JS e navegador Wasm. O mecanismo expect/actual mantém as diferenças de plataforma apenas nas bordas.
1. O que Você Aprenderá
- Arquitetura KMP:
commonMain/jvmMain/iosMain/jsMain - Estratégias de módulos compartilhados: rede, modelos de dados, lógica de negócio
- Configuração Gradle multiplataforma
- Interoperabilidade: JVM ↔ Java / iOS ↔ Swift / JS ↔ JavaScript
- Charlie em ação:
OrderValidatorcompartilhado emcommonMain
2. A História Real de um Arquiteto
(1) Problema: Triplicando a Lógica de Negócio em Três Plataformas
A empresa do Charlie tem um backend JVM, um app iOS e um frontend JS. A mesma lógica de OrderValidator foi implementada três vezes em Java, Swift e TypeScript. Corrigir um bug exigia mudanças em três lugares — isso causou dois incidentes de comportamento inconsistente em um mês.
(2) Solução de Código Compartilhado do KMP
// commonMain: UMA implementação para todas as plataformas
class OrderValidator {
fun validate(order: Order): List<String> {
val errors = mutableListOf<String>()
if (!order.id.startsWith("ORD-")) errors.add("Invalid ID")
if (order.total < 0) errors.add("Negative total")
return errors
}
}
Uma base de código, três plataformas. Corrija um bug uma vez, e o comportamento será naturalmente consistente em todos os lugares.
3. Arquitetura KMP
(1) Estrutura do Projeto
shared/
├── src/
│ ├── commonMain/kotlin/ # Código compartilhado (todas as plataformas)
│ │ └── com/order/
│ │ ├── Order.kt
│ │ ├── OrderValidator.kt
│ │ └── Platform.kt # declarações expect
│ ├── commonTest/kotlin/ # Testes compartilhados
│ ├── jvmMain/kotlin/ # Código específico da JVM
│ │ └── com/order/
│ │ └── Platform.kt # actual para JVM
│ ├── iosMain/kotlin/ # Código específico do iOS
│ │ └── com/order/
│ │ └── Platform.kt # actual para iOS
│ └── jsMain/kotlin/ # Código específico do JS
│ └── com/order/
│ └── Platform.kt # actual para JS
└── build.gradle.kts
(2) Mecanismo expect / actual
// commonMain: declare o que você precisa (expect)
expect fun getPlatformName(): String
expect class DateFormatter() {
fun format(timestamp: Long): String
}
// jvmMain: forneça implementação JVM (actual)
actual fun getPlatformName(): String = "JVM"
actual class DateFormatter actual constructor() {
actual fun format(timestamp: Long): String =
java.text.SimpleDateFormat("yyyy-MM-dd").format(timestamp)
}
// iosMain: forneça implementação iOS (actual)
actual fun getPlatformName(): String = "iOS"
actual class DateFormatter actual constructor() {
actual fun format(timestamp: Long): String {
// Usa NSDateFormatter
return NSDateFormatter().apply {
dateFormat = "yyyy-MM-dd"
}.stringFromDate(NSDate(timestamp / 1000.0))
}
}
// jsMain: forneça implementação JS (actual)
actual fun getPlatformName(): String = "JS"
actual class DateFormatter actual constructor() {
actual fun format(timestamp: Long): String {
// Usa JavaScript Date
return js("new Date(timestamp).toISOString().split('T')[0]")
}
}
(3) Arquitetura Multiplataforma KMP
flowchart TD
A[commonMain<br/>Lógica de Negócio Compartilhada] --> B[jvmMain<br/>Actual JVM]
A --> C[iosMain<br/>Actual iOS]
A --> D[jsMain<br/>Actual JS]
A --> E[wasmMain<br/>Actual Wasm]
B --> B1[Spring Boot<br/>Backend]
C --> C1[App iOS<br/>Interop Swift]
D --> D1[Node.js / Navegador]
E --> E1[Runtime Web Assembly]
4. Configuração Gradle Multiplataforma
(1) build.gradle.kts
plugins {
kotlin("multiplatform") version "1.9.22"
}
group = "com.order"
version = "1.0.0"
repositories {
mavenCentral()
}
kotlin {
// Declarar plataformas alvo
jvm {
compilations.all {
kotlinOptions.jvmTarget = "17"
}
testRuns["test"].executionTask.configure {
useJUnitPlatform()
}
}
iosX64()
iosArm64()
iosSimulatorArm64()
js(IR) {
browser()
nodejs()
}
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
val jvmMain by getting
val jvmTest by getting
val iosMain by getting
val jsMain by getting
}
}
5. Estratégia de Módulo Compartilhado
(1) O que Compartilhar e o que Não Compartilhar
| Camada | Estratégia de Compartilhamento | Motivo |
|---|---|---|
| Modelos de dados | ✅ Totalmente compartilhado | Dados puros, sem dependência de plataforma |
| Lógica de negócio | ✅ Totalmente compartilhado | Regras centrais devem ser consistentes |
| Rede | ✅ Interface compartilhada + expect HTTP | Interface unificada, implementações diferem |
| Serialização | ✅ Compartilhado (kotlinx.serialization) | Suporte nativo a múltiplos formatos |
| UI | ❌ Específico da plataforma | Frameworks de UI diferem significativamente |
| Banco de dados | ⚠️ SQL compartilhado / expect | SQL pode ser compartilhado, drivers diferem |
| Logging | ⚠️ expect/actual | APIs de logging por plataforma diferem |
(2) Padrões de Estratégia Compartilhada
// Padrão 1: Código puramente compartilhado (sem expect/actual necessário)
data class Order(val id: String, val total: Double, val status: String)
class OrderValidator {
fun validate(order: Order): List<String> = buildList {
if (!order.id.startsWith("ORD-")) add("Invalid ID format")
if (order.total < 0) add("Negative total")
if (order.status !in validStatuses) add("Invalid status")
}
companion object {
private val validStatuses = setOf("PENDING", "CONFIRMED", "SHIPPED", "CANCELLED")
}
}
// Padrão 2: Interface + fábrica expect
interface HttpClient {
suspend fun get(url: String): String
suspend fun post(url: String, body: String): String
}
expect fun createHttpClient(): HttpClient
// Padrão 3: Função expect para comportamento específico da plataforma
expect fun logDebug(tag: String, message: String)
6. Interoperabilidade
(1) Métodos de Interoperabilidade por Plataforma
| Par de Plataformas | Interoperabilidade | Observações |
|---|---|---|
| JVM ↔ Java | Bidirecional perfeita | Kotlin chama Java diretamente; Java pode chamar Kotlin |
| iOS ↔ Swift | Bidirecional | Kotlin compila para framework Obj-C; Swift chama perfeitamente |
| JS ↔ JavaScript | Bidirecional | Função js() chama JS; @JsExport exporta Kotlin |
| Wasm ↔ JS | Unidirecional (JS chama Kotlin) | Módulo Wasm exporta funções para chamadas JS |
(2) Exemplo de Interoperabilidade JVM
// Kotlin chamando Java
val order = JavaOrderService() // Classe Java
order.processOrder("ORD-001") // Método Java
// Java chamando Kotlin (bytecode gerado é padrão)
// OrderKt.processOrder(order); // Função de nível superior
(3) Exemplo de Interoperabilidade JS
// Kotlin chamando JavaScript
fun fetchFromApi(url: String): dynamic {
return js("fetch(url).then(r => r.json())")
}
// Exportar Kotlin para JavaScript
@JsExport
class OrderValidator {
fun validate(id: String, total: Double): Boolean {
return id.startsWith("ORD-") && total >= 0
}
}
7. Exemplo Completo: OrderValidator Multiplataforma
// ============================================
// OrderProcessor - Módulo Compartilhado KMP
// Funcionalidade: OrderValidator compartilhado com logging por plataforma
// ============================================
// --- commonMain ---
data class Order(val id: String, val total: Double, var status: String, val customer: String)
// expect: declaração específica da plataforma
expect fun logInfo(tag: String, message: String)
class OrderValidator {
fun validate(order: Order): ValidationResult {
val errors = mutableListOf<String>()
if (!order.id.startsWith("ORD-")) errors.add("Invalid ID format: ${order.id}")
if (order.total < 0) errors.add("Negative total: ${order.total}")
if (order.total > 1_000_000) errors.add("Total exceeds maximum: ${order.total}")
if (order.status !in VALID_STATUSES) errors.add("Invalid status: ${order.status}")
val result = if (errors.isEmpty()) ValidationResult.Valid else ValidationResult.Invalid(errors)
logInfo("OrderValidator", "Validated ${order.id}: $result")
return result
}
fun calculatePriority(order: Order): String = when {
order.total > 10_000 -> "HIGH"
order.total > 1_000 -> "MEDIUM"
else -> "LOW"
}
companion object {
private val VALID_STATUSES = setOf("PENDING", "CONFIRMED", "SHIPPED", "DELIVERED", "CANCELLED")
}
}
sealed class ValidationResult {
object Valid : ValidationResult()
data class Invalid(val errors: List<String>) : ValidationResult()
}
// --- jvmMain ---
// actual fun logInfo(tag: String, message: String) {
// println("[$tag] $message") // Ou use SLF4J
// }
// --- iosMain ---
// actual fun logInfo(tag: String, message: String) {
// NSLog("$tag: $message")
// }
// --- jsMain ---
// actual fun logInfo(tag: String, message: String) {
// console.log("[$tag] $message")
// }
// --- Demo (alvo JVM) ---
fun main() {
// Simular actual JVM
// actual fun logInfo(tag: String, message: String) = println("[$tag] $message")
val validator = OrderValidator()
val orders = listOf(
Order("ORD-001", 299.99, "PENDING", "Alice"),
Order("BAD-002", -50.0, "INVALID", "Bob"),
Order("ORD-003", 15_000.00, "CONFIRMED", "Charlie"),
Order("ORD-004", 2_000_000.00, "PENDING", "Dave")
)
println("=== KMP OrderValidator Demo ===")
orders.forEach { order ->
val result = validator.validate(order)
val priority = validator.calculatePriority(order)
when (result) {
is ValidationResult.Valid -> println(" ✅ ${order.id}: Válido (Prioridade: $priority)")
is ValidationResult.Invalid -> println(" ❌ ${order.id}: ${result.errors}")
}
}
}
Saída:
=== KMP OrderValidator Demo ===
✅ ORD-001: Válido (Prioridade: LOW)
❌ BAD-002: [Invalid ID format: BAD-002, Negative total: -50.0, Invalid status: INVALID]
✅ ORD-003: Válido (Prioridade: HIGH)
❌ ORD-004: [Total exceeds maximum: 2000000.0]
9. Exemplos práticos rápidos
▶ Exemplo: Estrutura de projeto KMP
// build.gradle.kts (root)
plugins {
kotlin("multiplatform") version "1.9.0"
}
kotlin {
iosX64()
iosArm64()
jvm()
sourceSets {
val commonMain by getting
val commonTest by getting { dependencies { kotlinTest {} } }
val iosX64Main by getting
val iosArm64Main by getting
val iosMain by creating { dependsOn(commonMain) }
val jvmMain by getting { dependsOn(commonMain) }
val jvmTest by getting { dependsOn(commonTest) }
}
}
▶ Exemplo: Código commonMain
// commonMain/.../Platform.kt
// expect: declaração que cada plataforma deve fornecer
expect fun getPlatformName(): String
// commonMain/.../OrderProcessor.kt
data class Order(val id: String, val total: Double)
class OrderProcessor {
fun process(order: Order): String {
return "[${getPlatformName()}] Processando ${order.id}"
}
}
▶ Exemplo: Platform-specific implementations
// androidMain/.../Platform.kt
actual fun getPlatformName(): String = "Android ${android.os.Build.VERSION.SDK_INT}"
// iosMain/.../Platform.kt
import platform.UIKit.UIDevice
actual fun getPlatformName(): String {
val device = UIDevice.currentDevice
return "iOS ${device.systemVersion}"
}
// jvmMain/.../Platform.kt
actual fun getPlatformName(): String = "JVM ${System.getProperty("java.version")}"
// Uso
val processor = OrderProcessor()
println(processor.process(Order("ORD-001", 299.99)))
// Saída varia: "[Android 33]...", "[iOS 17.2]...", "[JVM 17.0.7]..."
▶ Exemplo: expect/actual para APIs dependentes
// commonMain - declare API
expect class FileSystem {
fun read(path: String): String
fun write(path: String, content: String)
}
expect fun currentTimeMillis(): Long
// jvmMain
actual class FileSystem {
actual fun read(path: String) = java.io.File(path).readText()
actual fun write(path: String, content: String) {
java.io.File(path).writeText(content)
}
}
actual fun currentTimeMillis() = System.currentTimeMillis()
// iosMain
import platform.Foundation.*
actual class FileSystem {
actual fun read(path: String): String {
return NSString.stringWithContentsOfFile(path, encoding = NSUTF8StringEncoding, error = null) ?: ""
}
actual fun write(path: String, content: String) {
NSString.stringWithString(content).writeToFile(path, atomically = true, encoding = NSUTF8StringEncoding, error = null)
}
}
actual fun currentTimeMillis() = NSDate.timeIntervalSinceReferenceDate.toLong() * 1000
▶ Exemplo: Compartilhando lógica de negócios
// commonMain - business logic puro
class PricingCalculator {
fun applyDiscount(subtotal: Double, tier: String): Double {
val discount = when (tier) {
"GOLD" -> 0.15
"PLATINUM" -> 0.25
else -> 0.0
}
return subtotal * (1 - discount)
}
fun calculateTax(amount: Double) = amount * 0.08
}
// Usado em qualquer plataforma
val calc = PricingCalculator()
val original = 1000.0
val discounted = calc.applyDiscount(original, "PLATINUM")
val withTax = calc.calculateTax(discounted) + discounted
println("Original: \$$original")
println("Com desconto 25%: \$$discounted")
println("Com imposto: \$$withTax")
// Saída:
// Original: $1000.0
// Com desconto 25%: $750.0
// Com imposto: $810.0
▶ Exemplo: Biblioteca KMP popular (klibs)
// Usando kotlinx-datetime em commonMain
import kotlinx.datetime.*
fun formatDate(date: Instant): String {
val local = date.toLocalDateTime(TimeZone.currentSystemDefault())
return "${local.year}-${local.monthNumber.toString().padStart(2, '0')}-${local.dayOfMonth.toString().padStart(2, '0')}"
}
val now = Clock.System.now()
println("Data atual: ${formatDate(now)}")
// Adicionar dias
val dueDate = now.plus(7, DateTimeUnit.DAY, TimeZone.currentSystemDefault())
println("Vencimento: ${formatDate(dueDate)}")
// Saída:
// Data atual: 2026-07-28
// Vencimento: 2026-08-04
▶ Exemplo: Platform-agnostic networking
// commonMain usando ktor
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
class ApiClient(private val baseUrl: String) {
private val client = HttpClient()
suspend fun fetchOrder(id: String): String {
val response = client.get("$baseUrl/orders/$id")
return response.bodyAsText()
}
}
// Uso:
val api = ApiClient("https://api.example.com")
val orderJson = api.fetchOrder("ORD-001")
println(orderJson)
❓ Perguntas Frequentes
P: Qual a diferença entre KMP e Flutter? R: KMP compartilha lógica de negócio (UI nativa por plataforma); Flutter compartilha UI (renderização Skia). KMP é mais flexível (preservando a experiência de UI nativa); Flutter é mais unificado (UI única). Eles podem se complementar.
P: O KMP está pronto para produção? R: Alvos JVM e Android são totalmente estáveis; alvo iOS é estável; alvos JS e Wasm estão amadurecendo rapidamente. Empresas como Netflix, VMware e Cash App já o usam em larga escala em produção.
P: expect/actual pode ser usado para classes? R: Sim.
expect classdeclara uma interface multiplataforma;actual classfornece implementações por plataforma. Construtor e assinaturas de métodos devem corresponder exatamente.
P: Como organizar módulos compartilhados e de plataforma? R: Coloque a lógica compartilhada em
commonMain; isole diferenças de plataforma comexpect/actualnas bordas. Evite código específico de plataforma emcommonMain— useexpectpara abstraí-lo.
P: O KMP é amigável para desenvolvedores iOS? R: Muito amigável. KMP compila para um framework Obj-C que Swift chama perfeitamente. Desenvolvedores iOS só precisam se preocupar com o lado Swift — não precisam conhecer Kotlin.
P: Como é a velocidade de build do KMP? R: Compilação multi-alvo aumenta o tempo de build (cada alvo compila independentemente). Compilação incremental do Gradle e cache do compilador Kotlin ajudam a mitigar isso. Em CI/CD, builds paralelas por alvo são recomendadas.
📖 Resumo
- Arquitetura central do KMP:
commonMain(compartilhado) + source sets específicos de plataforma (jvmMain, iosMain, jsMain, ...) expectdeclara requisitos multiplataforma;actualfornece implementações por plataforma- Modelos de dados e lógica de negócio são totalmente compartilhados; UI e APIs de plataforma usam
expect/actual - Gradle
kotlin("multiplatform")declara builds multi-alvo - Interoperabilidade de plataforma: JVM↔Java perfeita, iOS↔Swift via framework Obj-C, JS↔JS via
js()/@JsExport - KMP não é tudo ou nada — você pode adotá-lo gradualmente compartilhando apenas módulos parciais
📝 Exercícios
- Iniciante (⭐): Crie um projeto KMP, defina
OrderemcommonMaine use-o nos alvos JVM e JS. Dica: Plugin Gradlekotlin("multiplatform") - Intermediário (⭐⭐): Use
expect/actualpara fazergetPlatformName()retornar valores diferentes na JVM e no JS. Dica:expect fun getPlatformName(): String+ implementaçõesactual - Desafio (⭐⭐⭐): Implemente um cliente HTTP multiplataforma: defina a interface
HttpClientemcommonMain, implemente comjava.net.URLemjvmMaine com a APIfetchemjsMain. Dica:expect fun createHttpClient(): HttpClient