expect/actual — essência, palavras-chave do KMM e como funcionam

Autor: IT Sectr Publicado: 2026-06-05 Tempo de leitura: 8 min

expect/actual é um mecanismo do Kotlin Multiplatform que permite declarar APIs dependentes de plataforma em código comum. A palavra-chave expect cria um contrato de função, classe ou propriedade no commonMain, enquanto a palavra-chave actual fornece uma implementação concreta para cada plataforma. O compilador verifica se cada declaração expect possui uma implementação actual correspondente em todas as plataformas alvo. Segundo a JetBrains, 2025, este mecanismo é usado em 80% dos projetos KMM para implementar lógica de negócio multiplataforma.

Principais pontos

  • expect é a palavra-chave para declarar um contrato de função, classe ou propriedade em código comum.
  • actual é a palavra-chave para fornecer uma implementação específica de plataforma para uma declaração expect.
  • commonMain é o source set com código comum onde as declarações expect são colocadas.
  • Verificação do compilador — o compilador garante que existem implementações actual para todas as plataformas alvo.
  • Source set — conjuntos (iosMain, androidMain) onde residem as implementações actual específicas de plataforma.

O que é expect/actual?

expect/actual é um mecanismo declarativo do Kotlin Multiplatform para implementar programação orientada à plataforma. Ele permite descrever uma API uma vez no módulo comum (expect) e implementá-la separadamente para cada plataforma (actual). Ao contrário das interfaces, expect/actual não cria chamadas virtuais — o compilador vincula as declarações expect e actual em tempo de compilação, eliminando a sobrecarga do despacho dinâmico.

A história do expect/actual começou com a introdução do Kotlin Multiplatform em 2017. Inicialmente, o mecanismo era chamado de expect/actual declarations e era experimental. No Kotlin 1.2, foram adicionadas anotações expect, e no Kotlin 1.3, o expect/actual tornou-se estável para classes e funções. Com o tempo, o mecanismo foi expandido: Kotlin 1.6 adicionou suporte para expect/actual em objetos acompanhantes, Kotlin 1.7 para classes enum e Kotlin 2.0 para typealias.

A característica chave do expect/actual é a segurança em tempo de compilação. Se um desenvolvedor adicionar uma declaração expect no commonMain mas esquecer de fornecer uma implementação actual para iOS, o compilador gerará um erro. Isso evita falhas em tempo de execução comuns em abordagens que usam reflexão ou carregamento dinâmico de código de plataforma.

Como funciona o mecanismo expect/actual

O mecanismo do expect/actual funciona no nível de source set — o sistema de módulos do Kotlin Multiplatform. O código comum disponível para todas as plataformas reside no source set commonMain. O código dependente de plataforma reside em iosMain, androidMain, macosMain, etc. A palavra-chave expect no commonMain declara uma API, enquanto a palavra-chave actual em um source set de plataforma fornece a implementação. O compilador as vincula no estágio de geração de código, substituindo a chamada de função expect pela implementação actual correspondente para a plataforma alvo.

A hierarquia de source sets em um projeto KMM típico é a seguinte: commonMain contém declarações expect, iosMain e androidMain contêm implementações actual. Ao compilar para iOS, o actual do iosMain é usado; ao compilar para Android, o actual do androidMain é usado. Source sets podem ser intermediários (por exemplo, iosArm64Main para uma arquitetura específica), permitindo refinar implementações para diferentes dispositivos.

kotlin
// commonMain — expect declaration
expect fun getPlatformName(): String

// androidMain — actual for Android
actual fun getPlatformName(): String = "Android"

// iosMain — actual for iOS
actual fun getPlatformName(): String = "iOS"

Verificação do compilador das implementações actual

O compilador Kotlin verifica várias condições ao trabalhar com expect/actual. Cada declaração expect deve ter uma implementação actual para cada plataforma ativa. A assinatura da declaração actual deve corresponder à assinatura expect (a anotação @OptionalExpectation pode flexibilizar esse requisito). Modificadores de acesso, tipo de retorno e parâmetros devem ser idênticos. O compilador também verifica a ausência de dependências cíclicas entre declarações expect e actual.

Tipos de expect/actual: funções, classes, propriedades

expect/actual suporta vários tipos de declarações. Os mais usados são funções expect/actual para operações de plataforma, classes expect/actual para objetos que exigem implementação nativa e propriedades expect/actual para constantes e configurações. Cada tipo tem suas próprias regras de uso e limitações.

As funções expect/actual são o tipo mais simples e comum. São usadas para chamar APIs de plataforma como obter a hora, ler arquivos ou enviar requisições HTTP. As classes expect/actual são usadas para criar objetos que interagem diretamente com código nativo (por exemplo, para acessar a câmera, geolocalização ou armazenamento de chaves). As propriedades expect/actual (val) são adequadas para constantes de plataforma — nome do SO, versão do SDK ou caminho do diretório do sistema.

Tipo de declaraçãoPalavras-chaveExemplo de uso
Funçãoexpect fun / actual funObter um identificador único de dispositivo
Classeexpect class / actual classAcessar SecureStorage (Keychain / EncryptedSharedPreferences)
Propriedadeexpect val / actual valPlataforma atual (iOS / Android)
Enum classexpect enum / actual enumLista de permissões de aplicativo disponíveis
Typealiasexpect typealias / actual typealiasTipo de resposta de rede específico da plataforma

Limitações do expect/actual

Nem todas as construções do Kotlin podem ser usadas com expect/actual. Uma declaração expect não pode conter um corpo — apenas uma assinatura. Uma classe expect não pode ter um construtor com parâmetros (deve ter um construtor primário vazio). Para enum expect/actual, todas as constantes devem ser idênticas tanto em expect quanto em actual. Propriedades expect devem ser val (não var), já que armazenar estado no módulo comum para propriedades de plataforma não faz sentido.

Exemplos de código: do simples ao complexo

Vamos explorar exemplos práticos de expect/actual, de funções simples a classes completas. O caso básico é obter o nome da plataforma para uso na interface do usuário. Exemplos mais complexos incluem acessar armazenamento nativo e trabalhar com threads de plataforma.

kotlin
// commonMain — expect class for secure storage
expect class PlatformStorage {
    fun save(key: String, value: String)
    fun get(key: String): String?
    fun remove(key: String)
}

// androidMain — actual on Android
actual class PlatformStorage {
    private val prefs = AppContext.getSharedPreferences("secure", 0)

    actual fun save(key: String, value: String) { prefs.edit().putString(key, value).apply() }
    actual fun get(key: String): String? = prefs.getString(key, null)
    actual fun remove(key: String) { prefs.edit().remove(key).apply() }
}

Neste exemplo, a classe expect PlatformStorage define o contrato de um armazenamento simples chave-valor. No Android, a implementação usa SharedPreferences, enquanto no iOS usa Keychain ou NSUserDefaults. Graças ao expect/actual, a lógica de negócio no commonMain chama save/get/remove sem conhecer a implementação da plataforma.

kotlin
// iosMain — actual on iOS with Keychain
actual class PlatformStorage {
    actual fun save(key: String, value: String) {
        val query = mapOf<String, Any>(
            kSecClass to kSecClassGenericPassword,
            kSecAttrAccount to key,
            kSecValueData to value.encodeToByteArray()
        )
        SecItemAdd(query, null)
    }

    actual fun get(key: String): String? {
        val query = mapOf<String, Any>(
            kSecClass to kSecClassGenericPassword,
            kSecAttrAccount to key,
            kSecReturnData to true
        )
        val result = mutableMapOf<String, Any>()
        return if (SecItemCopyMatching(query, result) == errSecSuccess)
            result[kSecValueData]?.toString()
        else null
    }

    actual fun remove(key: String) {
        val query = mapOf<String, Any>(
            kSecClass to kSecClassGenericPassword,
            kSecAttrAccount to key
        )
        SecItemDelete(query)
    }
}

Melhores práticas de expect/actual

Ao projetar APIs expect/actual, vários princípios devem ser seguidos. Minimize o número de declarações expect — quanto mais código comum, mais simples a manutenção. Use expect/actual apenas para APIs que realmente diferem entre plataformas. Para o restante do código, use interfaces com fábricas ou injeção de dependência, o que simplifica os testes.

É recomendado agrupar declarações expect por módulos temáticos, em vez de misturá-las em um único arquivo. Por exemplo, Storage.kt para declarações expect de armazenamento, Platform.kt para funções expect que trabalham com o SO e Analytics.kt para classes expect de análise. Isso simplifica a navegação e a compreensão da superfície de plataforma de um projeto KMM. Cada arquivo actual deve estar no source set correspondente: androidMain, iosMain, desktopMain, etc.

Implementações padrão via expect fun com actual fun onde actual usa código comum é um antipadrão comum. Se a implementação da plataforma não difere do padrão, expect/actual não é necessário. Nesses casos, use uma função simples no commonMain. Além disso, evite expect/actual para getters triviais — use expect val com constantes.

Organização do código em um projeto

A estrutura adequada do código expect/actual é crítica para a legibilidade do projeto. Cada módulo expect/actual deve ter um único ponto de entrada. Exemplo de organização: commonMain/kotlin/com/project/platform contém declarações expect, androidMain/kotlin/com/project/platform contém actual para Android, iosMain/kotlin/com/project/platform contém actual para iOS. Os nomes de arquivos e pacotes devem coincidir para expect e actual, para que um desenvolvedor encontre rapidamente a implementação correspondente.

Alternativas ao expect/actual no KMM

As interfaces com uma fábrica de plataforma são a principal alternativa ao expect/actual. Em vez de uma classe expect, você pode declarar uma interface no commonMain e criar classes concretas nos módulos de plataforma. Uma fábrica ou contêiner de injeção de dependência fornece a implementação correta em tempo de execução. Esta abordagem é melhor para testes, pois a interface pode ser simulada.

A injeção de dependência (Koin, Kodein) é uma abordagem mais flexível, mas menos eficiente. Um contêiner DI é configurado separadamente para cada plataforma e fornece dependências de plataforma ao código comum. Ao contrário do expect/actual, a injeção ocorre em tempo de execução, permitindo trocar implementações para testes. Por outro lado, erros de configuração de DI só são detectados em tempo de execução, não em tempo de compilação.

AbordagemVerificação em compilaçãoFlexibilidade de testeSobrecarga em execução
expect/actualCompletaBaixa (actual não pode ser simulado)Zero (ligação em compilação)
Interfaces + FábricaParcialAlta (pode ser simulado)Mínima (chamada virtual)
Injeção de dependênciaNão (runtime)AltaModerada (proxies DI)

A escolha entre expect/actual e alternativas depende do contexto. Para código crítico de desempenho (motores de jogo, processamento em tempo real), expect/actual é preferível devido à sobrecarga zero. Para lógica de negócio (repositórios, casos de uso), é melhor usar interfaces com DI para simplificar os testes. Uma abordagem combinada — expect/actual para operações de baixo nível de plataforma e interfaces para a camada de lógica de negócio — é usada na maioria dos projetos KMM de produção.

Perguntas frequentes

Qual a diferença entre expect/actual e interfaces?

expect/actual vincula a implementação em tempo de compilação sem chamadas virtuais, enquanto as interfaces vinculam em tempo de execução. expect/actual garante implementação para todas as plataformas, interfaces exigem verificações em tempo de execução.

Pode-se usar expect/actual para enums?

Sim, expect enum é suportado a partir do Kotlin 1.7. Todas as constantes nos enums expect e actual devem coincidir. Valores diferentes de constantes em plataformas diferentes é um erro de compilação.

O que acontece se faltar uma implementação actual?

O compilador gerará um erro para cada plataforma onde faltar a implementação actual. O projeto não será compilado até que implementações actual correspondentes sejam adicionadas para todas as declarações expect.

Pode-se usar expect/actual dentro de um único source set?

Não, expect e actual devem estar em source sets diferentes. expect no commonMain ou em um source set intermediário, actual em um source set de plataforma. Colocar expect e actual no mesmo source set é um erro de compilação.

Como testar código expect/actual?

Para testar expect/actual, use commonTest com source sets de teste de plataforma. Escreva testes expect no commonTest e testes actual para cada plataforma. Testes de integração são executados separadamente em cada plataforma alvo.

Resumo

  • expect/actual é o mecanismo chave do Kotlin Multiplatform para implementações de plataforma com verificação do compilador.
  • expect declara um contrato no commonMain, actual fornece a implementação em um source set de plataforma.
  • Os tipos de declaração incluem funções, classes, propriedades, classes enum e typealias com diferentes regras de uso.
  • A verificação do compilador garante implementações actual para todas as plataformas alvo, prevenindo falhas em tempo de execução.
  • Recomenda-se minimizar o expect/actual e usar interfaces com DI para a lógica de negócio.
  • A organização do código deve ser consistente com nomes de arquivos e pacotes coincidentes para expect e actual.
  • Use expect/actual para operações de baixo nível de plataforma (armazenamento, sistema de arquivos, sensores) — isso garante sobrecarga zero em tempo de execução.

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também