# Cómo paginar datos de servidor en una lista virtual

> Cargar páginas bajo demanda con PagedDataAdapter (caché LRU) sobre VirtualListView.

Source: https://crossfly.dev/es/docs/cookbook/lists/paginacao — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# Cómo paginar datos de servidor en una lista virtual

**Propósito.** Mostrar un conjunto de datos grande cuya fuente está paginada (ej.: API REST) sin
cargar todo de una vez. El `PagedDataAdapter` mantiene una **caché LRU de páginas**: solo un número
limitado de páginas queda en memoria; al desplazarse a una región cuyos datos fueron descartados, un
spinner aparece y la página se recarga mediante `loadPage`. El scroll se bloquea mientras la página
carga.

## APIs usadas
- `abstract class PagedDataAdapter(int pageSize, int maxCachedPages)` — `pageSize` elementos por página; como máximo `maxCachedPages` páginas en memoria (LRU). Implementa `VirtualListView.DataAdapter`.
- `abstract int getTotalCount()` — total de elementos en el servidor (se convierte en el `getCount()` de la lista).
- `abstract void loadPage(int pageIndex, PageCallback callback)` — carga una página **de forma asíncrona**; al terminar, llama a `callback.onPageLoaded(pageIndex, items)`. Puede ser llamado desde cualquier thread — la lista hace el switch a la UI.
- `abstract Component createView() throws Exception` — crea la view en blanco reutilizable.
- `abstract void bindItem(Component view, Object item, int index) throws Exception` — puebla la view con el **objeto** del elemento (no el texto). A diferencia del `bindView` crudo: aquí recibes el elemento ya resuelto por la página.
- `interface PageCallback { void onPageLoaded(int pageIndex, Object[] items); }`
- `PagedDataAdapter.isAvailable(int index)` — true si el índice ya está en la caché.
- `PagedDataAdapter.clearCache()` — limpia caché y estado de loading.

## Ejemplo completo (copiar, pegar y ejecutar)
```java
package com.exemplo;

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.ui.graphics.*;   // Relative (para VirtualListView)

/** Pantalla principal ejecutable. Apunta el launcher (classname) y corre: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   /** Ítem de negocio simulado — cámbialo por tu entidad real. */
   static class Registro
   {
      final String nome;
      Registro(String nome) { this.nome = nome; }
      String getNome() { return nome; }
   }

   static final int totalNoServidor = 12345;   // ej.: metadata de la API

   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      PagedDataAdapter adapter = new PagedDataAdapter(20, 5)   // 20 ítems/página, 5 páginas en memoria
      {
         public int getTotalCount()
         {
            return totalNoServidor;
         }

         public void loadPage(int pageIndex, PageCallback callback)
         {
            // asíncrono: aquí simulamos la respuesta del servidor en background
            onUI(() ->
            {
               Object[] registros = new Object[20];
               for (int i = 0; i < registros.length; i++)
                  registros[i] = new Registro("Item " + (pageIndex * 20 + i));
               callback.onPageLoaded(pageIndex, registros);
            });
         }

         public Component createView() throws Exception
         {
            return new Label("");                  // fila simple reutilizable
         }

         public void bindItem(Component view, Object item, int index) throws Exception
         {
            Registro r = (Registro) item;          // el ítem ya resuelto de la página
            ((Label) view).setText(r.getNome());
         }
      };

      // PagedDataAdapter es un DataAdapter: pásalo directo al VirtualListView
      VirtualListView lista = new VirtualListView(Relative.ofFont(3f), adapter);
      add(LAY_FILL, lista);
   }
}
```

> El fragmento de abajo muestra **solo el núcleo**. Para ejecutarlo, colócalo en el esqueleto `MainWindow` de arriba.

## Fragmento mínimo
```java
// verificado en la fuente: PagedDataAdapter.java
PagedDataAdapter adapter = new PagedDataAdapter(20, 5)   // 20 ítems/página, 5 páginas en memoria
{
   public int getTotalCount()
   {
      return totalNoServidor;                // ej.: 12345 (viniendo de metadata de la API)
   }

   public void loadPage(int pageIndex, PageCallback callback)
   {
      // asíncrono: dispara la petición y responde por el callback
      api.buscarPagina(pageIndex, 20, registros ->
         callback.onPageLoaded(pageIndex, registros));   // registros = Object[]
   }

   public Component createView() throws Exception
   {
      return new Label("");                  // fila simple reutilizable
   }

   public void bindItem(Component view, Object item, int index) throws Exception
   {
      Registro r = (Registro) item;          // el ítem ya resuelto de la página
      ((Label) view).setText(r.getNome());
   }
};

// PagedDataAdapter es un DataAdapter: pásalo directo al VirtualListView
VirtualListView lista = new VirtualListView(Relative.ofFont(3f), adapter);
layout.add(1f, lista);
```

## Profundiza
- El `VirtualListView` se enlaza al adapter automáticamente en el constructor (`adapter.listView = this`) — no hace falta amarrarlo manualmente.
- Mientras `loadPage` está en vuelo, el adapter marca `waitingForData` y el `VirtualListView` **bloquea pan/fling/wheel** y pausa el fling actual (retomado al llegar la página).
- Para scroll infinito por append (sin caché LRU), mira [carregar-mais.md](/docs/cookbook/lists/carregar-mais).
- Guía del widget: [PagedDataAdapter](/docs/ui/widgets/paged-data-adapter) · Buenas prácticas: [Rendimiento y listas grandes](/docs/best-practices/performance-e-listas-grandes) · Fuente canónica: Capítulo 15
