# How to paginate server data in a virtual list

> Load pages on demand with PagedDataAdapter (LRU cache) over VirtualListView.

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

# How to paginate server data in a virtual list

**Purpose.** Display a large dataset whose source is paginated (e.g., a REST API) without
loading everything at once. The `PagedDataAdapter` keeps an **LRU cache of pages**: only a limited
number of pages stays in memory; when scrolling to a region whose data was discarded, a
spinner appears and the page is reloaded via `loadPage`. Scrolling is locked while the page
loads.

## APIs used
- `abstract class PagedDataAdapter(int pageSize, int maxCachedPages)` — `pageSize` items per page; at most `maxCachedPages` pages in memory (LRU). Implements `VirtualListView.DataAdapter`.
- `abstract int getTotalCount()` — total number of items on the server (becomes the list's `getCount()`).
- `abstract void loadPage(int pageIndex, PageCallback callback)` — loads a page **asynchronously**; when done, call `callback.onPageLoaded(pageIndex, items)`. Can be called from any thread — the list switches to the UI.
- `abstract Component createView() throws Exception` — creates the reusable blank view.
- `abstract void bindItem(Component view, Object item, int index) throws Exception` — populates the view with the item **object** (not the text). Unlike the raw `bindView`: here you receive the item already resolved by the page.
- `interface PageCallback { void onPageLoaded(int pageIndex, Object[] items); }`
- `PagedDataAdapter.isAvailable(int index)` — true if the index is already in the cache.
- `PagedDataAdapter.clearCache()` — clears the cache and loading state.

## Complete example (copy, paste and run)
```java
package com.exemplo;

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

/** Runnable main screen. Point the launcher (classname) and run: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   /** Simulated business item — replace with your real entity. */
   static class Registro
   {
      final String nome;
      Registro(String nome) { this.nome = nome; }
      String getNome() { return nome; }
   }

   static final int totalNoServidor = 12345;   // e.g.: API metadata

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

      PagedDataAdapter adapter = new PagedDataAdapter(20, 5)   // 20 items/page, 5 pages in memory
      {
         public int getTotalCount()
         {
            return totalNoServidor;
         }

         public void loadPage(int pageIndex, PageCallback callback)
         {
            // async: here we simulate the server response in the 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("");                  // reusable simple row
         }

         public void bindItem(Component view, Object item, int index) throws Exception
         {
            Registro r = (Registro) item;          // the already-resolved item from the page
            ((Label) view).setText(r.getNome());
         }
      };

      // PagedDataAdapter is a DataAdapter: pass it straight to VirtualListView
      VirtualListView lista = new VirtualListView(Relative.ofFont(3f), adapter);
      add(LAY_FILL, lista);
   }
}
```

> The snippet below shows **only the core**. To run it, place it inside the `MainWindow` skeleton above.

## Minimal snippet
```java
// verified in source: PagedDataAdapter.java
PagedDataAdapter adapter = new PagedDataAdapter(20, 5)   // 20 items/page, 5 pages in memory
{
   public int getTotalCount()
   {
      return totalNoServidor;                // e.g.: 12345 (coming from API metadata)
   }

   public void loadPage(int pageIndex, PageCallback callback)
   {
      // async: fires the request and responds via the callback
      api.buscarPagina(pageIndex, 20, registros ->
         callback.onPageLoaded(pageIndex, registros));   // records = Object[]
   }

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

   public void bindItem(Component view, Object item, int index) throws Exception
   {
      Registro r = (Registro) item;          // the already-resolved item from the page
      ((Label) view).setText(r.getNome());
   }
};

// PagedDataAdapter is a DataAdapter: pass it straight to VirtualListView
VirtualListView lista = new VirtualListView(Relative.ofFont(3f), adapter);
layout.add(1f, lista);
```

## Go deeper
- The `VirtualListView` binds to the adapter automatically in the constructor (`adapter.listView = this`) — no need to wire it manually.
- While `loadPage` is in flight, the adapter marks `waitingForData` and the `VirtualListView` **blocks pan/fling/wheel** and pauses the current fling (resumed when the page arrives).
- For infinite scroll by append (without an LRU cache), see [carregar-mais.md](/docs/cookbook/lists/carregar-mais).
- Widget guide: [PagedDataAdapter](/docs/ui/widgets/paged-data-adapter) · Best practices: [Performance and large lists](/docs/best-practices/performance-e-listas-grandes) · Canonical source: Chapter 15
