expect/actual — esencia, palabras clave de KMM y cómo funcionan

Autor: IT Sectr Publicado: 2026-06-05 Tiempo de lectura: 8 min

expect/actual es un mecanismo de Kotlin Multiplatform que permite declarar API dependientes de la plataforma en código común. La palabra clave expect crea un contrato de función, clase o propiedad en commonMain, mientras que la palabra clave actual proporciona una implementación concreta para cada plataforma. El compilador verifica que cada declaración expect tenga una implementación actual correspondiente en todas las plataformas objetivo. Según JetBrains, 2025, este mecanismo se utiliza en el 80% de los proyectos KMM para implementar lógica de negocio multiplataforma.

Puntos clave

  • expect es la palabra clave para declarar un contrato de función, clase o propiedad en código común.
  • actual es la palabra clave para proporcionar una implementación de plataforma de una declaración expect.
  • commonMain es el source set con código común donde se colocan las declaraciones expect.
  • Verificación del compilador — el compilador garantiza que existen implementaciones actual para todas las plataformas objetivo.
  • Source set — conjuntos (iosMain, androidMain) donde residen las implementaciones actual específicas de la plataforma.

¿Qué es expect/actual?

expect/actual es un mecanismo declarativo de Kotlin Multiplatform para implementar programación orientada a la plataforma. Permite describir una API una vez en el módulo común (expect) e implementarla por separado para cada plataforma (actual). A diferencia de las interfaces, expect/actual no crea llamadas virtuales — el compilador vincula las declaraciones expect y actual en tiempo de compilación, eliminando la sobrecarga de la despacho dinámico.

La historia de expect/actual comenzó con la introducción de Kotlin Multiplatform en 2017. Inicialmente, el mecanismo se llamaba expect/actual declarations y era experimental. En Kotlin 1.2 se añadieron las anotaciones expect, y en Kotlin 1.3 expect/actual se volvió estable para clases y funciones. Con el tiempo, el mecanismo se amplió: Kotlin 1.6 añadió soporte para expect/actual en companion objects, Kotlin 1.7 para enum classes y Kotlin 2.0 para typealias.

La característica clave de expect/actual es la seguridad en tiempo de compilación. Si un desarrollador añade una declaración expect en commonMain pero olvida proporcionar una implementación actual para iOS, el compilador generará un error. Esto evita fallos en tiempo de ejecución típicos de enfoques que usan reflexión o carga dinámica de código de plataforma.

Cómo funciona el mecanismo expect/actual

El mecanismo de expect/actual funciona a nivel de source set — el sistema de módulos de Kotlin Multiplatform. El código común disponible para todas las plataformas reside en el source set commonMain. El código dependiente de la plataforma reside en iosMain, androidMain, macosMain, etc. La palabra clave expect en commonMain declara una API, mientras que la palabra clave actual en un source set de plataforma proporciona la implementación. El compilador las vincula en la etapa de generación de código, reemplazando la llamada a la función expect por la implementación actual correspondiente para la plataforma objetivo.

La jerarquía de source sets en un proyecto KMM típico es la siguiente: commonMain contiene las declaraciones expect, iosMain y androidMain contienen las implementaciones actual. Al compilar para iOS, se usa el actual de iosMain; al compilar para Android, el actual de androidMain. Los source sets pueden ser intermedios (por ejemplo, iosArm64Main para una arquitectura específica), lo que permite refinar las implementaciones 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"

Verificación del compilador de las implementaciones actual

El compilador de Kotlin verifica varias condiciones al trabajar con expect/actual. Cada declaración expect debe tener una implementación actual para cada plataforma activa. La firma de la declaración actual debe coincidir con la firma expect (la anotación @OptionalExpectation puede relajar este requisito). Los modificadores de acceso, el tipo de retorno y los parámetros deben ser idénticos. El compilador también verifica que no haya dependencias cíclicas entre las declaraciones expect y actual.

Tipos de expect/actual: funciones, clases, propiedades

expect/actual admite varios tipos de declaraciones. Los más utilizados son las funciones expect/actual para operaciones de plataforma, las clases expect/actual para objetos que requieren implementación nativa y las propiedades expect/actual para constantes y configuraciones. Cada tipo tiene sus propias reglas de uso y limitaciones.

Las funciones expect/actual son el tipo más simple y común. Se utilizan para llamar a API de plataforma como obtener la hora, leer archivos o enviar solicitudes HTTP. Las clases expect/actual se utilizan para crear objetos que interactúan directamente con código nativo (por ejemplo, para acceder a la cámara, geolocalización o almacenamiento de claves). Las propiedades expect/actual (val) son adecuadas para constantes de plataforma — nombre del SO, versión del SDK o ruta del directorio del sistema.

Tipo de declaraciónPalabras claveEjemplo de uso
Funciónexpect fun / actual funObtener un identificador único de dispositivo
Claseexpect class / actual classAcceder a SecureStorage (Keychain / EncryptedSharedPreferences)
Propiedadexpect val / actual valPlataforma actual (iOS / Android)
Enum classexpect enum / actual enumLista de permisos de aplicación disponibles
Typealiasexpect typealias / actual typealiasTipo de respuesta de red específico de la plataforma

Limitaciones de expect/actual

No todas las construcciones de Kotlin se pueden usar con expect/actual. Una declaración expect no puede contener un cuerpo — solo una firma. Una clase expect no puede tener un constructor con parámetros (debe tener un constructor primario vacío). Para enum expect/actual, todas las constantes deben ser idénticas tanto en expect como en actual. Las propiedades expect deben ser val (no var), ya que almacenar estado en el módulo común para propiedades de plataforma no tiene sentido.

Ejemplos de código: de lo simple a lo complejo

Exploremos ejemplos prácticos de expect/actual desde funciones simples hasta clases completas. El caso básico es obtener el nombre de la plataforma para usarlo en la interfaz de usuario. Los ejemplos más complejos incluyen el acceso al almacenamiento nativo y el trabajo con hilos 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() }
}

En este ejemplo, la clase expect PlatformStorage define el contrato de un almacenamiento simple clave-valor. En Android, la implementación usa SharedPreferences, mientras que en iOS usa Keychain o NSUserDefaults. Gracias a expect/actual, la lógica de negocio en commonMain llama a save/get/remove sin conocer la implementación de la 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)
    }
}

Mejores prácticas de expect/actual

Al diseñar API expect/actual, se deben seguir varios principios. Minimice la cantidad de declaraciones expect — cuanto más código común, más sencillo es el mantenimiento. Use expect/actual solo para API que realmente difieren entre plataformas. Para el resto del código, use interfaces con fábricas o inyección de dependencias, lo que simplifica las pruebas.

Se recomienda agrupar las declaraciones expect por módulos temáticos, en lugar de mezclarlas en un solo archivo. Por ejemplo, Storage.kt para declaraciones expect de almacenamiento, Platform.kt para funciones expect que trabajan con el SO y Analytics.kt para clases expect de analítica. Esto simplifica la navegación y la comprensión de la superficie de plataforma de un proyecto KMM. Cada archivo actual debe estar en el source set correspondiente: androidMain, iosMain, desktopMain, etc.

Las implementaciones predeterminadas mediante expect fun con actual fun donde actual usa código común es un antipatrón común. Si la implementación de la plataforma no difiere de la predeterminada, expect/actual no es necesario. En tales casos, use una función simple en commonMain. Evite también expect/actual para getters triviales — use expect val con constantes.

Organización del código en un proyecto

La estructura adecuada del código expect/actual es crítica para la legibilidad del proyecto. Cada módulo expect/actual debe tener un único punto de entrada. Ejemplo de organización: commonMain/kotlin/com/project/platform contiene las declaraciones expect, androidMain/kotlin/com/project/platform contiene actual para Android, iosMain/kotlin/com/project/platform contiene actual para iOS. Los nombres de archivos y paquetes deben coincidir para expect y actual, de modo que un desarrollador pueda encontrar rápidamente la implementación correspondiente.

Alternativas a expect/actual en KMM

Las interfaces con una fábrica de plataforma son la principal alternativa a expect/actual. En lugar de una clase expect, puede declarar una interfaz en commonMain y crear clases concretas en los módulos de plataforma. Una fábrica o un contenedor de inyección de dependencias proporciona la implementación correcta en tiempo de ejecución. Este enfoque es mejor para pruebas, ya que la interfaz se puede simular.

La inyección de dependencias (Koin, Kodein) es un enfoque más flexible pero menos eficiente. Un contenedor DI se configura por separado para cada plataforma y proporciona dependencias de plataforma al código común. A diferencia de expect/actual, la inyección ocurre en tiempo de ejecución, lo que permite intercambiar implementaciones para pruebas. Por otro lado, los errores de configuración de DI solo se detectan en tiempo de ejecución, no en tiempo de compilación.

EnfoqueVerificación en compilaciónFlexibilidad de pruebasSobrecarga en ejecución
expect/actualCompletaBaja (actual no se puede simular)Cero (enlace en compilación)
Interfaces + FábricaParcialAlta (se puede simular)Mínima (llamada virtual)
Inyección de dependenciasNo (runtime)AltaModerada (proxies DI)

La elección entre expect/actual y alternativas depende del contexto. Para código crítico en rendimiento (motores de juegos, procesamiento en tiempo real), expect/actual es preferible por su sobrecarga cero. Para lógica de negocio (repositorios, casos de uso), es mejor usar interfaces con DI para simplificar las pruebas. Un enfoque combinado — expect/actual para operaciones de bajo nivel de plataforma e interfaces para la capa de lógica de negocio — se utiliza en la mayoría de los proyectos KMM de producción.

Preguntas frecuentes

¿Cuál es la diferencia entre expect/actual y las interfaces?

expect/actual vincula la implementación en tiempo de compilación sin llamadas virtuales, mientras que las interfaces lo hacen en tiempo de ejecución. expect/actual garantiza la implementación para todas las plataformas, las interfaces requieren verificaciones en tiempo de ejecución.

¿Se puede usar expect/actual para enums?

Sí, expect enum es compatible desde Kotlin 1.7. Todas las constantes en los enums expect y actual deben coincidir. Diferentes valores de constantes en diferentes plataformas es un error de compilación.

¿Qué sucede si falta una implementación actual?

El compilador generará un error para cada plataforma donde falte la implementación actual. El proyecto no se compilará hasta que se añadan las implementaciones actual correspondientes para todas las declaraciones expect.

¿Se puede usar expect/actual dentro de un mismo source set?

No, expect y actual deben estar en diferentes source sets. expect en commonMain o un source set intermedio, actual en un source set de plataforma. Colocar expect y actual en el mismo source set es un error de compilación.

¿Cómo probar el código expect/actual?

Para probar expect/actual, use commonTest con source sets de prueba de plataforma. Escriba pruebas expect en commonTest y pruebas actual para cada plataforma. Las pruebas de integración se ejecutan por separado en cada plataforma objetivo.

Resumen

  • expect/actual es el mecanismo clave de Kotlin Multiplatform para implementaciones de plataforma con verificación del compilador.
  • expect declara un contrato en commonMain, actual proporciona la implementación en un source set de plataforma.
  • Los tipos de declaración incluyen funciones, clases, propiedades, enum classes y typealias con diferentes reglas de uso.
  • La verificación del compilador garantiza implementaciones actual para todas las plataformas objetivo, evitando fallos en tiempo de ejecución.
  • Se recomienda minimizar expect/actual y usar interfaces con DI para la lógica de negocio.
  • La organización del código debe ser coherente con nombres de archivos y paquetes que coincidan para expect y actual.
  • Use expect/actual para operaciones de bajo nivel de plataforma (almacenamiento, sistema de archivos, sensores) — esto garantiza una sobrecarga de ejecución cero.

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también