# PagedDataAdapter

> DataAdapter paginado para VirtualListView, com cache LRU de páginas e carregamento assíncrono.

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

# PagedDataAdapter

`PagedDataAdapter` é um `DataAdapter` **paginado** para [`VirtualListView`](/docs/ui/widgets/virtuallistview), com um **cache LRU de páginas**. Mantém só algumas páginas em memória; ao rolar para uma região cuja página foi descartada, mostra um spinner e recarrega assincronamente via `loadPage`.

É uma classe **abstrata**: você implementa `getTotalCount`, `loadPage`, `createView` e `bindItem`. A tradução índice→página, o cache, o spinner e a trava de scroll durante o carregamento já vêm prontos.

> 🔴 · **Pacote:** `dev.crossfly.ui` · **Implementa:** `VirtualListView.DataAdapter` · **Uso:** passado ao construtor de `VirtualListView`

## Quando usar
- Dados que vêm de rede/banco em **páginas** e não cabem todos em memória.
- Você quer cache automático (LRU) com recarga transparente ao voltar a uma região antiga.

## Quando NÃO usar
- Todos os dados já estão em memória → implemente `DataAdapter` diretamente na [`VirtualListView`](/docs/ui/widgets/virtuallistview).
- Lista pequena → [`ListView`](/docs/ui/widgets/listview).

## Exemplo completo (copiar, colar e rodar)
```java
package com.exemplo;

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.ui.graphics.*;   // Relative

/** Tela principal executável. Aponte o launcher (classname) e rode: 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 itens/página, 5 páginas
         public int getTotalCount() { return total; }
         public void loadPage(int pageIndex, PageCallback cb) {
            // Simula um carregamento (troque por chamada de rede/banco em 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);   // listas usam LAY_FILL (absorvem a altura)
   }
}
```

> Os trechos abaixo mostram **só o miolo** (o que vai dentro de `onShow()`). Para rodar, coloque-os no esqueleto `MainWindow` acima.

## Uso básico
```java
PagedDataAdapter adapter = new PagedDataAdapter(20, 5) { // 20 itens/página, 5 páginas em memória
   public int getTotalCount() { return serverTotal; }
   public void loadPage(int pageIndex, PageCallback cb) {
      api.fetch(pageIndex, data -> cb.onPageLoaded(pageIndex, data)); // pode 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);
```

## Construtores
| Construtor | Descrição |
|---|---|
| `PagedDataAdapter(int pageSize, int maxCachedPages)` | `pageSize`: itens por página. `maxCachedPages`: páginas mantidas em memória (LRU) |

Ambos os campos são `public final`. A `VirtualListView` injeta a si mesma no adapter quando você passa este adapter ao seu construtor.

## Métodos abstratos a implementar
| Método | Descrição |
|---|---|
| `int getTotalCount()` | Total de itens no servidor (usado por `getCount()`) |
| `void loadPage(int pageIndex, PageCallback callback)` | Carrega uma página assíncrona; chame `callback.onPageLoaded(pageIndex, items)` ao terminar (de qualquer thread) |
| `Component createView() throws Exception` | Cria uma view em branco |
| `void bindItem(Component view, Object item, int index) throws Exception` | Vincula o item bruto (`Object`) à view — faça o cast para o seu tipo |

## Propriedades e métodos úteis
| Método | Tipo | O que faz |
|---|---|---|
| `isAvailable(int index)` | `boolean` | Se o item já está no cache |
| `clearCache()` | `void` | Zera cache e estado de carregamento |
| `getCount()` | `int` | Delega a `getTotalCount()` |

## Funcionamento (resumo)
- `bindView(view, index)` traduz `pageIndex = index / pageSize` e `itemInPage = index % pageSize`.
  - Página no cache → `bindItem` imediato (`waitingForData = false`).
  - Página ausente → `waitingForData = true` e dispara `requestPage(pageIndex)`.
- `requestPage` evita requisições duplicadas (`loading`), mostra spinner e **pausa o fling**; ao voltar o callback, marshala para a thread de UI com `Component.onUI(...)`, grava a página, esconde o spinner, força re-bind da faixa (`invalidateRange`) e **retoma o fling**.
- O `cache` é um `LinkedHashMap` em modo access-order que descarta a página mais antiga (LRU) quando excede `maxCachedPages`.

## Callbacks e eventos
| Callback | Quando dispara |
|---|---|
| `PageCallback.onPageLoaded(int pageIndex, Object[] items)` | Você chama ao terminar de carregar a página (de qualquer thread) |

## Estados
- **`waitingForData`** — `true` enquanto alguma página carrega; sinaliza à `VirtualListView` para **travar o scroll**.
- **LRU** — páginas antigas são descartadas; ao rolar de volta, são recarregadas (novo spinner + requisição).

## Exemplo — cast de item 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 do Object bruto
      ((Label) view).setText(c.getNome());
   }
};
VirtualListView list = new VirtualListView(Relative.ofFont(2.4f), adapter);
```

## Boas práticas
- Os itens da página chegam como `Object[]` — faça o cast do `item` para o seu tipo em `bindItem`.
- **Não** chame APIs de UI dentro de `loadPage` antes do callback — o adapter já troca para a thread de UI internamente ao receber `onPageLoaded`.
- Ajuste `maxCachedPages` ao padrão de rolagem: cache maior evita recargas ao voltar, ao custo de memória.

## Erros comuns
- **Chamar `onPageLoaded` com `pageIndex` errado** — o adapter usa esse índice para gravar no cache e invalidar a faixa correta.
- **Bloquear em `loadPage`** — deve ser assíncrono; a thread de UI não pode esperar a rede.

## Veja também
- Guia da área: [`ui/`](/docs/ui) · Receitas: [`cookbook/`](/docs/cookbook)
- Componentes relacionados: [`VirtualListView`](/docs/ui/widgets/virtuallistview)
