# Como paginar dados de servidor em uma lista virtual

> Carregar páginas sob demanda com PagedDataAdapter (cache LRU) sobre VirtualListView.

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

# Como paginar dados de servidor em uma lista virtual

**Propósito.** Exibir um conjunto de dados grande cuja fonte é paginada (ex.: API REST) sem
carregar tudo de uma vez. O `PagedDataAdapter` mantém um **cache LRU de páginas**: só um número
limitado de páginas fica em memória; ao rolar para uma região cujos dados foram descartados, um
spinner aparece e a página é recarregada via `loadPage`. O scroll é travado enquanto a página
carrega.

## APIs usadas
- `abstract class PagedDataAdapter(int pageSize, int maxCachedPages)` — `pageSize` itens por página; no máximo `maxCachedPages` páginas em memória (LRU). Implementa `VirtualListView.DataAdapter`.
- `abstract int getTotalCount()` — total de itens no servidor (vira o `getCount()` da lista).
- `abstract void loadPage(int pageIndex, PageCallback callback)` — carrega uma página **de forma assíncrona**; ao terminar, chame `callback.onPageLoaded(pageIndex, items)`. Pode ser chamado de qualquer thread — a lista faz o switch para a UI.
- `abstract Component createView() throws Exception` — cria a view em branco reutilizável.
- `abstract void bindItem(Component view, Object item, int index) throws Exception` — popula a view com o **objeto** do item (não o texto). Diferente do `bindView` cru: aqui você recebe o item já resolvido pela página.
- `interface PageCallback { void onPageLoaded(int pageIndex, Object[] items); }`
- `PagedDataAdapter.isAvailable(int index)` — true se o índice já está no cache.
- `PagedDataAdapter.clearCache()` — limpa cache e estado de loading.

## 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 (para VirtualListView)

/** Tela principal executável. Aponte o launcher (classname) e rode: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   /** Item de negócio simulado — troque pela sua entidade real. */
   static class Registro
   {
      final String nome;
      Registro(String nome) { this.nome = nome; }
      String getNome() { return nome; }
   }

   static final int totalNoServidor = 12345;   // ex.: metadata da API

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

      PagedDataAdapter adapter = new PagedDataAdapter(20, 5)   // 20 itens/página, 5 páginas em memória
      {
         public int getTotalCount()
         {
            return totalNoServidor;
         }

         public void loadPage(int pageIndex, PageCallback callback)
         {
            // assíncrono: aqui simulamos a resposta do servidor em 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("");                  // linha simples reutilizável
         }

         public void bindItem(Component view, Object item, int index) throws Exception
         {
            Registro r = (Registro) item;          // o item já resolvido da página
            ((Label) view).setText(r.getNome());
         }
      };

      // PagedDataAdapter é um DataAdapter: passe direto ao VirtualListView
      VirtualListView lista = new VirtualListView(Relative.ofFont(3f), adapter);
      add(LAY_FILL, lista);
   }
}
```

> O trecho abaixo mostra **só o miolo**. Para rodar, coloque-o no esqueleto `MainWindow` acima.

## Trecho mínimo
```java
// verificado na fonte: PagedDataAdapter.java
PagedDataAdapter adapter = new PagedDataAdapter(20, 5)   // 20 itens/página, 5 páginas em memória
{
   public int getTotalCount()
   {
      return totalNoServidor;                // ex.: 12345 (vindo de metadata da API)
   }

   public void loadPage(int pageIndex, PageCallback callback)
   {
      // assíncrono: dispara a requisição e responde pelo callback
      api.buscarPagina(pageIndex, 20, registros ->
         callback.onPageLoaded(pageIndex, registros));   // registros = Object[]
   }

   public Component createView() throws Exception
   {
      return new Label("");                  // linha simples reutilizável
   }

   public void bindItem(Component view, Object item, int index) throws Exception
   {
      Registro r = (Registro) item;          // o item já resolvido da página
      ((Label) view).setText(r.getNome());
   }
};

// PagedDataAdapter é um DataAdapter: passe direto ao VirtualListView
VirtualListView lista = new VirtualListView(Relative.ofFont(3f), adapter);
layout.add(1f, lista);
```

## Aprofunde-se
- O `VirtualListView` liga-se ao adapter automaticamente no construtor (`adapter.listView = this`) — não é preciso amarrar manualmente.
- Enquanto `loadPage` está em voo, o adapter marca `waitingForData` e o `VirtualListView` **bloqueia pan/fling/wheel** e pausa o fling atual (retomado ao chegar a página).
- Para scroll infinito por append (sem cache LRU), veja [carregar-mais.md](/docs/cookbook/lists/carregar-mais).
- Guia de widget: [PagedDataAdapter](/docs/ui/widgets/paged-data-adapter) · Boas práticas: [Performance e listas grandes](/docs/best-practices/performance-e-listas-grandes) · Fonte canônica: Capítulo 15
