---
title: "Kotlin Multiplatform Tutorial em Português — Passo a Passo | Kotlin Brasil"
url: "https://kotlin.dev.br/tutoriais/kotlin-multiplatform-tutorial/"
markdown_url: "https://kotlin.dev.br/tutoriais/kotlin-multiplatform-tutorial.MD"
description: "Aprenda Kotlin Multiplatform (KMP): compartilhe código entre Android, iOS e web. Tutorial com expect/actual, Compose Multiplatform e Ktor Client."
date: "2025-07-25"
author: "Karina Melo"
---

# Kotlin Multiplatform Tutorial em Português — Passo a Passo | Kotlin Brasil

Aprenda Kotlin Multiplatform (KMP): compartilhe código entre Android, iOS e web. Tutorial com expect/actual, Compose Multiplatform e Ktor Client.


**Kotlin Multiplatform** (KMP) permite compartilhar lógica de negócio entre Android, iOS, desktop e web usando uma única base de código Kotlin. Neste tutorial, vamos configurar um projeto KMP do zero, entender o mecanismo `expect`/`actual`, criar um módulo compartilhado, integrar com Compose Multiplatform para UI, usar Ktor Client para requisições HTTP e configurar injeção de dependências com Koin.

## O que é Kotlin Multiplatform?

Diferente de soluções como Flutter ou React Native que substituem completamente a UI nativa, o KMP foca em compartilhar a **lógica de negócio** — modelos de dados, networking, válidações, regras de negócio — enquanto permite que cada plataforma use sua própria tecnologia de interface. Com a chegada do Compose Multiplatform, agora também é possível compartilhar a camada de UI entre Android, iOS e desktop.

O KMP é uma tecnologia estável da JetBrains, já usada em produção por empresas como Netflix, Philips, VMWare e Cash App. A ideia central é simples: escreva código Kotlin no módulo `commonMain` e ele compila para JVM (Android), nativo (iOS via Kotlin/Native) e JavaScript (web).

## Passo 1: Configuração do Projeto

A forma mais prática de criar um projeto KMP é usando o wizard em [kmp.jetbrains.com](https://kmp.jetbrains.com). Para entender a fundo, vamos ver a configuração manual. A estrutura de diretórios fica assim:

```
meu-projeto-kmp/
├── shared/
│   └── src/
│       ├── commonMain/kotlin/    # Código compartilhado
│       ├── commonTest/kotlin/    # Testes compartilhados
│       ├── androidMain/kotlin/   # Código Android-especifico
│       └── iosMain/kotlin/       # Código iOS-especifico
├── androidApp/                   # Aplicação Android
├── iosApp/                       # Aplicação iOS (Xcode)
└── build.gradle.kts
```

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

```kotlin
// shared/build.gradle.kts
plugins {
    kotlin("multiplatform")
    id("com.android.library")
    kotlin("plugin.serialization")
}

kotlin {
    androidTarget {
        compilations.all {
            compileTaskProvider.configure {
                compilerOptions {
                    jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
                }
            }
        }
    }

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

    sourceSets {
        commonMain.dependencies {
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
            implementation("io.ktor:ktor-client-core:2.3.12")
            implementation("io.ktor:ktor-client-content-negotiation:2.3.12")
            implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.12")
            implementation("io.insert-koin:koin-core:3.5.6")
        }
        androidMain.dependencies {
            implementation("io.ktor:ktor-client-okhttp:2.3.12")
        }
        iosMain.dependencies {
            implementation("io.ktor:ktor-client-darwin:2.3.12")
        }
    }
}

android {
    namespace = "com.exemplo.shared"
    compileSdk = 34
    defaultConfig {
        minSdk = 24
    }
}
```

Note como as dependências são organizadas por source set: `commonMain` para código compartilhado, `androidMain` e `iosMain` para implementações específicas de plataforma. O Ktor Client, por exemplo, usa OkHttp no Android e Darwin no iOS.

## Passo 2: Mecanismo expect/actual

O `expect`/`actual` é o recurso que permite declarar uma API no código comum e fornecer implementações específicas por plataforma. Pense como uma [interface](/glossario/interface/) resolvida em tempo de compilação.

```kotlin
// commonMain/kotlin/Platform.kt
expect class Platform() {
    val nome: String
    val versao: String
}

expect fun obterTimestampAtual(): Long
```

```kotlin
// androidMain/kotlin/Platform.android.kt
actual class Platform actual constructor() {
    actual val nome: String = "Android ${android.os.Build.VERSION.SDK_INT}"
    actual val versao: String = android.os.Build.VERSION.RELEASE
}

actual fun obterTimestampAtual(): Long = System.currentTimeMillis()
```

```kotlin
// iosMain/kotlin/Platform.ios.kt
import platform.UIKit.UIDevice
import platform.Foundation.NSDate
import platform.Foundation.timeIntervalSince1970

actual class Platform actual constructor() {
    actual val nome: String = UIDevice.currentDevice.systemName()
    actual val versao: String = UIDevice.currentDevice.systemVersion
}

actual fun obterTimestampAtual(): Long =
    (NSDate().timeIntervalSince1970 * 1000).toLong()
```

No código comum, use `Platform()` normalmente — o compilador escolhe a implementação correta para cada target. O ideal é minimizar o uso de `expect`/`actual` e mover a maior parte da lógica para `commonMain`, usando-o apenas quando realmente precisa de APIs nativas.

## Passo 3: Módulo Compartilhado com Lógica de Negócio

Vamos criar um módulo compartilhado que busca dados de uma API e aplica regras de negócio:

```kotlin
// commonMain/kotlin/modelo/Produto.kt
import kotlinx.serialization.Serializable

@Serializable
data class Produto(
    val id: Int,
    val nome: String,
    val preco: Double,
    val categoria: String
)

@Serializable
data class RespostaApi<T>(
    val dados: List<T>,
    val total: Int
)
```

```kotlin
// commonMain/kotlin/repositorio/ProdutoRepositorio.kt
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*

class ProdutoRepositorio(private val client: HttpClient) {
    suspend fun buscarProdutos(): List<Produto> {
        val resposta: RespostaApi<Produto> = client.get("https://api.exemplo.com/produtos").body()
        return resposta.dados
    }

    suspend fun buscarPorCategoria(categoria: String): List<Produto> {
        return buscarProdutos().filter { it.categoria == categoria }
    }
}
```

```kotlin
// commonMain/kotlin/usecase/ListarProdutosUseCase.kt
class ListarProdutosUseCase(private val repositorio: ProdutoRepositorio) {
    suspend fun executar(filtroPrecoMaximo: Double? = null): List<Produto> {
        val produtos = repositorio.buscarProdutos()
        return if (filtroPrecoMaximo != null) {
            produtos.filter { it.preco <= filtroPrecoMaximo }
                .sortedBy { it.preco }
        } else {
            produtos.sortedBy { it.nome }
        }
    }
}
```

Toda essa lógica compila para Android, iOS e qualquer outro target. As [data classes](/glossario/data-class/) com `@Serializable` funcionam em todas as plataformas graças ao `kotlinx.serialization`, que gera código de serialização em tempo de compilação.

## Passo 4: Compose Multiplatform para UI Compartilhada

Com o **Compose Multiplatform**, você pode compartilhar também a camada de UI entre Android, iOS e desktop usando a mesma API do Jetpack Compose:

```kotlin
// commonMain/kotlin/ui/TelaProdutos.kt
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun TelaProdutos(viewModel: ProdutosViewModel) {
    val estado by viewModel.estado.collectAsState()

    LaunchedEffect(Unit) {
        viewModel.carregarProdutos()
    }

    Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
        Text(
            text = "Produtos",
            style = MaterialTheme.typography.headlineMedium
        )
        Spacer(modifier = Modifier.height(16.dp))

        when {
            estado.carregando -> CircularProgressIndicator()
            estado.erro != null -> Text("Erro: ${estado.erro}", color = MaterialTheme.colorScheme.error)
            else -> LazyColumn {
                items(estado.produtos) { produto ->
                    CartaoProduto(produto)
                }
            }
        }
    }
}

@Composable
fun CartaoProduto(produto: Produto) {
    Card(
        modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(text = produto.nome, style = MaterialTheme.typography.titleMedium)
            Text(text = "R$ ${"%.2f".format(produto.preco)}")
            Text(text = produto.categoria, style = MaterialTheme.typography.bodySmall)
        }
    }
}
```

O Compose Multiplatform usa exatamente a mesma API do Jetpack Compose do Android. O compilador gera renderização nativa para cada plataforma — Skia para iOS e desktop, Android Canvas para Android.

## Passo 5: Ktor Client Multiplatform

A configuração do Ktor Client no código comum com engines específicas por plataforma:

```kotlin
// commonMain/kotlin/rede/HttpClientFactory.kt
import io.ktor.client.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json

expect fun criarHttpClient(): HttpClient

fun criarHttpClientComum(engine: HttpClientEngine): HttpClient {
    return HttpClient(engine) {
        install(ContentNegotiation) {
            json(Json {
                prettyPrint = true
                isLenient = true
                ignoreUnknownKeys = true
            })
        }
    }
}
```

```kotlin
// androidMain/kotlin/rede/HttpClientFactory.android.kt
import io.ktor.client.engine.okhttp.*

actual fun criarHttpClient(): HttpClient = criarHttpClientComum(OkHttp.create())
```

```kotlin
// iosMain/kotlin/rede/HttpClientFactory.ios.kt
import io.ktor.client.engine.darwin.*

actual fun criarHttpClient(): HttpClient = criarHttpClientComum(Darwin.create())
```

## Passo 6: Injeção de Dependências com Koin

O **Koin** é um framework de DI leve que funciona nativamente com KMP, sem geração de código nem reflection pesada:

```kotlin
// commonMain/kotlin/di/Modulos.kt
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module

val moduloCompartilhado = module {
    single { criarHttpClient() }
    singleOf(::ProdutoRepositorio)
    singleOf(::ListarProdutosUseCase)
    factory { ProdutosViewModel(get()) }
}
```

```kotlin
// androidMain: inicializacao no Application
class MeuApp : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidContext(this@MeuApp)
            modules(moduloCompartilhado)
        }
    }
}
```

```kotlin
// iosMain: inicializacao para Swift
fun inicializarKoin() {
    startKoin {
        modules(moduloCompartilhado)
    }
}
```

No lado iOS (Swift), chame `IosMainKt.inicializarKoin()` no `AppDelegate`. O Koin gerencia o grafo de dependências de forma idêntica em ambas as plataformas.

## Erros Comuns

**1. Usar APIs específicas de plataforma no `commonMain`:** O código em `commonMain` só pode usar a biblioteca padrão do Kotlin e dependências multiplatform. Se precisar de `java.io.File`, use `expect`/`actual` ou a biblioteca `okio` que é multiplatform.

**2. Esquecer de adicionar todos os targets iOS:** Sempre inclua `iosX64()` (simulador Intel), `iosArm64()` (dispositivo real) e `iosSimulatorArm64()` (simulador Apple Silicon). Omitir qualquer um causa erros confusos ao compilar.

**3. Serialização não funciona:** O `kotlinx.serialization` exige o plugin de compilador `kotlin("plugin.serialization")`. Sem ele, a annotation `@Serializable` não gera o serializer e você recebe erros em runtime.

**4. [Coroutines](/glossario/coroutine/) no iOS congelando:** No Kotlin/Native, o modelo de memória antigo tinha restrições com coroutines. Use Kotlin 1.9+ que traz o **novo modelo de memória** por padrão, eliminando esse problema.

**5. Dependências com versões incompatíveis:** No KMP, todas as dependências devem suportar seus targets. Verifique no repositório da biblioteca se ela publica artefatos para `iosArm64`, `iosX64`, etc. Use o [Kotlin Multiplatform Compatibility Guide](https://kotlinlang.org/docs/multiplatform-compatibility-guide.html) como referência.

## Conclusão e Próximos Passos

Neste tutorial, construímos um projeto Kotlin Multiplatform completo com módulo compartilhado, mecanismo `expect`/`actual`, UI com Compose Multiplatform, networking com Ktor Client e DI com Koin. O KMP representa o futuro do desenvolvimento multiplataforma, permitindo reaproveitar lógica crítica sem sacrificar a experiência nativa de cada plataforma.

Como próximos passos, explore [SQLDelight para persistência local multiplatform](/blog/sqldelight-kotlin-multiplatform-android-ios-2026/), **Napier** para logging multiplataforma, e **SKIE** para melhorar a interoperabilidade Swift-Kotlin. Para transformar esse conhecimento em plano profissional, leia também o guia de [desenvolvedor Kotlin Multiplatform](/carreira/desenvolvedor-kotlin-multiplatform/). Para aprofundar seus conhecimentos em coroutines compartilhadas, confira nosso tutorial sobre [Coroutines Avançadas](/tutoriais/coroutines-avançado/) e o guia [Kotlin para Backend](/guias/kotlin-para-backend/) onde exploramos o Ktor em mais detalhes.
