O sonho de escrever código uma vez e rodar em múltiplas plataformas existe há décadas. O Kotlin Multiplatform (KMP) é a abordagem mais pragmática que vi até agora — em vez de substituir a UI nativa, ele compartilha apenas a lógica de negócio.

O que é Kotlin Multiplatform?

KMP permite compartilhar código Kotlin entre plataformas:

  • Android (nativo)
  • iOS (nativo, via Kotlin/Native)
  • Desktop (JVM)
  • Web (Kotlin/JS ou Kotlin/Wasm)

O diferencial do KMP é que ele não substitui a UI nativa. Você compartilha a lógica (repositories, use cases, models, networking) e mantém a UI nativa (Jetpack Compose no Android, SwiftUI no iOS).

Arquitetura

┌─────────────────────────────────────┐
│         Shared Module (KMP)         │
│  ┌───────────────────────────────┐  │
│  │  Models / Data Classes        │  │
│  │  API Client (Ktor)            │  │
│  │  Database (SQLDelight)        │  │
│  │  Use Cases / Business Logic   │  │
│  │  Repository Pattern           │  │
│  └───────────────────────────────┘  │
└──────────┬──────────────┬───────────┘
           │              │
    ┌──────┴──────┐ ┌─────┴──────┐
    │   Android   │ │    iOS     │
    │  Jetpack    │ │  SwiftUI   │
    │  Compose    │ │            │
    └─────────────┘ └────────────┘

Configuração do projeto

build.gradle.kts (shared module)

plugins {
    kotlin("multiplatform")
    kotlin("plugin.serialization") version "1.7.20"
    id("com.android.library")
}

kotlin {
    android()
    
    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach {
        it.binaries.framework {
            baseName = "shared"
        }
    }

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-core:2.2.0")
                implementation("io.ktor:ktor-client-content-negotiation:2.2.0")
                implementation("io.ktor:ktor-serialization-kotlinx-json:2.2.0")
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
                implementation("com.squareup.sqldelight:runtime:1.5.5")
            }
        }
        
        val androidMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-okhttp:2.2.0")
                implementation("com.squareup.sqldelight:android-driver:1.5.5")
            }
        }
        
        val iosMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-darwin:2.2.0")
                implementation("com.squareup.sqldelight:native-driver:1.5.5")
            }
        }
    }
}

Código compartilhado

Model

// shared/src/commonMain/kotlin/Models.kt
import kotlinx.serialization.Serializable

@Serializable
data class Tarefa(
    val id: Int,
    val titulo: String,
    val concluida: Boolean = false
)

API Client

// shared/src/commonMain/kotlin/TarefaApi.kt
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json

class TarefaApi {
    private val client = HttpClient {
        install(ContentNegotiation) {
            json(Json { ignoreUnknownKeys = true })
        }
    }

    suspend fun buscarTarefas(): List<Tarefa> {
        return client.get("https://api.exemplo.com/tarefas").body()
    }

    suspend fun criarTarefa(titulo: String): Tarefa {
        return client.post("https://api.exemplo.com/tarefas") {
            setBody(mapOf("titulo" to titulo))
        }.body()
    }
}

Repository

// shared/src/commonMain/kotlin/TarefaRepository.kt
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow

class TarefaRepository(private val api: TarefaApi) {
    private val _tarefas = MutableStateFlow<List<Tarefa>>(emptyList())
    val tarefas: Flow<List<Tarefa>> = _tarefas

    suspend fun carregar() {
        _tarefas.value = api.buscarTarefas()
    }

    suspend fun adicionar(titulo: String) {
        val nova = api.criarTarefa(titulo)
        _tarefas.value = _tarefas.value + nova
    }
}

Consumindo no Android

// Android - ViewModel
class TarefaViewModel : ViewModel() {
    private val api = TarefaApi()
    private val repo = TarefaRepository(api)
    
    val tarefas = repo.tarefas.stateIn(
        viewModelScope, SharingStarted.Lazily, emptyList()
    )

    fun carregar() {
        viewModelScope.launch { repo.carregar() }
    }

    fun adicionar(titulo: String) {
        viewModelScope.launch { repo.adicionar(titulo) }
    }
}

// Android - Composable
@Composable
fun TarefaScreen(viewModel: TarefaViewModel = viewModel()) {
    val tarefas by viewModel.tarefas.collectAsState()
    
    LazyColumn {
        items(tarefas) { tarefa ->
            Text(tarefa.titulo)
        }
    }
}

Consumindo no iOS (Swift)

// iOS - ViewModel
import shared

class TarefaViewModel: ObservableObject {
    private let api = TarefaApi()
    private let repo: TarefaRepository
    
    @Published var tarefas: [Tarefa] = []
    
    init() {
        repo = TarefaRepository(api: api)
        
        // Coletar Flow do Kotlin
        repo.tarefas.collect { tarefas in
            DispatchQueue.main.async {
                self.tarefas = tarefas
            }
        }
    }
    
    func carregar() {
        Task { await repo.carregar() }
    }
}

// iOS - SwiftUI
struct TarefaView: View {
    @StateObject private var viewModel = TarefaViewModel()
    
    var body: some View {
        List(viewModel.tarefas, id: \.id) { tarefa in
            Text(tarefa.titulo)
        }
        .onAppear { viewModel.carregar() }
    }
}

Vantagens sobre React Native / Flutter

Aspecto KMP React Native Flutter
UI nativa Sim Sim Não
Lógica compartilhada Sim Parcial Sim
Performance Nativa Ponte JS Nativa
Acesso nativo Direto Via bridge Via plugins
Learning curve Kotlin JavaScript Dart

Quando usar KMP

  • Equipe que já conhece Kotlin/Android
  • Apps que precisam de UI nativa em cada plataforma
  • Lógica de negócio complexa que vale a pena compartilhar
  • Apps com muitas integrações nativas (câmera, Bluetooth, pagamentos)

Conclusão

Kotlin Multiplatform é a abordagem mais pragmática para código compartilhado. Em vez de tentar substituir a UI nativa (como Flutter) ou usar uma ponte JavaScript (como React Native), ele compartilha apenas o que faz sentido: a lógica de negócio.

Se você já desenvolve Android com Kotlin, o KMP é natural — você continua escrevendo Kotlin, mas agora o mesmo código roda no iOS. A JetBrains está investindo pesado e o ecossistema está amadurecendo rápido.

Caso eu tenha falado alguma besteira, por favor, agradecerei correções e sugestões.