Cuando nos ponemos manos a la obra con el desarrollo de aplicaciones modernas, es muy común que necesitemos mostrar una cantidad ingente de datos, ya sea una lista de contactos, un feed de noticias o una galería de fotos. Si intentamos meter todo eso en un contenedor simple, la app se quedaría colgada antes de que el usuario pudiera siquiera hacer un gesto de scroll, ya que el sistema intentaría renderizar miles de elementos a la vez, fundiendo la batería y saturando la memoria del dispositivo.
Para evitar que nuestra interfaz se vuelva lenta como una tortuga, Jetpack Compose nos ofrece herramientas llamadas componentes diferidos o «lazy». Básicamente, estos componentes son los herederos espirituales del antiguo RecyclerView, pero con una sintaxis mucho más limpia y potente que hace que programar listados sea, sinceramente, pan comido comparado con el pasado.
¿Cuándo usar Column y Row frente a las versiones Lazy?
Si tienes un grupo pequeño de elementos que sabes que no van a crecer mucho y que no requieren desplazamiento, usar Column o Row es la opción más directa. Simplemente iteras la lista con un forEach y listo. Incluso puedes añadir el modificador verticalScroll() para que la pantalla se mueva, pero ojo: esto es una trampa para el rendimiento si la lista es larga, porque todos los elementos se componen independientemente de si el usuario los está viendo o no.
Aquí es donde entran en juego LazyColumn y LazyRow. Estos componentes solo renderizan los elementos que están dentro del viewport (el área visible). En cuanto un elemento sale de pantalla, el sistema lo «recicla» para usarlo en el siguiente que va a entrar, lo que garantiza que la app vuele aunque tengas diez mil registros.
Dominando el DSL de LazyListScope
A diferencia de los diseños tradicionales, los componentes diferidos no usan un bloque de contenido normal, sino un LazyListScope. Esto es básicamente un lenguaje específico de dominio (DSL) que nos permite definir la estructura de la lista de forma muy flexible. Tenemos varias funciones clave: item() para añadir un elemento único (como un encabezado) y items() para volcar una colección completa de datos.
Si necesitas saber en qué posición estás, existe la variante itemsIndexed(), que te devuelve el índice del elemento actual. Esto es superútil para cambiar el color de fondo de las filas o gestionar clics específicos basados en la posición.
Cuadrículas: LazyVerticalGrid y LazyHorizontalGrid
A veces una lista simple no basta y necesitamos organizar el contenido en celdas. Para ello, Compose dispone de LazyVerticalGrid y LazyHorizontalGrid. Lo más potente aquí es la configuración de las columnas. Podemos usar GridCells.Fixed si queremos un número exacto de columnas, o GridCells.Adaptive, que es la joya de la corona, ya que define un tamaño mínimo para la celda y ajusta la cantidad de columnas automáticamente según el ancho de la pantalla del móvil o tablet.
Si el diseño es más irregular, como el estilo Pinterest, tenemos las cuadrículas escalonadas (StaggeredGrid). Estas permiten que los elementos tengan alturas o anchuras diferentes sin dejar huecos vacíos molestos, manteniendo la eficiencia del renderizado diferido.
Optimización del rendimiento y experiencia de usuario
Para que la lista no se sienta «tosca», es fundamental gestionar el espaciado. Podemos usar contentPadding para dejar un margen general alrededor de toda la lista y Arrangement.spacedBy() para inyectar espacio exacto entre cada ítem. Pero si queremos ir al siguiente nivel, debemos hablar de las claves (keys).
Por defecto, Compose usa la posición como clave. Si mueves un elemento de sitio, el estado se pierde. Al proporcionar una clave estable y única (como el ID de la base de datos), Compose sabe exactamente qué elemento es cuál, permitiendo que el estado recordado se mantenga y que las animaciones de movimiento funcionen a la perfección con el modificador animateItem().
Gestión avanzada del estado y el scroll
Si queremos que nuestra app reaccione a lo que el usuario hace mientras desliza, necesitamos elevar el LazyListState mediante rememberLazyListState(). Con esto podemos saber cuál es el primer elemento visible y así, por ejemplo, mostrar un botón de «volver arriba» solo cuando el usuario haya bajado un poco en la lista.
Para evitar que la interfaz se redibuje constantemente, es recomendable envolver estas comprobaciones en un derivedStateOf(). Además, si queremos controlar el scroll programáticamente, podemos usar animateScrollToItem() dentro de una corrutina para crear transiciones suaves hacia una posición concreta.
Consejos de oro para evitar errores comunes
Un error típico es intentar anidar un LazyColumn dentro de otro componente que ya tiene scroll en la misma dirección; esto lanzará una IllegalStateException. La solución es simple: mete todo dentro de un único LazyColumn y usa item() e items() para organizar las diferentes secciones.
Otro punto crítico es evitar los elementos de 0 píxeles. Si cargas imágenes asincrónicamente sin definir un tamaño previo, el componente lazy pensará que todo cabe en pantalla y renderizará todo de golpe. Es vital establecer un tamaño predeterminado o un marcador de posición (placeholder) para mantener la estabilidad del scroll.
Finalmente, para exprimir al máximo la CPU, conviene usar el parámetro contentType. Esto le dice a Compose qué tipo de elemento es cada uno, permitiendo que reutilice las composiciones solo entre elementos de la misma estructura, evitando así esfuerzos innecesarios de renderizado.
Tener una interfaz fluida depende de elegir el contenedor adecuado según la cantidad de datos, optimizar la reutilización mediante claves y tipos de contenido, y manejar correctamente los estados de desplazamiento para que la navegación sea intuitiva y sin tirones. Comparte la información y ayuda a más personas a que conozcan del tema.
Continúar leyendo...