Escrever o mesmo código duas (ou três) vezes para plataformas diferentes é uma dor que todo time de desenvolvimento conhece. Kotlin Multiplatform (KMP) resolve isso permitindo compartilhar lógica de negócio entre Android, iOS, web e backend — tudo em Kotlin. Vamos entender como funciona.

O que é Kotlin Multiplatform?

Kotlin Multiplatform é uma tecnologia da JetBrains que permite escrever código uma vez e usar em múltiplas plataformas. Diferente de soluções como Flutter ou React Native, o KMP não substitui a UI nativa — ele compartilha a lógica de negócio enquanto cada plataforma mantém sua interface nativa.

Isso significa:

  • Android: UI com Jetpack Compose, lógica compartilhada em Kotlin
  • iOS: UI com SwiftUI, lógica compartilhada em Kotlin (compilado para framework nativo)
  • Web: UI com frameworks JS/Kotlin, lógica compartilhada
  • Backend: mesmos modelos e regras de negócio do app

Por que KMP e não Flutter/React Native?

Cada abordagem tem seus méritos, mas o KMP se destaca por:

  • UI nativa real: a interface de cada plataforma usa os componentes nativos
  • Adoção gradual: você pode integrar KMP em projetos existentes
  • Performance nativa: sem bridge, sem overhead de runtime
  • Flexibilidade: compartilhe só o que faz sentido — de 20% a 80% do código
  • Kotlin: uma das linguagens mais amadas pelos desenvolvedores

Estrutura de um projeto KMP

Um projeto KMP típico é organizado assim:

meu-app/
├── shared/                    # Módulo compartilhado
│   └── src/
│       ├── commonMain/        # Código comum a todas as plataformas
│       ├── commonTest/        # Testes compartilhados
│       ├── androidMain/       # Código específico Android
│       ├── iosMain/           # Código específico iOS
│       └── jvmMain/           # Código específico JVM (backend)
├── androidApp/                # Aplicativo Android
├── iosApp/                    # Aplicativo iOS (projeto Xcode)
└── build.gradle.kts

Configurando o projeto

O build.gradle.kts do módulo compartilhado:

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

kotlin {
    androidTarget()

    iosX64()
    iosArm64()
    iosSimulatorArm64()

    jvm()

    sourceSets {
        commonMain.dependencies {
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
            implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0")
            implementation("io.ktor:ktor-client-core:3.0.0")
            implementation("io.ktor:ktor-client-content-negotiation:3.0.0")
            implementation("io.ktor:ktor-serialization-kotlinx-json:3.0.0")
        }

        androidMain.dependencies {
            implementation("io.ktor:ktor-client-android:3.0.0")
        }

        iosMain.dependencies {
            implementation("io.ktor:ktor-client-darwin:3.0.0")
        }

        jvmMain.dependencies {
            implementation("io.ktor:ktor-client-cio:3.0.0")
        }
    }
}

Código compartilhado: modelos e regras de negócio

Tudo que vai em commonMain é compartilhado entre todas as plataformas:

// commonMain/kotlin/model/Produto.kt
@Serializable
data class Produto(
    val id: Long,
    val nome: String,
    val descricao: String,
    val preco: Double,
    val imagemUrl: String
)

// commonMain/kotlin/model/Carrinho.kt
data class Carrinho(
    val itens: List<ItemCarrinho> = emptyList()
) {
    val total: Double
        get() = itens.sumOf { it.produto.preco * it.quantidade }

    val quantidadeTotal: Int
        get() = itens.sumOf { it.quantidade }

    fun adicionar(produto: Produto, quantidade: Int = 1): Carrinho {
        val existente = itens.find { it.produto.id == produto.id }
        return if (existente != null) {
            copy(itens = itens.map {
                if (it.produto.id == produto.id) it.copy(quantidade = it.quantidade + quantidade)
                else it
            })
        } else {
            copy(itens = itens + ItemCarrinho(produto, quantidade))
        }
    }

    fun remover(produtoId: Long): Carrinho {
        return copy(itens = itens.filter { it.produto.id != produtoId })
    }
}

data class ItemCarrinho(val produto: Produto, val quantidade: Int)

Esse código roda igualzinho no Android, no iOS e no backend. Escreva uma vez, teste uma vez, use em todo lugar.

Chamadas de rede compartilhadas

Usando Ktor Client, as chamadas de API ficam no código comum:

// commonMain/kotlin/api/ProdutoApi.kt
class ProdutoApi(private val client: HttpClient) {

    suspend fun buscarProdutos(): List<Produto> {
        return client.get("https://api.meuapp.com.br/produtos")
            .body<List<Produto>>()
    }

    suspend fun buscarProduto(id: Long): Produto {
        return client.get("https://api.meuapp.com.br/produtos/$id")
            .body<Produto>()
    }
}

// commonMain/kotlin/api/HttpClientFactory.kt
fun criarHttpClient() = HttpClient {
    install(ContentNegotiation) {
        json(Json {
            ignoreUnknownKeys = true
            isLenient = true
        })
    }
}

expect/actual: código específico por plataforma

Quando você precisa de algo específico de cada plataforma, usa o mecanismo expect/actual:

// commonMain — declaração expect (como uma interface)
expect fun plataformaAtual(): String

expect class ArmazenamentoLocal {
    fun salvar(chave: String, valor: String)
    fun ler(chave: String): String?
}

// androidMain — implementação actual
actual fun plataformaAtual(): String = "Android ${Build.VERSION.SDK_INT}"

actual class ArmazenamentoLocal(context: Context) {
    private val prefs = context.getSharedPreferences("app", Context.MODE_PRIVATE)

    actual fun salvar(chave: String, valor: String) {
        prefs.edit().putString(chave, valor).apply()
    }

    actual fun ler(chave: String): String? = prefs.getString(chave, null)
}

// iosMain — implementação actual
actual fun plataformaAtual(): String = "iOS ${UIDevice.currentDevice.systemVersion}"

actual class ArmazenamentoLocal {
    private val defaults = NSUserDefaults.standardUserDefaults

    actual fun salvar(chave: String, valor: String) {
        defaults.setObject(valor, forKey = chave)
    }

    actual fun ler(chave: String): String? = defaults.stringForKey(chave)
}

ViewModel compartilhado

Com KMP, até a camada de ViewModel pode ser compartilhada:

// commonMain/kotlin/viewmodel/ProdutoListViewModel.kt
class ProdutoListViewModel(private val api: ProdutoApi) {
    private val _state = MutableStateFlow<ProdutoListState>(ProdutoListState.Carregando)
    val state: StateFlow<ProdutoListState> = _state.asStateFlow()

    private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())

    fun carregar() {
        scope.launch {
            _state.value = ProdutoListState.Carregando
            try {
                val produtos = api.buscarProdutos()
                _state.value = ProdutoListState.Sucesso(produtos)
            } catch (e: Exception) {
                _state.value = ProdutoListState.Erro(e.message ?: "Erro desconhecido")
            }
        }
    }

    fun limpar() { scope.cancel() }
}

sealed class ProdutoListState {
    data object Carregando : ProdutoListState()
    data class Sucesso(val produtos: List<Produto>) : ProdutoListState()
    data class Erro(val mensagem: String) : ProdutoListState()
}

Usando no iOS (SwiftUI)

O código Kotlin compartilhado é acessado no Swift de forma transparente:

// Exemplo de como fica no SwiftUI (pseudo-código para contexto):
// struct ProdutoListView: View {
//     @StateObject var viewModel = ProdutoListViewModel(api: ProdutoApi(...))
//
//     var body: some View {
//         switch viewModel.state {
//         case .carregando: ProgressView()
//         case .sucesso(let produtos): List(produtos) { ... }
//         case .erro(let msg): Text(msg)
//         }
//     }
// }

Compose Multiplatform

Para quem quer compartilhar até a UI, existe o Compose Multiplatform — Jetpack Compose que roda em Android, iOS, Desktop e Web:

@Composable
fun ProdutoCard(produto: Produto, onClicar: () -> Unit) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(8.dp)
            .clickable { onClicar() }
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(produto.nome, style = MaterialTheme.typography.titleMedium)
            Text(produto.descricao, style = MaterialTheme.typography.bodyMedium)
            Text(
                "R$ ${"%.2f".format(produto.preco)}",
                style = MaterialTheme.typography.titleLarge,
                color = MaterialTheme.colorScheme.primary
            )
        }
    }
}

Esse composable roda em todas as plataformas!

Conclusão

Kotlin Multiplatform é a aposta mais promissora para compartilhamento de código entre plataformas em 2026. A abordagem incremental, o respeito pela UI nativa e a maturidade crescente do ecossistema fazem do KMP uma escolha cada vez mais segura para times de todos os tamanhos.

Comece pelo módulo de modelos e regras de negócio, vá expandindo aos poucos, e veja a produtividade do seu time decolar. O futuro é multiplataforma — e é em Kotlin!