Jetpack Compose es el toolkit moderno de Android para crear interfaces de usuario nativas mediante Kotlin y un modelo declarativo. En lugar de construir una pantalla con archivos XML y modificar manualmente cada vista cuando cambian los datos, el desarrollador describe qué debe mostrar la interfaz para un estado determinado.
Cuando ese estado cambia, Compose vuelve a evaluar las partes necesarias de la interfaz y actualiza únicamente aquello que puede haber variado. Esta forma de trabajo modifica la manera de diseñar componentes, gestionar formularios, construir listas, organizar la navegación y conectar la capa visual con la lógica de una aplicación.
Google presenta Jetpack Compose como el toolkit moderno recomendado para desarrollar interfaces nativas en Android. Su propuesta combina APIs de Kotlin, componentes reutilizables, integración con Material Design, herramientas de previsualización y compatibilidad con el resto del ecosistema Android. :contentReference[oaicite:0]{index=0}
En un proyecto real, Compose puede utilizarse para construir desde una pantalla de acceso hasta un catálogo completo, un reproductor, una aplicación para tablets o una interfaz adaptada a dispositivos plegables. También permite introducir pantallas nuevas dentro de aplicaciones que todavía utilizan Views y XML, por lo que no obliga a rehacer todo el producto en una única migración.
En Aula CM solemos insistir en que aprender Compose no consiste en memorizar componentes. La diferencia importante está en comprender el modelo mental: estado, eventos, recomposición, flujo de datos, ciclo de vida y separación de responsabilidades. Cuando estas bases no están claras, una pantalla puede funcionar visualmente y seguir siendo difícil de probar, ampliar o mantener.
Qué Es Jetpack Compose
Jetpack Compose es un conjunto de bibliotecas y herramientas para construir interfaces de usuario en Android mediante funciones de Kotlin. Estas funciones describen elementos visuales y se identifican con la anotación @Composable.
Una función composable puede representar una pantalla completa, una tarjeta de producto, un botón, una fila de navegación o una pequeña etiqueta. En su interior puede llamar a otras funciones composables para construir una jerarquía de interfaz.
El enfoque es declarativo porque el código expresa el resultado visual esperado. En lugar de localizar una vista concreta y cambiar una propiedad, la aplicación actualiza el estado. Compose utiliza ese nuevo estado para determinar cómo debe representarse la interfaz.
La documentación oficial explica este modelo mediante funciones que reciben datos y emiten elementos de interfaz. Cuando el usuario interactúa, la UI genera eventos; la lógica modifica el estado; y Compose vuelve a ejecutar las funciones que puedan necesitar una actualización. Este proceso recibe el nombre de recomposición. :contentReference[oaicite:1]{index=1}
Jetpack Compose forma parte del ecosistema Android Jetpack, pero no debe confundirse con todo Jetpack. Android Jetpack reúne bibliotecas para arquitectura, navegación, almacenamiento, tareas en segundo plano y otros problemas. Compose se centra principalmente en la construcción de interfaces, aunque se integra con muchas de esas bibliotecas.
Tampoco es un lenguaje independiente. El lenguaje utilizado es Kotlin. Compose aporta un compilador, un runtime, APIs de UI, componentes, herramientas de diseño y mecanismos específicos para trabajar con estado y renderizado.
Para Qué Sirve Jetpack Compose
Jetpack Compose sirve para desarrollar interfaces de aplicaciones Android sin depender de la estructura tradicional basada exclusivamente en layouts XML y clases View. Permite definir la apariencia, el comportamiento y la interacción desde código Kotlin.
Puede utilizarse para construir:
- Pantallas de acceso y registro.
- Catálogos de productos.
- Listas de contenidos.
- Formularios y buscadores.
- Menús y sistemas de navegación.
- Paneles de administración.
- Aplicaciones para móviles y tablets.
- Interfaces para Wear OS, televisión y otros dispositivos compatibles.
- Componentes reutilizables para sistemas de diseño.
- Animaciones y transiciones entre estados.
- Pantallas conectadas con APIs o bases de datos.
Una tienda online puede utilizar Compose para mostrar un catálogo mediante una lista eficiente, abrir una ficha de producto, gestionar el carrito y adaptar la estructura cuando la aplicación se utiliza en una pantalla grande.
Una plataforma de formación puede representar cursos, módulos, progreso y ejercicios mediante componentes reutilizables. El mismo componente de tarjeta puede recibir distintos datos y mostrar cada curso sin duplicar la lógica visual.
En una aplicación interna de marketing, Compose puede construir filtros, formularios, paneles de métricas y tablas adaptadas a diferentes dispositivos. La capa visual observa el estado proporcionado por un ViewModel y presenta carga, datos o errores según corresponda.
La ventaja no está únicamente en reducir líneas de código. El modelo declarativo favorece que la pantalla se describa como una función del estado. Esto puede disminuir inconsistencias como botones que quedan habilitados cuando no deberían, mensajes antiguos que continúan visibles o elementos que no se sincronizan con los datos actuales.
Cómo Funciona Jetpack Compose
Jetpack Compose funciona construyendo una representación de la interfaz a partir de funciones composables. Durante la primera ejecución, estas funciones describen los elementos que deben aparecer y sus relaciones.
Cuando un valor observable utilizado por una función cambia, Compose programa una actualización. No necesita reconstruir indiscriminadamente toda la aplicación: intenta volver a ejecutar únicamente las funciones que podrían mostrar un resultado diferente.
El ciclo general puede resumirse así:
- La aplicación dispone de un estado.
- Las funciones composables leen parte de ese estado.
- Compose genera la interfaz correspondiente.
- El usuario realiza una acción.
- La acción produce un evento.
- La lógica procesa el evento y actualiza el estado.
- Compose detecta el cambio.
- Las partes afectadas se recomponen.
La interfaz no debería modificar directamente objetos visuales internos. El flujo se organiza alrededor de datos que bajan hacia los componentes y eventos que suben hacia el propietario del estado.
La documentación de arquitectura de Compose define este patrón como flujo de datos unidireccional: el estado desciende y los eventos ascienden. Este modelo mejora la encapsulación, la coherencia y la capacidad de probar cada parte por separado. :contentReference[oaicite:2]{index=2}
Una pantalla de inicio de sesión, por ejemplo, puede recibir el texto del correo, el texto de la contraseña, el estado de carga y un posible mensaje de error. La pantalla no decide cómo se autentica el usuario. Emite eventos cuando cambia un campo o se pulsa el botón.
El ViewModel procesa esos eventos, valida la información, llama al repositorio y publica un nuevo estado. La interfaz se limita a representarlo.
Qué Es una Función Composable
Una función composable es una función de Kotlin preparada para participar en la construcción de una interfaz Compose. Puede recibir parámetros, llamar a otras funciones composables y emitir elementos dentro de la jerarquía visual.
Una función de este tipo no suele devolver una vista. Describe qué debe formar parte de la interfaz durante la composición. El runtime se encarga de relacionar esa descripción con los elementos que ya existen.
Las funciones composables deberían ser pequeñas, legibles y tener una responsabilidad reconocible. Una pantalla completa puede coordinar varios componentes, pero una tarjeta no debería encargarse de consultar una API, escribir en una base de datos y navegar al mismo tiempo.
En una revisión profesional comprobamos si cada componente puede entenderse a partir de sus parámetros. Si para mostrar una tarjeta hay que pasarle un ViewModel completo, un controlador de navegación, un repositorio y una actividad, existe demasiado acoplamiento.
Una alternativa más limpia consiste en pasar solo el título, la imagen, el precio, el estado de disponibilidad y las funciones que representan las acciones posibles. Así, el mismo componente puede previsualizarse, probarse y reutilizarse en distintos contextos.
Las funciones composables también deben evitar efectos secundarios directos durante su ejecución. Una recomposición puede producirse varias veces, cambiar de orden o incluso descartarse. Ejecutar una compra, guardar datos o enviar una petición simplemente porque la función se ha recompuesto generaría comportamientos imprevisibles.
Qué Es la Recomposición en Jetpack Compose
La recomposición es el proceso mediante el cual Compose vuelve a ejecutar funciones composables cuando cambian datos relevantes para la interfaz. Su objetivo es actualizar la pantalla para reflejar el nuevo estado.
No debe confundirse con volver a crear toda la actividad. Una recomposición puede afectar únicamente a una parte pequeña, como el texto de un contador, el estado de un botón o una fila concreta de una lista.
Compose intenta omitir funciones cuyos parámetros no han cambiado y que pueden considerarse seguras para ser reutilizadas. Esta optimización reduce trabajo innecesario, pero requiere que los componentes estén diseñados de forma predecible.
La documentación oficial advierte que una función composable puede ejecutarse con mucha frecuencia y que no debe depender de efectos secundarios producidos por su ejecución. También recomienda mantenerla rápida, idempotente y libre de trabajo costoso. :contentReference[oaicite:3]{index=3}
Un error habitual consiste en interpretar cualquier recomposición como un problema de rendimiento. Recomponer es parte del funcionamiento normal. El problema aparece cuando se recomponen áreas excesivas, se realizan cálculos costosos o se crean objetos innecesarios en cada ejecución.
En nuestras revisiones no empezamos optimizando por intuición. Primero medimos, observamos qué funciones se ejecutan, analizamos el estado leído y comprobamos si el problema se percibe en una interacción real.
Kotlin y Jetpack Compose
Jetpack Compose utiliza Kotlin para definir interfaces, condiciones, listas, eventos y estructuras reutilizables. No necesita un lenguaje de plantillas separado para construir la jerarquía principal.
Esto permite utilizar directamente funciones, clases de datos, expresiones condicionales, extensiones y otras capacidades del lenguaje. Una pantalla puede mostrar un bloque diferente según el tipo de usuario o construir una colección de componentes a partir de una lista.
La relación entre ambos conceptos debe entenderse con precisión: Kotlin es el lenguaje y Jetpack Compose es el toolkit de interfaz. Es posible desarrollar Android con Kotlin sin Compose, utilizando Views tradicionales. También existen implementaciones de Compose en otros entornos, pero Jetpack Compose se refiere específicamente al ecosistema Android.
Compose no funciona con Java de la misma forma que con Kotlin. Una aplicación puede contener código Java y pantallas Compose, pero las APIs declarativas están diseñadas alrededor de las capacidades de Kotlin.
Para aprender Compose conviene dominar previamente:
- Funciones y parámetros.
- Funciones lambda.
- Clases y clases de datos.
- Nulabilidad.
- Colecciones.
- Delegación de propiedades.
- Coroutines y Flow.
- Gestión de dependencias con Gradle.
No es necesario conocer todo Kotlin antes de crear una primera pantalla, pero los problemas de arquitectura aparecen pronto si se copian componentes sin comprender cómo viajan los datos y las funciones.
Componentes de Jetpack Compose
Los componentes de Jetpack Compose son funciones composables preparadas para mostrar contenido, organizarlo o permitir interacción. Algunos pertenecen a Compose UI y Foundation; otros implementan patrones de Material Design.
Entre los más utilizados se encuentran:
- Text: muestra contenido textual.
- Button: representa una acción principal.
- IconButton: permite ejecutar una acción mediante un icono.
- Image: muestra imágenes.
- Icon: representa símbolos visuales.
- TextField: permite introducir texto.
- Card: agrupa contenido relacionado.
- Surface: crea una superficie con color, forma y elevación.
- Checkbox: representa una selección booleana.
- Switch: activa o desactiva una opción.
- Slider: permite elegir un valor dentro de un rango.
- Dialog: muestra contenido modal.
- Snackbar: comunica información temporal.
- Scaffold: organiza la estructura general de una pantalla.
Los componentes Material incluyen comportamientos, estilos y semántica adecuados para patrones de interacción habituales. Utilizarlos como base suele ser preferible a reconstruir cada control desde cero. :contentReference[oaicite:4]{index=4}
Sin embargo, emplear componentes predefinidos no garantiza una buena experiencia. Debemos configurar etiquetas, estados deshabilitados, errores, jerarquía visual, tamaños táctiles y contraste.
En un ecommerce, una tarjeta de producto podría combinar Image, Text, IconButton y Surface. La tarjeta debería recibir los datos necesarios y emitir eventos como seleccionar producto o añadir a favoritos.
Si cada tarjeta contiene directamente la llamada de red para cambiar el favorito, se mezcla presentación con infraestructura. El componente resulta más reutilizable cuando solo comunica la acción.
Layouts en Jetpack Compose
Los layouts determinan cómo se miden y colocan los componentes. Los elementos básicos son Column, Row y Box.
Column organiza sus hijos verticalmente. Row los distribuye horizontalmente. Box permite superponer o posicionar contenido dentro de un mismo contenedor.
Con estos tres elementos pueden construirse muchas interfaces. Una tarjeta puede usar Row para mostrar imagen y texto; dentro del área textual, una Column puede organizar título, descripción y precio.
Los layouts reciben restricciones de tamaño desde sus padres y miden a sus hijos dentro de esos límites. Comprender este recorrido ayuda a resolver problemas como componentes que ocupan más espacio del esperado o textos que no reciben una anchura adecuada.
No conviene trasladar literalmente la mentalidad de XML. En Compose, la jerarquía se expresa mediante funciones y modificadores. Las decisiones de medida dependen del orden y del contexto donde se aplica cada operación.
Cuando una interfaz crece, la solución no consiste en añadir contenedores indiscriminadamente. Cada nivel aumenta la complejidad de lectura y puede afectar al trabajo de composición y layout.
Modifiers en Jetpack Compose
Los modificadores permiten cambiar el tamaño, la disposición, la apariencia, la interacción y la semántica de un componente. Se pasan habitualmente mediante un parámetro llamado Modifier.
Un modificador puede añadir padding, establecer una anchura, aplicar un fondo, recortar una forma, capturar un clic o aportar una descripción para accesibilidad. La documentación oficial los define como mecanismos para decorar o ampliar un composable. :contentReference[oaicite:5]{index=5}
Los modificadores pueden encadenarse, pero su orden importa. Aplicar primero un fondo y después padding no siempre produce el mismo resultado que invertir ambas operaciones.
Podemos imaginar la cadena como capas que envuelven al componente. Algunas modifican las restricciones de medida; otras actúan sobre el dibujo o la interacción.
Un error frecuente consiste en crear cadenas muy largas directamente dentro de una pantalla. Cuando existe una combinación que representa una decisión de diseño reutilizable, puede extraerse a un componente o a un modificador propio.
La recomendación práctica es aceptar un Modifier como parámetro en los componentes reutilizables y aplicarlo al elemento raíz. Esto permite que el componente padre decida su posición y tamaño sin modificar la implementación interna.
Scaffold en Jetpack Compose
Scaffold es un componente que proporciona una estructura general para pantallas basadas en Material Design. Permite coordinar barras superiores, navegación inferior, botones flotantes, snackbars y contenido principal.
La documentación oficial describe Scaffold como una plataforma estructural para ensamblar interfaces complejas y señala que puede recibir componentes para top bar, bottom bar y floating action button, entre otros espacios. :contentReference[oaicite:6]{index=6}
Scaffold no diseña la pantalla automáticamente. Define zonas y comunica al contenido el espacio que debe respetar. El contenido recibe valores de padding que deben aplicarse para evitar quedar debajo de las barras.
En una aplicación de tareas, el Scaffold puede incluir:
- Una barra superior con el nombre del proyecto.
- Una barra inferior para cambiar de sección.
- Un botón flotante para crear una tarea.
- Un SnackbarHost para mensajes temporales.
- Una lista como contenido principal.
Un error frecuente aparece cuando se ignora el padding interno proporcionado por Scaffold. El contenido puede quedar oculto detrás de la app bar o de la navegación del sistema.
Tampoco es necesario utilizar Scaffold en cada componente. Normalmente representa la estructura de una pantalla o de un nivel principal, no el contenedor de cada tarjeta.
TextField en Jetpack Compose
TextField es el componente utilizado para recibir y editar texto. Puede representar un campo de búsqueda, un correo, una contraseña, una cantidad o un comentario.
El campo debe mostrar un valor y disponer de un mecanismo para comunicar cambios. En el modelo tradicional basado en valor, recibe el texto actual y emite una acción cuando el usuario lo modifica.
La documentación actual también distingue campos basados en estado, que gestionan valor, selección y composición mediante un objeto específico. Google recomienda este enfoque para determinados usos, aunque conviene revisar la estabilidad y la versión de las bibliotecas antes de adoptarlo en producción. :contentReference[oaicite:7]{index=7}
Un formulario profesional necesita más que varios TextField. Debe contemplar:
- Etiqueta comprensible.
- Tipo de teclado adecuado.
- Acción del teclado.
- Mensaje de ayuda.
- Estado de error.
- Validación.
- Autocompletado.
- Accesibilidad.
- Protección de datos sensibles.
- Conservación del estado necesaria.
Un campo de correo puede validar formato cuando el usuario termina de editar o intenta continuar. Mostrar el error en cada pulsación puede generar una experiencia agresiva, especialmente mientras el valor todavía está incompleto.
La validación visual tampoco sustituye la validación del servidor. Una aplicación puede impedir entradas incorrectas comunes, pero la regla definitiva debe comprobarse en la capa que procesa los datos.
Formularios con Jetpack Compose
Los formularios combinan campos, selecciones, mensajes y acciones. Su complejidad aumenta cuando existen dependencias entre valores, validación asíncrona o pasos múltiples.
Una estructura mantenible separa el estado del formulario de la representación visual. La UI recibe los valores, los errores y las acciones disponibles. El propietario del estado decide cómo validar y cuándo permitir el envío.
Un formulario de registro podría mantener:
- Correo introducido.
- Contraseña.
- Confirmación.
- Aceptación de condiciones.
- Errores por campo.
- Estado de envío.
- Error general del servidor.
No recomendamos representar toda la pantalla con variables booleanas independientes. Combinaciones incompatibles pueden producir un formulario que muestra carga y error al mismo tiempo.
Una clase de estado bien definida permite expresar situaciones coherentes. También facilita crear previews con formulario vacío, valores válidos, errores y carga.
Cuando trabajamos este tipo de ejercicios en clase, comprobamos el recorrido completo: entrada, validación, foco, teclado, envío, respuesta y restauración tras un cambio de configuración.
LazyColumn en Jetpack Compose
LazyColumn es el componente utilizado para mostrar listas verticales que pueden desplazarse. Su equivalente horizontal es LazyRow.
Estos componentes describen los elementos de la lista y Compose incorpora el contenido necesario según la posición visible. La documentación oficial relaciona su propósito con las listas eficientes y explica que LazyColumn crea una lista vertical, mientras LazyRow trabaja horizontalmente. :contentReference[oaicite:8]{index=8}
Una lista de productos puede incluir cientos de registros, pero no resulta útil construir visualmente todos a la vez. El sistema prepara los elementos necesarios para la zona visible y su entorno próximo.
Para utilizar una lista correctamente debemos considerar:
- Claves estables para identificar cada elemento.
- Tipos de contenido.
- Estado de desplazamiento.
- Carga incremental.
- Estados vacío, carga y error.
- Separadores.
- Animaciones de cambios.
- Accesibilidad.
Las claves son especialmente importantes cuando la colección cambia de orden, inserta elementos o elimina registros. Una clave basada únicamente en la posición puede asociar estado visual a un elemento equivocado.
Un error frecuente consiste en colocar una LazyColumn dentro de otro contenedor vertical desplazable sin definir correctamente sus restricciones. Esto puede generar conflictos de medida o una experiencia de desplazamiento confusa.
Jetpack Compose y RecyclerView
LazyColumn cubre muchos casos que tradicionalmente se resolvían con RecyclerView. Ambos están orientados a mostrar colecciones de forma eficiente, pero su modelo de programación es diferente.
RecyclerView utiliza adaptadores, ViewHolder y layouts de elementos. En Compose, el contenido de la lista se declara mediante funciones composables dentro de un bloque específico.
La ausencia de ViewHolder no significa que la lista no necesite optimización. Sigue siendo necesario proporcionar claves estables, evitar cálculos costosos y controlar la carga de imágenes.
Durante una migración no es obligatorio reemplazar todas las RecyclerView. Una pantalla puede continuar utilizando Views mientras otras se desarrollan con Compose.
Conviene migrar cuando existe una razón clara: simplificar la pantalla, unificar componentes, mejorar el mantenimiento o introducir un nuevo diseño. Cambiar una lista estable únicamente por modernización estética puede consumir recursos sin aportar valor al usuario.
Navigation en Jetpack Compose
Navigation Compose permite definir destinos y desplazarse entre pantallas composables utilizando la infraestructura del componente Navigation de Android.
Una aplicación puede establecer un grafo con rutas para inicio, catálogo, producto, carrito y perfil. El NavController administra el destino actual y las operaciones sobre la pila.
La documentación oficial indica que una aplicación construida completamente con Compose puede utilizar Navigation Compose. En aplicaciones híbridas con Views y Compose, la estrategia depende de la arquitectura existente y puede mantenerse temporalmente la navegación basada en fragments durante la migración. :contentReference[oaicite:9]{index=9}
Una buena implementación evita pasar el NavController a todos los componentes. Las pantallas pueden recibir funciones como abrir producto, volver o confirmar compra.
Esta separación aporta varias ventajas:
- Los componentes conocen acciones, no rutas.
- Las previews no necesitan crear navegación.
- Los tests pueden simular eventos.
- La pantalla puede reutilizarse en otro flujo.
- Las rutas quedan centralizadas.
También debemos evitar enviar objetos complejos completos como argumentos de navegación. Es preferible transmitir identificadores mínimos y recuperar la información desde una fuente de datos o un estado compartido cuando corresponda.
Parámetros y Rutas en Navigation Compose
Los parámetros permiten identificar qué contenido debe mostrar un destino. Una ficha de producto puede recibir un identificador; una pantalla de resultados puede recibir un filtro.
Los valores deben ser pequeños, serializables y estables. Pasar un objeto entero puede generar problemas al restaurar el proceso, modificar la estructura o superar límites del mecanismo de navegación.
El destino debe validar los argumentos. Una ruta manipulada, un enlace profundo o un estado restaurado pueden proporcionar un identificador inexistente.
En un caso profesional también revisamos enlaces profundos, navegación desde notificaciones, restauración de pila y comportamiento del botón Atrás.
Estado en Jetpack Compose
El estado representa la información que puede modificar lo que aparece en pantalla. Puede ser el texto escrito, el elemento seleccionado, una lista de resultados, un error o la indicación de carga.
Compose necesita observar los cambios relevantes para programar la recomposición. Una variable normal modificada en memoria no siempre provoca una actualización visual.
La documentación oficial explica la relación entre estado y composables y destaca que Compose permite expresar de forma explícita dónde se almacena y cómo se utiliza. :contentReference[oaicite:10]{index=10}
No todo el estado tiene la misma duración:
- Un estado visual local puede pertenecer al componente.
- Un valor que debe sobrevivir a recomposiciones puede recordarse.
- Un dato que debe sobrevivir a recreaciones puede necesitar guardado.
- El estado de una pantalla con lógica de negocio puede residir en un ViewModel.
- Los datos permanentes deben almacenarse en repositorios o bases de datos.
Guardar demasiado estado en la UI produce duplicaciones. Si el repositorio contiene la lista de favoritos y la pantalla mantiene una copia independiente, ambas pueden dejar de coincidir.
La recomendación es identificar una fuente de verdad y derivar el resto cuando sea posible.
Remember y RememberSaveable
remember permite conservar un valor durante las recomposiciones mientras el componente permanezca dentro de la composición. Resulta útil para estados locales y objetos vinculados a la vida visual del componente.
Sin remember, un valor creado dentro de la función puede reinicializarse cada vez que se ejecuta. Esto haría imposible mantener un campo o una selección local.
rememberSaveable amplía este comportamiento para determinados valores que deben restaurarse después de recreaciones del entorno. No está pensado para almacenar grandes estructuras ni información de negocio compleja.
La documentación de estado distingue estas herramientas y explica que remember conserva objetos en la composición, mientras rememberSaveable puede mantener valores mediante mecanismos de guardado compatibles. :contentReference[oaicite:11]{index=11}
Un campo de búsqueda puede guardar temporalmente el texto introducido. Sin embargo, los resultados de una consulta no deberían depender únicamente de rememberSaveable. Pueden reconstruirse desde el ViewModel o el repositorio.
Un error frecuente consiste en usar remember para “arreglar” cualquier valor que se reinicia. Antes de aplicarlo debemos decidir quién es el propietario correcto y cuánto tiempo debe vivir ese dato.
State Hoisting en Jetpack Compose
State hoisting consiste en mover el estado hacia un propietario superior para que un componente reciba el valor y emita eventos en lugar de gestionarlo internamente.
Un campo reutilizable puede recibir el texto y una función que comunica el cambio. De esta forma, su padre decide dónde se almacena, cómo se valida y qué otros componentes dependen del mismo valor.
La documentación recomienda elevar el estado al ancestro común más bajo entre los componentes que lo leen y lo modifican, manteniéndolo lo más cerca posible de donde se consume. :contentReference[oaicite:12]{index=12}
Elevar todo el estado hasta la actividad tampoco es una solución. Produce componentes superiores enormes y dependencias innecesarias.
La decisión práctica depende del uso:
- Si solo un componente necesita el valor, puede mantenerlo localmente.
- Si varios hermanos lo comparten, puede subir al padre común.
- Si interviene lógica de negocio, puede pertenecer a un ViewModel.
- Si debe persistir, la fuente debe estar en la capa de datos.
Un componente sin estado suele ser más fácil de probar y reutilizar, pero no todos los componentes deben ser completamente stateless. Los estados puramente visuales pueden permanecer cerca del elemento.
ViewModel con Jetpack Compose
ViewModel permite mantener el estado de una pantalla y coordinar lógica que debe sobrevivir a determinadas recreaciones. Compose puede observar el estado expuesto y enviar eventos al ViewModel.
El ViewModel no debería construir componentes ni depender de clases visuales concretas. Su función es preparar datos y gestionar acciones relacionadas con la pantalla.
Una pantalla de catálogo puede recibir un estado con:
- Productos disponibles.
- Filtros seleccionados.
- Estado de carga.
- Mensaje de error.
- Posibilidad de cargar más.
El ViewModel recibe eventos como refrescar, cambiar filtro o seleccionar favorito. Después utiliza casos de uso o repositorios y publica un nuevo estado.
Pasar el ViewModel a todos los componentes crea acoplamiento. La documentación de previews recomienda separar una función conectada al ViewModel de otra función visual que recibe datos y acciones, porque esta segunda es más reutilizable, previsualizable y testeable. :contentReference[oaicite:13]{index=13}
En nuestras clases utilizamos esta separación para detectar responsabilidades. Si la función visual necesita conocer cómo se obtiene cada dato, todavía no está suficientemente desacoplada.
MVVM con Jetpack Compose
MVVM es una arquitectura habitual en Android y puede utilizarse con Compose. La View representa la interfaz, el ViewModel gestiona el estado y las acciones, y el modelo agrupa datos y lógica.
Compose no obliga a utilizar MVVM. También puede integrarse con otras arquitecturas. Lo importante es mantener un flujo comprensible y evitar que la UI acceda directamente a todas las fuentes.
Un error frecuente consiste en crear un ViewModel por cada componente visual. Los ViewModel suelen corresponder a pantallas o flujos con estado de negocio, no a cada botón o tarjeta.
Otro error aparece cuando el ViewModel se convierte en una clase gigantesca que conoce navegación, almacenamiento, red, analítica y presentación. La arquitectura debería distribuir responsabilidades mediante repositorios, casos de uso y servicios cuando la complejidad lo justifique.
No necesitamos aplicar una estructura empresarial a una aplicación de dos pantallas. La arquitectura debe crecer con el problema. Introducir demasiadas capas desde el primer día puede dificultar el aprendizaje y el mantenimiento.
Flow y LiveData en Jetpack Compose
Flow y LiveData son mecanismos utilizados para exponer datos observables. Compose puede convertir sus emisiones en estado y actualizar la interfaz.
StateFlow resulta habitual para representar un estado actual. Un ViewModel puede publicar un objeto inmutable que la pantalla observa de forma consciente del ciclo de vida.
LiveData continúa siendo útil en proyectos existentes. Una migración a Compose no obliga a reemplazar inmediatamente todas las fuentes observables.
La decisión importante es evitar múltiples fuentes de verdad para la misma pantalla. Si una parte observa Flow, otra mantiene remember y otra consulta directamente el repositorio, el resultado puede ser difícil de sincronizar.
También debemos diferenciar estado persistente de eventos puntuales. Un mensaje de confirmación no debería reaparecer cada vez que la pantalla vuelve a observar el último estado.
En una revisión profesional comprobamos qué ocurre al rotar, navegar hacia atrás, restaurar el proceso y repetir una acción. Estos recorridos revelan eventos duplicados y estados que no se han modelado correctamente.
Coroutines y Efectos Secundarios en Compose
Las operaciones asíncronas, la navegación, las animaciones controladas y la interacción con sistemas externos necesitan ejecutarse en un contexto predecible. No deben iniciarse directamente durante cualquier recomposición.
Compose proporciona APIs de efectos para relacionar estas operaciones con el ciclo de vida de la composición. Entre ellas se encuentran mecanismos para ejecutar trabajo cuando cambian determinadas claves, disponer de un ámbito de coroutines o realizar limpieza.
La documentación define un efecto secundario como un cambio fuera del ámbito de una función composable y recomienda utilizar APIs específicas para ejecutarlo de forma controlada. :contentReference[oaicite:14]{index=14}
Un caso válido sería mostrar un snackbar cuando se recibe una confirmación. Otro sería iniciar una animación cuando cambia un estado.
Una llamada para guardar un pedido debería iniciarse como respuesta a un evento del usuario y gestionarse desde la capa adecuada. No debería ejecutarse simplemente porque la pantalla ha leído un estado.
Un error frecuente consiste en utilizar efectos para corregir una arquitectura confusa. Cuando varios efectos observan variables y modifican otras variables, aparece una cadena difícil de seguir. Antes de añadir otro efecto conviene revisar si el flujo podría expresarse mediante eventos y estado derivado.
Material 3 en Jetpack Compose
Material 3 proporciona componentes, colores, tipografías, formas y patrones de interacción basados en el sistema de diseño de Google. Compose dispone de una implementación específica para trabajar con este modelo.
El tema permite centralizar decisiones visuales. Los componentes pueden utilizar colores semánticos como primary, surface o error en lugar de valores escritos de forma independiente.
Una configuración habitual incluye:
- Esquema de color claro.
- Esquema de color oscuro.
- Color dinámico cuando proceda.
- Tipografía.
- Formas.
- Estilos propios del producto.
El tema no debería limitarse a cambiar el color principal. Debe reflejar jerarquía, contraste, estados interactivos y coherencia entre componentes.
En proyectos reales detectamos muchas pantallas que utilizan Material 3, pero aplican tamaños y colores distintos en cada componente. El sistema de diseño pierde utilidad cuando las decisiones continúan duplicadas.
También conviene diferenciar el componente de la marca. Podemos envolver controles Material dentro de componentes propios para establecer variantes consistentes sin reescribir su comportamiento desde cero.
Temas y Diseño en Jetpack Compose
Un tema organiza las decisiones que afectan a toda la aplicación. Permite que una tarjeta, un botón y una barra compartan el mismo lenguaje visual.
La tipografía debe utilizar estilos semánticos. No es recomendable asignar manualmente tamaño, peso y altura de línea en cada Text. Si el sistema cambia, habría que revisar toda la aplicación.
Las formas también comunican jerarquía. Utilizar bordes redondeados diferentes sin criterio genera una interfaz inconsistente.
El modo oscuro no consiste en invertir colores. Debe revisarse contraste, elevación, imágenes, estados deshabilitados y legibilidad.
En una revisión profesional preparamos componentes de ejemplo con distintas situaciones:
- Texto corto y largo.
- Datos ausentes.
- Estado de error.
- Modo oscuro.
- Fuente ampliada.
- Pantalla estrecha y ancha.
Esta validación temprana evita descubrir durante el desarrollo que un componente solo funciona con el contenido perfecto del diseño inicial.
Animaciones en Jetpack Compose
Compose incluye APIs para animar valores, visibilidad, tamaño, contenido, posición y transiciones. Estas herramientas permiten explicar cambios y mejorar la continuidad visual.
Una animación útil comunica causa y efecto. Por ejemplo, expandir una tarjeta puede mostrar dónde aparece la información adicional; animar el estado de un botón puede confirmar una acción.
La documentación oficial incluye componentes como AnimatedVisibility para controlar la aparición y desaparición de contenido y distintas APIs para animar cambios de valores y contenido. :contentReference[oaicite:15]{index=15}
Las animaciones no deberían utilizarse para ocultar una espera larga ni decorar cada interacción. Un exceso aumenta la carga visual y puede perjudicar a personas sensibles al movimiento.
También debemos medir su coste. Una animación que obliga a recalcular un layout complejo en cada frame puede provocar saltos.
La recomendación práctica es comenzar por la intención: qué cambio necesita comprender el usuario. Después elegimos la API más simple que pueda representarlo.
Jetpack Compose y Arquitectura de Aplicaciones
Compose modifica la capa visual, pero no elimina la necesidad de una arquitectura. La aplicación sigue necesitando separar datos, lógica y presentación.
Una estructura frecuente puede incluir:
- Capa de interfaz con composables y ViewModel.
- Capa de dominio con casos de uso cuando sean necesarios.
- Capa de datos con repositorios y fuentes.
- Servicios de red.
- Base de datos local.
- Gestión de dependencias.
La documentación oficial señala que adoptar Compose no altera por sí mismo las capas de datos o negocio. El toolkit encaja especialmente bien con flujo unidireccional y propietarios de estado. :contentReference[oaicite:16]{index=16}
No existe una estructura de carpetas universal. Una aplicación pequeña puede organizarse por funcionalidad sin crear decenas de abstracciones. Un producto con varios equipos puede necesitar módulos separados.
En una arquitectura por funcionalidades, cada sección reúne pantalla, ViewModel, estado y componentes relacionados. Los elementos realmente compartidos se trasladan a módulos comunes.
Un error habitual consiste en crear una carpeta global de componentes donde terminan cientos de elementos sin relación. La reutilización debe demostrarse mediante usos reales, no suponerse desde el primer día.
Clean Architecture con Jetpack Compose
Clean Architecture busca separar responsabilidades y orientar las dependencias hacia reglas de negocio independientes de detalles externos. Puede utilizarse con Compose, pero no debe aplicarse como una plantilla rígida.
Una pantalla puede enviar una acción al ViewModel. El ViewModel llama a un caso de uso. El caso de uso utiliza un repositorio. El repositorio decide si consulta red, base local o caché.
Esta separación resulta útil cuando existe lógica compleja, varias fuentes o necesidad de pruebas independientes. En una aplicación sencilla, crear una clase para cada operación puede añadir ruido.
En proyectos reales evaluamos tres factores:
- Complejidad del dominio.
- Número de desarrolladores.
- Probabilidad de cambio.
Una arquitectura debe permitir cambiar una API sin reescribir la pantalla. También debería permitir probar una regla sin iniciar un emulador.
Lo que no debe ocurrir es que la función composable reciba una respuesta de red sin transformar y decida allí cómo interpretar códigos, permisos o reglas comerciales.
Hilt con Jetpack Compose
Hilt facilita la inyección de dependencias en aplicaciones Android. Puede proporcionar repositorios, servicios, bases de datos y ViewModel sin construir manualmente toda la jerarquía.
Compose se integra con ViewModel gestionados mediante Hilt, pero los componentes visuales no deberían solicitar dependencias indiscriminadamente.
La inyección suele mantenerse cerca de los límites de la pantalla. Una función conectada obtiene el ViewModel y pasa estado y eventos a una función visual independiente.
Esta separación evita que un botón dependa del contenedor de inyección. También facilita previews, porque la función visual puede recibir datos de muestra sin crear repositorios.
Un error frecuente consiste en utilizar la inyección para ocultar dependencias globales. Que una clase pueda obtenerse automáticamente no significa que deba estar disponible en cualquier parte.
Room con Jetpack Compose
Room proporciona una capa de acceso a bases de datos locales sobre SQLite. Compose puede observar datos expuestos por repositorios que utilizan Room.
Una aplicación de tareas puede guardar registros localmente y exponer una colección mediante Flow. Cuando la base cambia, el repositorio publica información actualizada, el ViewModel crea un nuevo estado y la lista se recompone.
La interfaz no debería ejecutar consultas directamente. El repositorio centraliza el acceso, la transformación y la estrategia de datos.
Debemos evitar cargar grandes cantidades sin paginación o filtrar todo en memoria cuando la base puede hacerlo de forma eficiente.
También conviene separar entidades de persistencia y modelos visuales cuando sus responsabilidades difieren. Una columna interna no tiene por qué llegar hasta la tarjeta mostrada al usuario.
Retrofit con Jetpack Compose
Retrofit se utiliza habitualmente para consumir APIs HTTP. Su relación con Compose se produce a través de la arquitectura, no porque deba llamarse desde un composable.
El repositorio invoca el servicio, interpreta la respuesta y devuelve un resultado. El ViewModel transforma ese resultado en estado de pantalla.
La UI puede representar:
- Carga inicial.
- Contenido disponible.
- Resultado vacío.
- Error recuperable.
- Error sin conexión.
- Actualización parcial.
Un modelo basado únicamente en un booleano de carga y una lista no puede expresar todas estas situaciones de forma segura.
También debemos controlar cancelación, reintentos e idempotencia. Pulsar varias veces un botón no debería crear varios pedidos.
En una revisión profesional simulamos respuestas lentas, errores y datos incompletos. Una pantalla que solo se prueba con una API perfecta no está terminada.
Imágenes en Jetpack Compose
Compose incluye el componente Image para mostrar recursos visuales, pero las imágenes remotas suelen requerir una biblioteca que gestione descarga, caché y decodificación.
La imagen debe tener un propósito definido. Si transmite información, necesita una descripción accesible. Si es decorativa, no debería introducir ruido para tecnologías de asistencia.
También debemos considerar:
- Relación de aspecto.
- Recorte.
- Estado de carga.
- Error.
- Tamaño solicitado.
- Memoria.
- Caché.
- Conexiones lentas.
Cargar una imagen original de gran resolución para mostrar una miniatura aumenta memoria y red. La petición debería ajustarse al uso real cuando la infraestructura lo permita.
En una lista, la estabilidad de elementos y la gestión de imágenes influyen directamente en la fluidez. Optimizar solo la composición no resuelve una decodificación excesiva.
Interfaces Adaptativas con Jetpack Compose
Android funciona en móviles, tablets, plegables, escritorios, televisores y ventanas de tamaños variables. Una interfaz profesional no debería asumir una única anchura.
Compose facilita construir layouts que reaccionan al espacio disponible mediante condiciones, componentes adaptativos y patrones de varios paneles.
La documentación actual de Material 3 Adaptive incluye herramientas para clases de tamaño, navegación, layouts multipanel y posturas de dispositivos plegables. :contentReference[oaicite:17]{index=17}
Una aplicación de correo puede mostrar únicamente la lista en un móvil y presentar lista y detalle simultáneamente en una tablet.
Adaptar no significa estirar el mismo contenido hasta llenar la pantalla. Puede ser necesario cambiar navegación, densidad, jerarquía y número de paneles.
En nuestras revisiones comprobamos ventanas estrechas, orientación horizontal, modo multiventana y fuente ampliada. Utilizar solo dos modelos de dispositivo en Preview no cubre todas las combinaciones.
Accesibilidad en Jetpack Compose
La accesibilidad permite que la aplicación pueda utilizarse con lectores de pantalla, control por interruptores, ampliación y otras tecnologías de asistencia.
Compose utiliza una capa semántica que describe el significado y el comportamiento de los elementos. Los componentes Material aportan parte de esta información, pero los componentes personalizados requieren revisión.
La documentación oficial señala que Compose proporciona bases y herramientas para construir interfaces accesibles, y que Material, Foundation y Compose UI incluyen muchas prácticas integradas. :contentReference[oaicite:18]{index=18}
Debemos comprobar:
- Descripciones de imágenes e iconos.
- Roles de los controles.
- Orden de navegación.
- Tamaño de áreas táctiles.
- Contraste.
- Mensajes de error.
- Escalado de texto.
- Estados seleccionados.
- Contenido dinámico.
Un icono de papelera sin descripción puede resultar invisible para un lector de pantalla. Una tarjeta completa con varias acciones puede generar un recorrido confuso si sus semánticas no se agrupan correctamente.
La prueba manual con TalkBack sigue siendo necesaria. Los tests automatizados detectan problemas, pero no sustituyen experimentar el flujo completo con tecnologías de asistencia. :contentReference[oaicite:19]{index=19}
Previews en Jetpack Compose
Las previews permiten renderizar funciones composables dentro de Android Studio sin iniciar siempre una aplicación completa. Resultan útiles para revisar componentes aislados y estados visuales.
Una preview puede mostrar distintas configuraciones de dispositivo, tema, idioma o escala tipográfica. También puede recibir datos de muestra.
La documentación oficial explica que la anotación Preview permite visualizar composables en la vista de diseño y configurar diferentes variantes. :contentReference[oaicite:20]{index=20}
Para aprovecharlas, el componente debe recibir estado y eventos mediante parámetros. Una función que crea internamente un ViewModel con varias dependencias será más difícil de previsualizar.
Un conjunto profesional de previews puede cubrir:
- Estado normal.
- Carga.
- Error.
- Contenido vacío.
- Texto largo.
- Modo oscuro.
- Fuente grande.
- Pantalla compacta.
- Pantalla expandida.
Las previews no reemplazan el emulador ni el dispositivo. No disponen de todas las APIs del entorno real y no validan rendimiento, navegación o integración completa.
Testing en Jetpack Compose
Compose ofrece APIs para localizar nodos semánticos, ejecutar acciones y comprobar propiedades de la interfaz. Esto permite validar que una pantalla responde correctamente a un estado y a la interacción.
Un test puede verificar que el botón está deshabilitado cuando el formulario es inválido, que aparece un error después de una respuesta o que seleccionar un elemento emite la acción correcta.
La documentación oficial recomienda probar el comportamiento de los layouts Compose para detectar errores y mejorar la calidad. :contentReference[oaicite:21]{index=21}
Los tests no deberían depender de detalles visuales internos que pueden cambiar sin modificar el comportamiento. Es preferible localizar controles por su semántica, texto o identificadores de prueba utilizados con criterio.
También conviene separar:
- Tests unitarios del ViewModel.
- Tests de funciones de transformación.
- Tests de componentes visuales.
- Tests de navegación.
- Tests de integración.
- Pruebas manuales de accesibilidad.
Una interfaz declarativa facilita probar estados concretos. Podemos proporcionar un estado de error directamente a la pantalla sin provocar primero el fallo real de una API.
Rendimiento en Jetpack Compose
Compose ofrece buen rendimiento cuando la interfaz está diseñada siguiendo su modelo. Los problemas suelen aparecer por trabajo excesivo durante recomposición, listas mal identificadas, cálculos repetidos o estados demasiado amplios.
La documentación de rendimiento organiza la actualización en fases de composición, layout y dibujo. Comprender qué fase se repite ayuda a seleccionar la optimización correcta. :contentReference[oaicite:22]{index=22}
Algunas buenas prácticas son:
- Mantener composables rápidos.
- Evitar operaciones de entrada y salida durante la composición.
- Proporcionar claves estables en listas.
- Derivar valores de forma eficiente.
- No leer estado más arriba de lo necesario.
- Evitar crear objetos costosos repetidamente.
- Medir en compilaciones adecuadas.
- Analizar recorridos reales.
- Utilizar herramientas de perfilado.
Un error común consiste en evaluar el rendimiento únicamente durante una compilación de desarrollo. El comportamiento de depuración no representa necesariamente la versión de producción.
Tampoco debemos utilizar anotaciones de estabilidad o estructuras inmutables sin comprender el modelo. Marcar incorrectamente un tipo puede ocultar cambios y producir errores.
La optimización profesional comienza con un síntoma observable, una medición y una hipótesis. No con la eliminación indiscriminada de composables.
Jetpack Compose vs XML
XML y Compose son dos formas de construir interfaces Android. XML describe layouts y el código manipula Views. Compose utiliza funciones Kotlin para describir la UI según el estado.
Las principales diferencias son:
- XML: separa layout y lógica en archivos diferentes.
- Compose: define la estructura mediante Kotlin.
- XML: utiliza Views y referencias a elementos.
- Compose: utiliza funciones composables.
- XML: suele requerir actualizar propiedades manualmente.
- Compose: vuelve a representar la UI cuando cambia el estado.
- XML: cuenta con un ecosistema histórico muy amplio.
- Compose: ofrece herramientas modernas de preview, estado y tematización.
Compose no es automáticamente mejor en todos los proyectos. Una aplicación estable basada en Views puede mantener pantallas sin necesidad de reescribirlas.
Para nuevas interfaces, Compose suele simplificar la construcción y la reutilización. Para migraciones, la interoperabilidad permite combinar ambos modelos. La documentación oficial confirma que las aplicaciones híbridas pueden incorporar Compose dentro de Views y Views dentro de Compose. :contentReference[oaicite:23]{index=23}
La decisión debe considerar mantenimiento, experiencia del equipo, cobertura de tests y valor del cambio. Reescribir toda la aplicación de una vez aumenta el riesgo.
Jetpack Compose vs Flutter
Jetpack Compose y Flutter utilizan modelos declarativos, pero resuelven problemas diferentes. Compose está integrado en Android y utiliza Kotlin. Flutter emplea Dart y busca compartir una base de código entre varias plataformas.
Compose accede de forma directa al ecosistema Android y puede introducirse gradualmente en una aplicación existente. Flutter suele implicar adoptar su runtime, su sistema de widgets y una arquitectura multiplataforma.
La elección depende de:
- Plataformas objetivo.
- Conocimiento del equipo.
- Integración nativa necesaria.
- Reutilización de código.
- Dependencias existentes.
- Mantenimiento a largo plazo.
No recomendamos elegir una tecnología únicamente por la cantidad de código compartido. Una base común puede reducir duplicación y, al mismo tiempo, aumentar la complejidad de comportamientos específicos de cada plataforma.
Jetpack Compose vs SwiftUI
SwiftUI es el framework declarativo de Apple para construir interfaces en sus plataformas. Jetpack Compose cumple un papel similar dentro de Android.
Ambos trabajan con estado, componentes declarativos y previews, pero utilizan lenguajes, herramientas y ciclos de vida diferentes.
Una persona que conoce SwiftUI reconocerá ideas generales, aunque no debe trasladar cada patrón literalmente. La navegación, el ciclo de vida y las bibliotecas de plataforma tienen particularidades.
Para productos con equipos separados de Android e iOS, ambos frameworks permiten aplicar principios visuales comunes sin obligar a compartir toda la implementación.
Jetpack Compose vs React Native
React Native permite construir aplicaciones para varias plataformas utilizando JavaScript o TypeScript y una arquitectura basada en componentes. Jetpack Compose utiliza Kotlin y se orienta a interfaces nativas Android.
React Native puede ser adecuado cuando el equipo busca compartir desarrollo entre Android e iOS. Compose resulta natural cuando la prioridad es Android, su ecosistema y sus APIs.
La comparación debe incluir integración con SDK, rendimiento real, experiencia del equipo, soporte de bibliotecas y coste de mantenimiento.
Una tecnología multiplataforma no elimina el conocimiento nativo. Permisos, notificaciones, procesos en segundo plano y publicación continúan teniendo diferencias.
Ventajas de Jetpack Compose
- Modelo declarativo: la interfaz se deriva del estado.
- Integración con Kotlin: no necesita definir toda la jerarquía en XML.
- Componentes reutilizables: una función puede representar una pieza completa.
- Previews: permiten revisar estados dentro de Android Studio.
- Tematización: facilita centralizar colores, tipografías y formas.
- Interoperabilidad: puede convivir con Views.
- Testing: la UI puede probarse a partir de estado y semántica.
- Animaciones: dispone de APIs declarativas.
- Diseño adaptativo: facilita reaccionar al espacio disponible.
- Ecosistema: se integra con Navigation, ViewModel, Room y otras bibliotecas Android.
La productividad aumenta especialmente cuando el equipo crea un sistema de componentes y una arquitectura clara. Si cada pantalla gestiona estado y estilos de forma diferente, las ventajas se reducen.
El modelo declarativo también facilita revisar todos los estados posibles de una pantalla. Podemos preparar ejemplos de carga, error y contenido sin ejecutar el recorrido completo.
Desventajas y Limitaciones de Jetpack Compose
- Requiere aprender un modelo mental diferente.
- El estado mal diseñado puede provocar inconsistencias.
- Las recomposiciones pueden generar dudas iniciales.
- Las bibliotecas evolucionan y necesitan seguimiento.
- Algunos componentes avanzados requieren trabajo adicional.
- Una migración completa puede ser costosa.
- Las previews tienen limitaciones.
- El código puede crecer si no se separan responsabilidades.
- Las integraciones antiguas pueden necesitar interoperabilidad.
Compose tampoco evita los problemas habituales de Android: ciclo de vida, permisos, conectividad, almacenamiento, accesibilidad y variedad de dispositivos.
Una interfaz declarativa puede ser difícil de mantener si toda la lógica termina dentro de una función de cientos de líneas.
También existe el riesgo de incorporar APIs experimentales sin revisar su estabilidad. En un proyecto real conviene comprobar el estado de cada biblioteca y planificar actualizaciones.
Ejemplo Práctico de Jetpack Compose en un Ecommerce
Imaginemos una aplicación que muestra productos, permite filtrarlos y añadirlos al carrito. La pantalla principal recibe un estado del ViewModel.
Al iniciar, el estado indica carga y la interfaz muestra un indicador. El ViewModel solicita los productos al repositorio. El repositorio consulta una API y puede combinar los resultados con favoritos guardados localmente.
Cuando llegan los datos, el estado incluye la lista, los filtros y el número de productos en el carrito. LazyColumn muestra cada registro mediante una tarjeta reutilizable.
La tarjeta recibe nombre, precio, imagen, disponibilidad y favorito. También recibe funciones para abrir la ficha y cambiar el favorito. No conoce Retrofit, Room ni Navigation.
Al seleccionar un producto, la pantalla comunica la acción al nivel de navegación. La ficha recibe el identificador y el ViewModel recupera el contenido correspondiente.
El botón de compra no ejecuta directamente una llamada desde el composable. Emite un evento. El ViewModel valida stock, actualiza el repositorio y modifica el estado.
Si la operación termina correctamente, la UI puede mostrar un snackbar. Si falla, muestra un mensaje y permite reintentar.
En una tablet, la interfaz puede mostrar catálogo y detalle simultáneamente. Los componentes básicos continúan siendo los mismos; cambia la composición de la pantalla según el espacio.
Este caso demuestra que Compose construye la experiencia visual, pero la calidad depende de arquitectura, estados, errores, navegación y datos.
Metodología para Implementar Jetpack Compose
1. Definir los Estados de la Pantalla
Antes de construir componentes, identificamos qué situaciones debe representar la interfaz: carga, contenido, vacío, error y operaciones parciales.
También definimos datos y acciones. Esta fase evita diseñar únicamente el estado perfecto.
2. Separar Componentes Visuales
Dividimos la pantalla en piezas con responsabilidad concreta. Cada componente recibe la información mínima y emite acciones.
No extraemos cada Text a una función. La reutilización debe responder a unidades visuales o de comportamiento reconocibles.
3. Diseñar el Flujo de Datos
Decidimos quién posee cada estado, cómo se actualiza y qué capa procesa cada evento.
El estado visual local permanece cerca del componente. La lógica de pantalla se coordina desde el ViewModel y los datos persistentes pertenecen al repositorio.
4. Crear Previews Representativas
Preparamos estados normales, vacíos, largos y erróneos. Esto permite detectar problemas antes de conectar la infraestructura.
5. Integrar Navegación
Las rutas se centralizan y las pantallas reciben funciones. Los argumentos se reducen a identificadores o valores necesarios.
6. Conectar Datos
El ViewModel consume repositorios y publica estado. La UI no conoce detalles de red o base de datos.
7. Probar Interacciones
Validamos escritura, foco, botones, listas, errores, navegación y restauración.
También comprobamos pulsaciones repetidas, conectividad lenta y datos incompletos.
8. Revisar Accesibilidad
Utilizamos servicios de asistencia, escalas de fuente y tamaños distintos. Corregimos semántica, contraste y orden.
9. Medir Rendimiento
Analizamos recorridos reales, listas y animaciones. Optimizamos después de identificar el cuello de botella.
10. Planificar Mantenimiento
Registramos versiones de bibliotecas, componentes propios y decisiones de arquitectura. Las actualizaciones deben probarse antes de llegar a producción.
Errores Frecuentes en Jetpack Compose
Guardar Todo el Estado con Remember
Remember no sustituye un ViewModel ni una fuente persistente. El estado puede perderse al salir de la composición.
Pasar el ViewModel a Todos los Componentes
Esto acopla la UI a una implementación y dificulta previews y tests. Es mejor pasar datos y eventos.
Ejecutar Llamadas Durante la Composición
Una recomposición puede repetir la operación. Las llamadas deben gestionarse como respuesta a eventos o mediante efectos controlados.
Ignorar el Orden de los Modifiers
El orden cambia medida, dibujo e interacción. Cuando el resultado no es el esperado, debemos revisar la cadena completa.
No Utilizar Claves en Listas
Los elementos pueden perder identidad cuando la colección cambia. Las claves estables ayudan a conservar el estado correcto.
Crear Composables Enormes
Una función que contiene toda la pantalla, validación, navegación y datos es difícil de mantener. Conviene separar por responsabilidades.
Optimizar sin Medir
No toda recomposición es un problema. Las decisiones deben basarse en herramientas y recorridos reales.
Diseñar Solo para un Dispositivo
La pantalla puede romperse con fuentes grandes, ventanas estrechas o tablets. Debemos validar variedad de tamaños.
Utilizar Componentes Personalizados sin Semántica
Un control puede parecer correcto visualmente y resultar inutilizable con lector de pantalla.
Migrar Toda la Aplicación de una Vez
Una migración masiva aumenta riesgo y dificulta comparar resultados. La interoperabilidad permite avanzar por funcionalidades.
Confundir Estado con Evento
Un estado se puede volver a observar; un evento puntual no debería repetirse automáticamente. La diferencia afecta a navegación y mensajes.
Copiar Arquitecturas Demasiado Complejas
Una estructura diseñada para cientos de módulos puede perjudicar una aplicación pequeña. La arquitectura debe responder al problema real.
Checklist para Revisar un Proyecto Jetpack Compose
- Las pantallas representan carga, error, vacío y contenido.
- El estado tiene un propietario claro.
- Los componentes reciben solo los datos necesarios.
- Los eventos se comunican mediante funciones.
- Los composables no realizan trabajo costoso.
- Las llamadas externas no se ejecutan durante recomposiciones arbitrarias.
- Las listas utilizan claves estables.
- Los formularios muestran errores comprensibles.
- Los datos sensibles se protegen.
- La navegación no se distribuye por todos los componentes.
- Los argumentos de ruta son mínimos.
- La lógica de negocio no vive en la UI.
- Los ViewModel no conocen componentes visuales.
- Existen previews con estados representativos.
- Se prueba modo oscuro.
- Se prueba fuente ampliada.
- Se prueban diferentes tamaños de ventana.
- Los iconos informativos tienen descripción.
- Los componentes personalizados incluyen semántica.
- Los botones respetan tamaños táctiles.
- Las animaciones tienen una función comprensible.
- El rendimiento se mide en condiciones adecuadas.
- Las dependencias se actualizan de forma controlada.
- Las APIs experimentales están identificadas.
- La migración desde Views sigue una estrategia gradual.
Preguntas Frecuentes sobre Jetpack Compose
¿Qué Es Jetpack Compose?
Jetpack Compose es el toolkit declarativo de Android para construir interfaces nativas mediante Kotlin y funciones composables.
¿Para Qué Sirve Jetpack Compose?
Sirve para crear pantallas, componentes, formularios, listas, navegación, animaciones e interfaces adaptativas en aplicaciones Android.
¿Jetpack Compose Es un Lenguaje?
No. Utiliza Kotlin como lenguaje y proporciona APIs y herramientas para desarrollar la interfaz.
¿Jetpack Compose Sustituye a Kotlin?
No. Compose se escribe con Kotlin. Ambos conceptos cumplen funciones diferentes.
¿Jetpack Compose Sustituye a XML?
Puede utilizarse en lugar de layouts XML para nuevas interfaces, pero ambos sistemas pueden convivir dentro de una aplicación.
¿Qué Es un Composable?
Es una función preparada para describir una parte de la interfaz. Puede recibir datos, emitir elementos visuales y llamar a otros composables.
¿Qué Es la Recomposición?
Es el proceso mediante el cual Compose vuelve a ejecutar las funciones necesarias cuando cambia el estado utilizado por la interfaz.
¿Qué Es Remember?
Es un mecanismo para conservar un valor durante las recomposiciones mientras el componente permanezca en la composición.
¿Qué Diferencia Hay entre Remember y RememberSaveable?
Remember conserva el valor durante la vida de la composición. RememberSaveable puede restaurar determinados valores después de recreaciones compatibles.
¿Qué Es State Hoisting?
Es el patrón de mover el estado a un propietario superior para que el componente reciba el valor y comunique eventos.
¿Qué Es Scaffold?
Es un componente que ayuda a organizar la estructura general de una pantalla con barras, botones flotantes, snackbars y contenido.
¿Qué Es LazyColumn?
Es un componente para mostrar listas verticales desplazables de forma eficiente.
¿Qué Es Navigation Compose?
Es la integración del componente Navigation con destinos construidos mediante funciones composables.
¿Se Puede Usar ViewModel con Compose?
Sí. ViewModel se utiliza habitualmente para gestionar el estado y las acciones de una pantalla.
¿Compose Funciona con LiveData?
Sí. También puede trabajar con Flow, StateFlow y otros tipos observables.
¿Se Puede Utilizar Room con Compose?
Sí. Room suele integrarse mediante repositorios y ViewModel que exponen datos observables a la interfaz.
¿Se Puede Utilizar Retrofit con Compose?
Sí. La llamada normalmente se realiza desde la capa de datos y la UI observa el estado publicado por el ViewModel.
¿Jetpack Compose Permite Animaciones?
Sí. Incluye APIs para visibilidad, valores, contenido, tamaño, movimiento y transiciones.
¿Jetpack Compose Es Multiplataforma?
Jetpack Compose está orientado a Android. Existen tecnologías relacionadas para compartir interfaces en otras plataformas, pero no deben confundirse automáticamente con todas las APIs de Android.
¿Jetpack Compose Es Mejor que Flutter?
Depende del proyecto. Compose ofrece integración nativa con Android y Kotlin. Flutter prioriza una base de código multiplataforma mediante Dart.
¿Jetpack Compose Es Mejor que XML?
Para nuevas interfaces suele ofrecer un modelo moderno y productivo. Una aplicación estable con Views puede continuar funcionando o migrarse gradualmente.
¿Es Necesario Utilizar MVVM?
No. Compose no obliga a una arquitectura concreta, aunque necesita una gestión clara del estado y de las responsabilidades.
¿Cómo Se Prueba una Pantalla Compose?
Puede probarse proporcionando estados, buscando nodos semánticos, ejecutando acciones y comprobando propiedades o eventos.
¿Se Puede Introducir Compose en una App Existente?
Sí. Las APIs de interoperabilidad permiten incorporar contenido Compose dentro de Views y mantener ambos sistemas durante una migración.
¿Cuándo No Conviene Migrar a Compose?
No conviene reescribir una pantalla estable si el coste, el riesgo y el mantenimiento no aportan un beneficio claro al producto o al equipo.
Conclusión: ¿Qué es Jetpack Compose?
Jetpack Compose es el toolkit declarativo moderno para construir interfaces Android mediante Kotlin. Sustituye la manipulación manual de Views por un modelo donde la pantalla representa el estado actual de la aplicación.
Sus funciones composables permiten crear componentes reutilizables, listas, formularios, sistemas de navegación, animaciones e interfaces adaptativas. La integración con ViewModel, Flow, Navigation, Room, Retrofit y Material 3 permite utilizarlo dentro de arquitecturas Android completas.
La dificultad principal no está en mostrar un Button o un TextField. El reto consiste en decidir dónde vive el estado, cómo se comunican los eventos, qué funciones pueden recomponerse y qué responsabilidades pertenecen a la UI.
En proyectos reales conviene comenzar por los estados de pantalla, separar componentes visuales, crear previews, validar accesibilidad y conectar después las fuentes de datos. Esta metodología permite detectar errores antes de que la interfaz dependa de toda la infraestructura.
Compose puede convivir con XML y Views, por lo que una migración gradual suele ser más segura que una reescritura completa. Cada pantalla debe migrarse por una razón concreta: reducir complejidad, mejorar el sistema de componentes, facilitar mantenimiento o resolver nuevas necesidades.
Cuando se aplica con un flujo de datos claro, componentes desacoplados y una arquitectura proporcionada al proyecto, Jetpack Compose no solo cambia cómo se dibuja una pantalla. También mejora la forma de modelar, revisar, probar y mantener la experiencia completa de una aplicación Android.
