En el artículo anterior de la serie (Unidad 0 — Tema 1), exploramos el escudo protector de Kotlin Null Safety y cómo sus garantías de nulidad estricta cruzan las fronteras de Android e iOS. Sin embargo, proteger variables individuales no es suficiente cuando la aplicación escala.
Imagina diseñar una aplicación donde sea físicamente imposible representar un estado inválido. Olvídate de esos bugs extraños donde la pantalla muestra el icono de carga (loader) y, al mismo tiempo, el mensaje de error y los datos antiguos mezclados.
En el desarrollo moderno, la arquitectura se basa en predecir estados precisos. Y en el ecosistema Kotlin, la herramienta definitiva para lograr esto son las jerarquías selladas: las Sealed Classes y Sealed Interfaces.
Cuando trabajamos en Kotlin Multiplatform (KMP), estas estructuras se convierten en el contrato sagrado que define cómo fluyen los datos desde el código compartido hasta la interfaz de usuario en Android y iOS.
1. ¿Qué significa que algo esté «sellado»?
En Kotlin, anteponer la palabra clave sealed a una clase o interfaz significa restringir su árbol de herencia. Le estás diciendo al compilador: «Escucha bien, nadie fuera de este paquete/módulo puede crear subclases de esta interfaz».
Esto le da al compilador el control total y absoluto de cuántas variantes existen de un tipo.
¿Sealed Class o Sealed Interface?
Aunque nacieron en momentos distintos de la evolución de Kotlin, hoy conviven en armonía:
sealed class: Ideal si tus subtipos necesitan heredar comportamiento común o propiedades compartidas definidas en la propia clase base. Puede tener constructor primario, pero eso no implica constructores «mágicos» en las subclases: cada subtipo sigue declarando explícitamente cómo inicializa su propio estado.sealed interface: Más ligera desde el punto de vista del modelo. Al ser una interfaz, una misma implementación puede cumplir con varios contratos a la vez. Es la opción favorita por defecto para modelar estados puros si no necesitas heredar lógica compleja.
2. El combo perfecto: Estados de UI y Respuestas de Red
El caso de uso estrella para las estructuras selladas es el manejo de peticiones asíncronas. En lugar de tener tres variables booleanas independientes (isLoading, hasError, data), unificamos todo en un único flujo de verdad:
sealed interface UIState<out T> {
object Loading : UIState<Nothing>
data class Success<out T>(val data: T) : UIState<T>
data class Error(val message: String) : UIState<Nothing>
}
Con este modelo, tu pantalla solo puede estar en uno de estos tres estados a la vez. No hay espacio para la ambigüedad.
3. El when exhaustivo: Tu compilador es tu mejor QA
La verdadera magia ocurre cuando combinas una interfaz sellada con la expresión when. Como el compilador conoce exactamente todas las opciones posibles, no te exige escribir una rama else.
fun renderUI(state: UIState<String>) {
when (state) {
is UIState.Loading -> showLoader()
is UIState.Success -> showContent(state.data)
is UIState.Error -> showError(state.message)
// ¡No hace falta poner un 'else'! El compilador sabe que no hay más opciones.
}
}
💡 La ventaja competitiva: Si mañana decides añadir un nuevo estado llamado
UIState.Empty, el proyecto dejará de compilar de inmediato en todos los lugares donde uses estewhen. El compilador te obligará a controlar el nuevo caso antes de lanzar la app, eliminando por completo los olvidos humanos.
4. Cruzando el puente hacia iOS: ¿Qué pasa en Swift?
Históricamente, este ha sido uno de los mayores dolores de cabeza en KMP. Como Objective-C (el puente intermedio clásico) no entiende el concepto de clases selladas, el compilador las traducía a clases normales herederas de una base. En Swift, los desarrolladores de iOS se veían obligados a escribir sentencias switch con un caso default obligatorio, perdiendo la verificación estática del compilador.
Afortunadamente, las herramientas modernas han solucionado esto:
El héroe actual: SKIE
Herramientas de la comunidad altamente adoptadas como SKIE (desarrollada por Touchlab) analizan tu código Kotlin y generan automáticamente Enums de Swift con valores asociados (Enums with associated values).
Para el desarrollador de iOS, consumir tu estado de Kotlin se siente 100% nativo:
switch viewState {
case .loading:
showLoader()
case .success(let content):
showContent(content.data)
case .error(let error):
showError(error.message)
} // ¡Exhaustivo y sin necesidad de un 'default'!
El futuro: Swift Export
JetBrains está desarrollando de forma nativa Swift Export, un mecanismo directo en el compilador de Kotlin que elimina el puente intermedio de Objective-C para que características potentes como los enums y las jerarquías de tipos se comuniquen de forma directa y transparente entre ambos lenguajes en los próximos años.
5. Enfoque Sénior: Sealed Classes vs. Sealed Interfaces bajo el capó de la JVM
Desde la perspectiva de diseño, la decisión entre sealed class y sealed interface debería apoyarse más en expresividad y restricciones del modelo que en supuestas ganancias de rendimiento difíciles de medir en la mayoría de aplicaciones.
- Constructor y estado compartido: Una
sealed classpuede centralizar propiedades y lógica base en su constructor o en miembros concretos. Eso resulta útil cuando todos los subtipos comparten invariantes reales. - Composición de contratos: Las
sealed interfacespermiten modelar estados o eventos sin imponer jerarquías de estado común, y además pueden combinarse con otras interfaces del dominio. - Cuándo elegir cada una: Por defecto, prefiere las Sealed Interfaces para modelar estados o eventos de la UI si solo necesitas variantes exhaustivas. Elige Sealed Classes cuando de verdad necesites compartir estado o comportamiento implementado entre subtipos.
Para profundizar
Si quieres ver las reglas exactas de compilación y cómo exprimir estas estructuras en tus proyectos, dale un vistazo a la documentación oficial:
- Sealed Classes e Interfaces en Kotlin: La documentación base sobre cómo estructurar la herencia cerrada en Kotlin.
- SKIE – Manejo de Jerarquías Selladas: Detalle técnico de cómo SKIE transforma el código multiplataforma en enums nativos de Swift.
- Evolución de Interoperabilidad con Swift: Hoja de ruta oficial de JetBrains sobre las capacidades directas de exportación de código a Swift.
Conclusión
Modelar tus datos usando sealed interface en un proyecto KMP eleva la calidad de todo el sistema. Estás exportando una arquitectura libre de estados imposibles que tanto el desarrollador de Android como el de iOS agradecerán profundamente. Menos condicionales defensivos, cero estados duplicados y toda la potencia del chequeo en tiempo de compilación.
Pero modelar el estado no sirve de nada si no somos capaces de resolver de dónde proceden las peticiones asíncronas de red y bases de datos que lo mutan. En el próximo artículo, Unidad 0 — Tema 3: Hilos y Asincronía: Tu primera guía de Corrutinas en Kotlin, aprenderemos a dominar la suspensión y la concurrencia estructurada multiplataforma de forma legible y elegante.

