Kotlin Multiplatform Mobile, agora oficialmente chamado apenas de Kotlin Multiplatform (KMP), permite compartilhar logica de negocio entre Android e iOS usando Kotlin. Diferente de frameworks cross-platform que compartilham tambem a UI, como Flutter ou React Native, o KMP adota uma abordagem pragmatica: voce compartilha o codigo que faz sentido compartilhar – logica de negocio, acesso a dados, validacoes – e mantem a interface nativa de cada plataforma. Neste guia, vamos configurar um projeto KMP do zero, entender a arquitetura, implementar modulos compartilhados e explorar as melhores praticas para projetos reais.
Por Que Kotlin Multiplatform
A proposta do KMP e diferente de outras solucoes cross-platform. Enquanto Flutter substitui a UI nativa por seu proprio engine de renderizacao e React Native usa uma ponte entre JavaScript e componentes nativos, o KMP compila diretamente para JVM no Android e para codigo nativo via Kotlin/Native no iOS. Isso significa performance nativa em ambas as plataformas sem camadas de abstracao adicionais.
As vantagens incluem compartilhamento gradual de codigo, integracao total com projetos existentes e a possibilidade de manter equipes Android e iOS trabalhando com suas ferramentas nativas para a UI.
Configurando o Projeto
O Kotlin Multiplatform Wizard (disponivel em kmp.jetbrains.com) gera a estrutura inicial. Um projeto KMP tipico possui tres modulos principais:
// settings.gradle.kts
rootProject.name = "MeuAppKMP"
include(":androidApp")
include(":iosApp")
include(":shared")
// shared/build.gradle.kts
plugins {
kotlin("multiplatform")
id("com.android.library")
kotlin("plugin.serialization")
}
kotlin {
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = "17"
}
}
}
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach {
it.binaries.framework {
baseName = "shared"
isStatic = true
}
}
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")
implementation("io.ktor:ktor-client-core:2.3.7")
implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
}
}
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-android:2.3.7")
}
}
val iosMain by getting {
dependencies {
implementation("io.ktor:ktor-client-darwin:2.3.7")
}
}
}
}
Estrutura de Source Sets
O KMP utiliza source sets para organizar codigo compartilhado e codigo especifico de cada plataforma:
// shared/src/commonMain/kotlin/
// Codigo Kotlin puro, compartilhado entre todas as plataformas
// shared/src/androidMain/kotlin/
// Codigo especifico do Android (pode usar APIs do Android SDK)
// shared/src/iosMain/kotlin/
// Codigo especifico do iOS (pode usar APIs do iOS via interop)
Expect e Actual
O mecanismo expect/actual permite declarar APIs no codigo comum e implementa-las em cada plataforma:
// commonMain - Declaracao
expect class PlatformInfo() {
val nome: String
val versao: String
}
// androidMain - Implementacao Android
actual class PlatformInfo actual constructor() {
actual val nome: String = "Android"
actual val versao: String = "${android.os.Build.VERSION.SDK_INT}"
}
// iosMain - Implementacao iOS
actual class PlatformInfo actual constructor() {
actual val nome: String = UIDevice.currentDevice.systemName()
actual val versao: String = UIDevice.currentDevice.systemVersion
}
Outro exemplo pratico com armazenamento local:
// commonMain
expect class KeyValueStorage {
fun getString(key: String, default: String = ""): String
fun putString(key: String, value: String)
fun clear()
}
// androidMain
actual class KeyValueStorage(private val context: Context) {
private val prefs = context.getSharedPreferences(
"app_prefs", Context.MODE_PRIVATE
)
actual fun getString(key: String, default: String): String {
return prefs.getString(key, default) ?: default
}
actual fun putString(key: String, value: String) {
prefs.edit().putString(key, value).apply()
}
actual fun clear() {
prefs.edit().clear().apply()
}
}
// iosMain
actual class KeyValueStorage {
private val defaults = NSUserDefaults.standardUserDefaults
actual fun getString(key: String, default: String): String {
return defaults.stringForKey(key) ?: default
}
actual fun putString(key: String, value: String) {
defaults.setObject(value, forKey = key)
}
actual fun clear() {
val dicionario = defaults.dictionaryRepresentation()
for (key in dicionario.keys) {
defaults.removeObjectForKey(key as String)
}
}
}
Networking Compartilhado com Ktor
O Ktor Client funciona em ambas as plataformas, permitindo compartilhar toda a camada de rede:
// commonMain
class ApiClient {
private val httpClient = HttpClient {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
prettyPrint = true
})
}
}
suspend fun buscarProdutos(): List<ProdutoDto> {
return httpClient.get("https://api.exemplo.com/produtos")
.body()
}
suspend fun buscarProduto(id: Long): ProdutoDto {
return httpClient.get("https://api.exemplo.com/produtos/$id")
.body()
}
}
@Serializable
data class ProdutoDto(
val id: Long,
val nome: String,
val preco: Double,
val descricao: String
)
Repository Compartilhado
O padrao Repository funciona perfeitamente no modulo compartilhado:
// commonMain
class ProdutoRepository(
private val apiClient: ApiClient
) {
private val _produtos = MutableStateFlow<List<ProdutoDto>>(emptyList())
val produtos: StateFlow<List<ProdutoDto>> = _produtos.asStateFlow()
suspend fun carregarProdutos(): Result<List<ProdutoDto>> {
return try {
val resultado = apiClient.buscarProdutos()
_produtos.value = resultado
Result.success(resultado)
} catch (e: Exception) {
Result.failure(e)
}
}
}
Integracao com o App Android
No Android, o modulo shared e consumido como uma dependencia normal:
// androidApp/build.gradle.kts
dependencies {
implementation(project(":shared"))
}
// Uso no Android
class ProdutoViewModel(
private val repository: ProdutoRepository
) : ViewModel() {
val produtos = repository.produtos
.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
}
Integracao com o App iOS
No iOS, o framework gerado e importado no Swift:
// No Xcode / Swift
import shared
class ProdutoViewModel: ObservableObject {
@Published var produtos: [ProdutoDto] = []
private let repository = ProdutoRepository(
apiClient: ApiClient()
)
func carregarProdutos() {
Task {
let resultado = try await repository.carregarProdutos()
// Atualizar estado
}
}
}
Boas Praticas com Kotlin Multiplatform
- Compartilhe logica, nao UI: mantenha a interface nativa para melhor experiencia do usuario em cada plataforma.
- Use interfaces no codigo comum: defina contratos com interfaces e implemente com
expect/actualapenas quando necessario. - Prefira bibliotecas multiplatform: Ktor, kotlinx-serialization, SQLDelight e Koin possuem suporte KMP nativo.
- Teste no commonTest: escreva testes unitarios no source set compartilhado para maxima cobertura entre plataformas.
- Adocao incremental: comece compartilhando uma camada pequena (modelos de dados, por exemplo) e expanda gradualmente.
- Mantenha o modulo shared leve: evite dependencias pesadas que aumentem o tamanho do framework iOS.
Erros Comuns e Armadilhas
- Congelar objetos no Kotlin/Native: versoes mais antigas exigiam que objetos compartilhados entre threads fossem “frozen”. A partir do novo gerenciador de memoria, isso nao e mais necessario, mas bibliotecas antigas podem ainda ter essa restricao.
- Coroutines no iOS: o Swift nao entende
suspendnativamente. Use wrappers como SKIE ou KMP-NativeCoroutines para expor funcoes suspensas como Swift async/await. - Build times longos: projetos KMP podem ter builds mais demorados. Configure caching adequado e builds incrementais.
- Ignorar diferencias de plataforma: nem toda funcionalidade pode ou deve ser compartilhada. Permissoes, notificacoes push e acesso a sensores geralmente sao melhor tratados nativamente.
- Dependencias transitivas: bibliotecas Java puras nao funcionam no iOS. Certifique-se de que todas as dependencias do commonMain sejam multiplatform.
Conclusao e Proximos Passos
O Kotlin Multiplatform oferece uma abordagem equilibrada para o desenvolvimento multiplataforma, combinando compartilhamento de codigo com interfaces nativas. A tecnologia amadureceu significativamente e ja e usada em producao por empresas como Netflix, Philips e Cash App. Para aprofundar seus conhecimentos, explore o Compose Multiplatform para compartilhar tambem a UI, estude SQLDelight para persistencia multiplatform e consulte os demais guias sobre arquitetura e testes aqui no Kotlin Brasil.