Compartilhar lógica de negócio entre plataformas com Kotlin Multiplatform já era realidade. Mas compartilhar a interface gráfica sempre foi o maior desafio. O Compose Multiplatform (CMP) da JetBrains muda esse cenário ao permitir que você escreva UI em Kotlin uma vez e rode no Android, iOS, Desktop e Web. Em 2026, com a estabilização do suporte a iOS e melhorias em todas as frentes, CMP se tornou uma alternativa séria para projetos multiplataforma.

O que é Compose Multiplatform?

Compose Multiplatform é um framework de UI declarativo da JetBrains construído sobre o Jetpack Compose do Google. A ideia é simples: se o Jetpack Compose funciona perfeitamente para Android, por que não levar o mesmo modelo para outras plataformas?

CMP usa o mecanismo de renderização Skia (a mesma engine do Chrome e do Flutter) para desenhar a UI de forma consistente em todas as plataformas. Isso significa que seus Composables rodam nativamente em:

PlataformaStatus em 2026Renderização
AndroidEstável (produção)Nativo via Jetpack Compose
iOSBeta avançadoSkia + UIKit integration
Desktop (JVM)Estável (produção)Skia via JVM
Web (Wasm)Alpha/ExperimentalSkia via WebAssembly

Se você já trabalhou com Compose para Desktop, CMP leva essa experiência ao próximo nível ao unificar todos os targets.

Configurando um Projeto CMP

A maneira mais rápida de começar é com o Kotlin Multiplatform Wizard da JetBrains (kmp.jetbrains.com). Mas vamos entender a estrutura manualmente.

Estrutura do Projeto

meu-app-cmp/
├── composeApp/
│   ├── src/
│   │   ├── commonMain/       # UI e lógica compartilhada
│   │   ├── androidMain/      # Código Android específico
│   │   ├── iosMain/          # Código iOS específico
│   │   └── desktopMain/      # Código Desktop específico
│   └── build.gradle.kts
├── iosApp/                    # Projeto Xcode wrapper
├── build.gradle.kts
└── settings.gradle.kts

Configuração do Gradle

// composeApp/build.gradle.kts
plugins {
    kotlin("multiplatform")
    id("org.jetbrains.compose")
    id("org.jetbrains.kotlin.plugin.compose")
}

kotlin {
    androidTarget()

    jvm("desktop")

    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach { target ->
        target.binaries.framework {
            baseName = "ComposeApp"
            isStatic = true
        }
    }

    sourceSets {
        commonMain.dependencies {
            implementation(compose.runtime)
            implementation(compose.foundation)
            implementation(compose.material3)
            implementation(compose.ui)
            implementation(compose.components.resources)
        }

        androidMain.dependencies {
            implementation(compose.preview)
            implementation("androidx.activity:activity-compose:1.9.0")
        }

        val desktopMain by getting {
            dependencies {
                implementation(compose.desktop.currentOs)
            }
        }
    }
}

Compartilhando UI entre Plataformas

O coração do CMP é o source set commonMain. Todo código colocado aqui roda em todas as plataformas. Vamos criar uma tela de perfil de usuário compartilhada:

// commonMain/kotlin/ui/PerfilScreen.kt
@Composable
fun PerfilScreen(usuario: Usuario) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(24.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        // Avatar circular
        Box(
            modifier = Modifier
                .size(120.dp)
                .clip(CircleShape)
                .background(MaterialTheme.colorScheme.primaryContainer),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = usuario.nome.first().toString(),
                style = MaterialTheme.typography.headlineLarge,
                color = MaterialTheme.colorScheme.onPrimaryContainer
            )
        }

        Spacer(modifier = Modifier.height(16.dp))

        Text(
            text = usuario.nome,
            style = MaterialTheme.typography.headlineMedium
        )

        Text(
            text = usuario.email,
            style = MaterialTheme.typography.bodyLarge,
            color = MaterialTheme.colorScheme.onSurfaceVariant
        )

        Spacer(modifier = Modifier.height(32.dp))

        // Lista de informações
        InfoCard("Cargo", usuario.cargo)
        InfoCard("Localização", usuario.cidade)
        InfoCard("Membro desde", usuario.membroDesde)
    }
}

@Composable
fun InfoCard(label: String, valor: String) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(vertical = 4.dp)
    ) {
        Row(
            modifier = Modifier.padding(16.dp),
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text(
                text = label,
                style = MaterialTheme.typography.labelLarge,
                color = MaterialTheme.colorScheme.onSurfaceVariant
            )
            Text(
                text = valor,
                style = MaterialTheme.typography.bodyLarge
            )
        }
    }
}

Esse código roda exatamente igual no Android, iOS e Desktop. Sem adaptações, sem #ifdef, sem código duplicado.

Código Específico por Plataforma com expect/actual

Nem tudo pode ser compartilhado. Para funcionalidades específicas de cada plataforma, o KMP oferece o mecanismo expect/actual:

// commonMain — declaração expect
expect fun compartilharTexto(texto: String)

expect fun obterVersaoApp(): String
// androidMain — implementação actual
actual fun compartilharTexto(texto: String) {
    val intent = Intent(Intent.ACTION_SEND).apply {
        type = "text/plain"
        putExtra(Intent.EXTRA_TEXT, texto)
    }
    context.startActivity(Intent.createChooser(intent, "Compartilhar"))
}

actual fun obterVersaoApp(): String {
    return BuildConfig.VERSION_NAME
}
// iosMain — implementação actual
actual fun compartilharTexto(texto: String) {
    val activityController = UIActivityViewController(
        activityItems = listOf(texto),
        applicationActivities = null
    )
    UIApplication.sharedApplication.keyWindow?.rootViewController
        ?.presentViewController(activityController, true, null)
}

actual fun obterVersaoApp(): String {
    return NSBundle.mainBundle.infoDictionary
        ?.get("CFBundleShortVersionString") as? String ?: "1.0"
}

O compilador garante que toda declaração expect tenha sua contrapartida actual em cada target. Sem actual, o build falha — segurança em tempo de compilação.

Navegação é um dos pontos que mais evoluiu em 2026. A biblioteca oficial Compose Navigation agora suporta KMP nativamente:

// commonMain
@Composable
fun AppNavigation() {
    val navController = rememberNavController()

    NavHost(
        navController = navController,
        startDestination = "home"
    ) {
        composable("home") {
            HomeScreen(
                onPerfilClick = { navController.navigate("perfil/$it") },
                onConfigClick = { navController.navigate("configuracoes") }
            )
        }

        composable("perfil/{userId}") { backStackEntry ->
            val userId = backStackEntry.arguments?.getString("userId") ?: return@composable
            PerfilScreen(
                userId = userId,
                onVoltar = { navController.popBackStack() }
            )
        }

        composable("configuracoes") {
            ConfiguracoesScreen(
                onVoltar = { navController.popBackStack() }
            )
        }
    }
}

Alternativas populares para navegação multiplataforma incluem:

  • Voyager — API simples e intuitiva, inspirada no Jetpack Navigation
  • Decompose — arquitetura baseada em componentes, boa para apps complexos
  • Appyx — navegação com animações avançadas

Gerenciamento de Estado

Para gerenciamento de estado, CMP funciona com as mesmas ferramentas do Compose:

// ViewModel compartilhado usando kotlinx-coroutines e Flow
class PerfilViewModel(
    private val repository: UsuarioRepository
) {
    private val _uiState = MutableStateFlow<PerfilUiState>(PerfilUiState.Carregando)
    val uiState: StateFlow<PerfilUiState> = _uiState.asStateFlow()

    fun carregarPerfil(userId: String) {
        CoroutineScope(Dispatchers.Default).launch {
            _uiState.value = PerfilUiState.Carregando
            try {
                val usuario = repository.buscarUsuario(userId)
                _uiState.value = PerfilUiState.Sucesso(usuario)
            } catch (e: Exception) {
                _uiState.value = PerfilUiState.Erro(e.message ?: "Erro desconhecido")
            }
        }
    }
}

sealed class PerfilUiState {
    data object Carregando : PerfilUiState()
    data class Sucesso(val usuario: Usuario) : PerfilUiState()
    data class Erro(val mensagem: String) : PerfilUiState()
}

Se você já trabalha com Coroutines e Flow no Android, a transição para CMP é natural. Confira nosso guia completo de Coroutines para dominar esses conceitos.

Limitações e Considerações

CMP amadureceu muito, mas ainda tem limitações que você precisa considerar:

iOS ainda em Beta

O suporte a iOS avançou significativamente, mas ainda é beta. Widgets de sistema, deep links avançados e algumas APIs do UIKit exigem código nativo. Para apps em produção no iOS, teste extensivamente.

Performance no iOS

A renderização via Skia no iOS tem overhead comparado a SwiftUI nativo. Para a maioria dos apps, a diferença é imperceptível. Para apps com animações pesadas ou listas muito longas, monitore o desempenho.

Tamanho do binário

Apps CMP tendem a ter binários maiores do que apps nativos puros, pois incluem o runtime do Skia. No Android, o impacto é menor (~2-3 MB extra). No iOS, pode chegar a ~10-15 MB adicionais.

Ecossistema de bibliotecas

Nem todas as bibliotecas do Jetpack Compose estão disponíveis para KMP. Antes de adotar CMP, verifique se suas dependências-chave suportam multiplataforma.

CMP vs Flutter vs React Native

Uma comparação inevitável. Cada framework tem seus pontos fortes:

AspectoCompose MultiplatformFlutterReact Native
LinguagemKotlinDartJavaScript/TypeScript
RenderizaçãoSkiaSkia (Impeller)Componentes nativos
Código compartilhadoUI + LógicaUI + LógicaUI + Lógica
Integração nativaExcelente (KMP)Boa (Platform Channels)Boa (Native Modules)
Curva de aprendizadoBaixa (se sabe Kotlin)MédiaBaixa (se sabe JS)
MaturidadeEm crescimentoMaduraMadura

Se sua equipe já domina Kotlin e tem um app Android com Jetpack Compose, CMP é a escolha mais natural — o código existente pode ser reaproveitado. Confira nossa comparação detalhada entre KMP e Flutter e entre KMP e React Native.

Para quem vem de outras linguagens, vale conhecer como cada ecossistema resolve o desafio de multiplataforma. Go também tem propostas de UI multiplataforma como Gio, embora com foco diferente. Já Python investe em frameworks como Kivy e BeeWare para o mesmo objetivo.

Exemplo Completo: App de Tarefas

Para consolidar, veja uma tela funcional de lista de tarefas compartilhada:

@Composable
fun TarefasScreen(viewModel: TarefasViewModel) {
    val tarefas by viewModel.tarefas.collectAsState()
    var novaTarefa by remember { mutableStateOf("") }

    Scaffold(
        topBar = {
            TopAppBar(title = { Text("Minhas Tarefas") })
        }
    ) { padding ->
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(padding)
                .padding(16.dp)
        ) {
            // Campo de nova tarefa
            Row(
                modifier = Modifier.fillMaxWidth(),
                verticalAlignment = Alignment.CenterVertically
            ) {
                OutlinedTextField(
                    value = novaTarefa,
                    onValueChange = { novaTarefa = it },
                    modifier = Modifier.weight(1f),
                    placeholder = { Text("Nova tarefa...") },
                    singleLine = true
                )
                Spacer(modifier = Modifier.width(8.dp))
                IconButton(
                    onClick = {
                        if (novaTarefa.isNotBlank()) {
                            viewModel.adicionar(novaTarefa)
                            novaTarefa = ""
                        }
                    }
                ) {
                    Icon(Icons.Default.Add, "Adicionar")
                }
            }

            Spacer(modifier = Modifier.height(16.dp))

            // Lista de tarefas
            LazyColumn {
                items(tarefas) { tarefa ->
                    TarefaItem(
                        tarefa = tarefa,
                        onToggle = { viewModel.alternarConclusao(tarefa.id) },
                        onRemover = { viewModel.remover(tarefa.id) }
                    )
                }
            }
        }
    }
}

Conclusão

Compose Multiplatform em 2026 é uma tecnologia madura o suficiente para projetos reais — especialmente se sua equipe já trabalha com Kotlin e Jetpack Compose. O compartilhamento de UI entre Android, iOS e Desktop reduz significativamente o esforço de desenvolvimento e manutenção.

Para começar, recomendo explorar nosso guia de Kotlin Multiplatform Mobile e o artigo sobre o futuro do KMP. Se você quer entender a base do Compose, confira o guia de Jetpack Compose.

O ecossistema Kotlin continua evoluindo — e o CMP é uma das peças mais empolgantes desse quebra-cabeça.

Se você busca performance nativa sem garbage collector para componentes críticos do seu app multiplataforma, vale explorar como Rust aborda interoperabilidade com mobile e WebAssembly.