Noticia Diseñando una arquitectura de estado de IU compartida en Kotlin Multiplatform con Compose Multiplatform

Diseñando una arquitectura de estado de IU compartida en Kotlin Multiplatform con Compose Multiplatform


Si te mueres por dejar de escribir el mismo código dos veces y quieres que tu aplicación se vea genial tanto en Android como en iOS, has llegado al sitio adecuado. El ecosistema de Kotlin Multiplatform (KMP) ha pegado un salto increíble, permitiéndonos no solo compartir la lógica de negocio, sino también la capa visual gracias a Compose Multiplatform, lo que supone un cambio de juego total para los desarrolladores móviles.

No se trata simplemente de copiar y pegar código, sino de plantearse una estratégia de arquitectura inteligente. Lograr que la interfaz de usuario reaccione de forma fluida y coherente en distintas plataformas requiere un diseño meticuloso donde el estado sea el centro de todo, evitando que la app se convierta en un caos imposible de mantener a medida que crece.

Organización del Proyecto y Modularización​


Para que un proyecto no se vuelva inmanejable, lo ideal es huir de los módulos gigantes y apostar por una estructura multi-módulo. Una técnica muy efectiva es organizar el código por funcionalidades o features, creando una jerarquía donde cada módulo sea independiente. Por ejemplo, podemos tener una carpeta core para las utilidades globales como el networking o el logging, y una carpeta de features donde cada funcionalidad tenga sus propias capas de dominio, datos y presentación.

El corazón de la aplicación suele ser el módulo composeApp. Este actúa como el pegamento que ensambla todas las piezas, gestionando la navegación y las configuraciones globales, funcionando de forma muy similar a como lo hace el módulo app en un proyecto estándar de Android. Esta separación de responsabilidades es lo que permite que el equipo pueda escalar la app sin pisarse los pies.

Implementando Clean Architecture en KMP​


Si queremos que la app sea testable y mantenible a largo plazo, Clean Architecture es la ruta a seguir. Esta metodología nos obliga a dividir el código en capas bien definidas para que ninguna dependa de la otra de forma incorrecta. La capa de dominio debe residir enteramente en el código común (commonMain), conteniendo los casos de uso y las interfaces de los repositorios, asegurando que la lógica de negocio sea totalmente agnóstica a la plataforma.

Por otro lado, la capa de datos se encarga de la implementación real. Aquí es donde entra en juego la magia de expect/actual, que nos permite definir una función en el código común y darle una implementación específica según si estamos en Android o iOS, algo vital para manejar bases de datos locales o APIs de red específicas. Finalmente, la capa de presentación utiliza Compose Multiplatform para renderizar la interfaz, aprovechando la potencia de Jetpack Compose en Android y su adaptación nativa en iOS.

Gestión del Estado y Flujos de Datos​


El gran reto de cualquier UI moderna es cómo manejar la información que cambia en tiempo real. En KMP, la herramienta estrella es StateFlow. Al ser un flujo de datos reactivo, permite que el ViewModel (que puede ser compartido en el módulo común) emita estados que la UI simplemente observa. Gracias a que Compose Multiplatform soporta la función collectAsState(), la pantalla se refresca automáticamente cada vez que el estado cambia, eliminando la necesidad de actualizaciones manuales tediosas.

Para quienes vienen de Android nativo y están acostumbrados a Hilt, el salto a Koin es muy común en el mundo multiplataforma. Koin es mucho más flexible para KMP ya que no depende de la generación de código de Android, facilitando la inyección de dependencias en el código compartido. Esto permite que los ViewModels tengan acceso a los repositorios sin importar si la app se está ejecutando en un iPhone o en un Pixel.

Flujo de Trabajo y Configuración Técnica​


Diseñar una arquitectura de estado de IU compartida en Kotlin Multiplatform con Compose Multiplatform


Para poner esto en marcha, Android Studio ofrece plantillas como Kotlin Multiplatform Shared Module, que automatiza gran parte de la configuración inicial. Es fundamental entender que, aunque compartamos la UI, para compilar la parte de iOS seguimos necesitando Xcode y una máquina macOS. El código de Kotlin se compila en un framework binario que luego Swift consume mediante una fase de compilación específica en Xcode.

En cuanto al desarrollo de componentes, la clave es diseñar Composables reutilizables en el módulo común. Un botón o un campo de texto puede definirse una sola vez y usarse en todas las plataformas. No obstante, no debemos caer en el error de querer compartir el 100% de todo; siempre es recomendable dejar un margen para personalizaciones nativas, como el efecto de goma elástica en los scrolls de iOS, para que el usuario sienta que la app es realmente nativa.

Calidad, Pruebas y Rendimiento​


No podemos olvidar que un código compartido sin tests es una bomba de relojería. La ventaja de KMP es que podemos escribir unit tests en commonTest utilizando kotlin.test, lo que significa que probamos la lógica de negocio una sola vez para todas las plataformas. Para los detalles más finos, siempre podemos añadir pruebas específicas en androidTest y iosTest.

En las versiones más recientes, se ha optimizado drásticamente la velocidad de compilación gracias al almacenamiento en caché del compilador y la compatibilidad con el compilador K2. Además, el renderizado en iOS ha mejorado significativamente, reduciendo la pérdida de fotogramas y haciendo que las animaciones sean mucho más fluidas, acercándose cada vez más a la experiencia de usuario nativa que todos buscamos.

La combinación de una estructura modular, la disciplina de Clean Architecture y el uso de flujos reactivos como StateFlow permite crear aplicaciones extremadamente robustas. Al centralizar la lógica y la interfaz en Compose Multiplatform, se reduce drásticamente el tiempo de desarrollo y los errores de inconsistencia entre sistemas operativos, logrando un equilibrio perfecto entre la eficiencia de compartir código y la calidad de un producto nativo.

Continúar leyendo...