# PagedDataAdapter

> DataAdapter paginado para VirtualListView, con caché LRU de páginas y carga asíncrona.

Source: https://crossfly.dev/es/docs/ui/widgets/paged-data-adapter — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# PagedDataAdapter

`PagedDataAdapter` es un `DataAdapter` **paginado** para [`VirtualListView`](/docs/ui/widgets/virtuallistview), con un **caché LRU de páginas**. Mantiene solo algunas páginas en memoria; al desplazarse a una región cuya página fue descartada, muestra un spinner y recarga asincrónicamente vía `loadPage`.

Es una clase **abstracta**: tú implementas `getTotalCount`, `loadPage`, `createView` y `bindItem`. La traducción índice→página, el caché, el spinner y el bloqueo de scroll durante la carga ya vienen listos.

> 🔴 · **Paquete:** `dev.crossfly.ui` · **Implementa:** `VirtualListView.DataAdapter` · **Uso:** pasado al constructor de `VirtualListView`

## Cuándo usar
- Datos que vienen de red/base de datos en **páginas** y no caben todos en memoria.
- Quieres caché automático (LRU) con recarga transparente al volver a una región antigua.

## Cuándo NO usar
- Todos los datos ya están en memoria → implementa `DataAdapter` directamente en el [`VirtualListView`](/docs/ui/widgets/virtuallistview).
- Lista pequeña → [`ListView`](/docs/ui/widgets/listview).

## 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

/** Pantalla principal ejecutable. Apunta el launcher (classname) y corre: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      final int pageSize = 20;
      final int total    = 10_000;

      PagedDataAdapter adapter = new PagedDataAdapter(pageSize, 5) { // 20 ítems/página, 5 páginas
         public int getTotalCount() { return total; }
         public void loadPage(int pageIndex, PageCallback cb) {
            // Simula una carga (reemplace por llamada de red/base en background).
            Object[] data = new Object[pageSize];
            for (int i = 0; i < pageSize; i++)
               data[i] = "Item " + (pageIndex * pageSize + i);
            cb.onPageLoaded(pageIndex, data);
         }
         public Component createView() { return new Label(""); }
         public void bindItem(Component view, Object item, int index) {
            ((Label) view).setText(item.toString());
         }
      };

      VirtualListView list = new VirtualListView(Relative.ofFont(2f), adapter);

      add(LAY_FILL, list);   // las listas usan LAY_FILL (absorben la altura)
   }
}
```

> Los fragmentos de abajo muestran **solo el núcleo** (lo que va dentro de `onShow()`). Para ejecutar, colócalos en el esqueleto `MainWindow` de arriba.

## Uso básico
```java
PagedDataAdapter adapter = new PagedDataAdapter(20, 5) { // 20 ítems/página, 5 páginas en memoria
   public int getTotalCount() { return serverTotal; }
   public void loadPage(int pageIndex, PageCallback cb) {
      api.fetch(pageIndex, data -> cb.onPageLoaded(pageIndex, data)); // puede ser background
   }
   public Component createView() { return new Label(""); }
   public void bindItem(Component view, Object item, int index) {
      ((Label) view).setText(item.toString());
   }
};
VirtualListView list = new VirtualListView(Relative.ofFont(2f), adapter);
layout.add(LAY_FILL, list);
```

## Constructores
| Constructor | Descripción |
|---|---|
| `PagedDataAdapter(int pageSize, int maxCachedPages)` | `pageSize`: ítems por página. `maxCachedPages`: páginas mantenidas en memoria (LRU) |

Ambos campos son `public final`. La `VirtualListView` se inyecta a sí misma en el adapter cuando pasas este adapter a su constructor.

## Métodos abstractos a implementar
| Método | Descripción |
|---|---|
| `int getTotalCount()` | Total de ítems en el servidor (usado por `getCount()`) |
| `void loadPage(int pageIndex, PageCallback callback)` | Carga una página asíncrona; llama a `callback.onPageLoaded(pageIndex, items)` al terminar (desde cualquier thread) |
| `Component createView() throws Exception` | Crea una view en blanco |
| `void bindItem(Component view, Object item, int index) throws Exception` | Vincula el ítem crudo (`Object`) a la view — haz el cast a tu tipo |

## Propiedades y métodos útiles
| Método | Tipo | Qué hace |
|---|---|---|
| `isAvailable(int index)` | `boolean` | Si el ítem ya está en el caché |
| `clearCache()` | `void` | Vacía caché y estado de carga |
| `getCount()` | `int` | Delega a `getTotalCount()` |

## Funcionamiento (resumen)
- `bindView(view, index)` traduce `pageIndex = index / pageSize` e `itemInPage = index % pageSize`.
  - Página en el caché → `bindItem` inmediato (`waitingForData = false`).
  - Página ausente → `waitingForData = true` y dispara `requestPage(pageIndex)`.
- `requestPage` evita solicitudes duplicadas (`loading`), muestra spinner y **pausa el fling**; al volver el callback, marshala a la thread de UI con `Component.onUI(...)`, graba la página, oculta el spinner, fuerza el re-bind de la franja (`invalidateRange`) y **reanuda el fling**.
- El `cache` es un `LinkedHashMap` en modo access-order que descarta la página más antigua (LRU) cuando excede `maxCachedPages`.

## Callbacks y eventos
| Callback | Cuándo se dispara |
|---|---|
| `PageCallback.onPageLoaded(int pageIndex, Object[] items)` | Tú lo llamas al terminar de cargar la página (desde cualquier thread) |

## Estados
- **`waitingForData`** — `true` mientras alguna página carga; señala a la `VirtualListView` que **bloquee el scroll**.
- **LRU** — las páginas antiguas se descartan; al desplazarse de vuelta, se recargan (nuevo spinner + solicitud).

## Ejemplo — cast de ítem tipado
```java
PagedDataAdapter adapter = new PagedDataAdapter(50, 4) {
   public int getTotalCount() { return repo.total(); }
   public void loadPage(int page, PageCallback cb) {
      repo.loadAsync(page, 50, lista -> cb.onPageLoaded(page, lista.toArray()));
   }
   public Component createView() { return new Label(""); }
   public void bindItem(Component view, Object item, int index) {
      Cliente c = (Cliente) item;            // cast del Object crudo
      ((Label) view).setText(c.getNome());
   }
};
VirtualListView list = new VirtualListView(Relative.ofFont(2.4f), adapter);
```

## Buenas prácticas
- Los ítems de la página llegan como `Object[]` — haz el cast del `item` a tu tipo en `bindItem`.
- **No** llames a APIs de UI dentro de `loadPage` antes del callback — el adapter ya cambia a la thread de UI internamente al recibir `onPageLoaded`.
- Ajusta `maxCachedPages` al patrón de desplazamiento: un caché mayor evita recargas al volver, a costa de memoria.

## Errores comunes
- **Llamar a `onPageLoaded` con el `pageIndex` equivocado** — el adapter usa ese índice para grabar en el caché e invalidar la franja correcta.
- **Bloquear en `loadPage`** — debe ser asíncrono; la thread de UI no puede esperar a la red.

## Ver también
- Guía del área: [`ui/`](/docs/ui) · Recetas: [`cookbook/`](/docs/cookbook)
- Componentes relacionados: [`VirtualListView`](/docs/ui/widgets/virtuallistview)
