# PagedDataAdapter

> Paged DataAdapter for VirtualListView, with an LRU page cache and asynchronous loading.

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

# PagedDataAdapter

`PagedDataAdapter` is a **paged** `DataAdapter` for [`VirtualListView`](/docs/ui/widgets/virtuallistview), with an **LRU page cache**. It keeps only a few pages in memory; when scrolling to a region whose page was discarded, it shows a spinner and reloads asynchronously via `loadPage`.

It is an **abstract** class: you implement `getTotalCount`, `loadPage`, `createView` and `bindItem`. The index→page translation, the cache, the spinner and the scroll lock during loading come ready-made.

> 🔴 · **Package:** `dev.crossfly.ui` · **Implements:** `VirtualListView.DataAdapter` · **Usage:** passed to the `VirtualListView` constructor

## When to use
- Data that comes from network/database in **pages** and does not all fit in memory.
- You want automatic caching (LRU) with transparent reloading when returning to an old region.

## When NOT to use
- All data is already in memory → implement `DataAdapter` directly on the [`VirtualListView`](/docs/ui/widgets/virtuallistview).
- Small list → [`ListView`](/docs/ui/widgets/listview).

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

/** Runnable main screen. Point the launcher (classname) and run: 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 items/page, 5 pages
         public int getTotalCount() { return total; }
         public void loadPage(int pageIndex, PageCallback cb) {
            // Simulates a load (replace with a network/DB call in the 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);   // lists use LAY_FILL (they absorb the height)
   }
}
```

> The snippets below show **only the core** (what goes inside `onShow()`). To run them, place them in the `MainWindow` skeleton above.

## Basic usage
```java
PagedDataAdapter adapter = new PagedDataAdapter(20, 5) { // 20 items/page, 5 pages in memory
   public int getTotalCount() { return serverTotal; }
   public void loadPage(int pageIndex, PageCallback cb) {
      api.fetch(pageIndex, data -> cb.onPageLoaded(pageIndex, data)); // can be 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);
```

## Constructors
| Constructor | Description |
|---|---|
| `PagedDataAdapter(int pageSize, int maxCachedPages)` | `pageSize`: items per page. `maxCachedPages`: pages kept in memory (LRU) |

Both fields are `public final`. The `VirtualListView` injects itself into the adapter when you pass this adapter to its constructor.

## Abstract methods to implement
| Method | Description |
|---|---|
| `int getTotalCount()` | Total items on the server (used by `getCount()`) |
| `void loadPage(int pageIndex, PageCallback callback)` | Loads a page asynchronously; call `callback.onPageLoaded(pageIndex, items)` when done (from any thread) |
| `Component createView() throws Exception` | Creates a blank view |
| `void bindItem(Component view, Object item, int index) throws Exception` | Binds the raw item (`Object`) to the view — cast it to your type |

## Useful properties and methods
| Method | Type | What it does |
|---|---|---|
| `isAvailable(int index)` | `boolean` | Whether the item is already in the cache |
| `clearCache()` | `void` | Clears cache and loading state |
| `getCount()` | `int` | Delegates to `getTotalCount()` |

## How it works (summary)
- `bindView(view, index)` translates `pageIndex = index / pageSize` and `itemInPage = index % pageSize`.
  - Page in cache → immediate `bindItem` (`waitingForData = false`).
  - Page missing → `waitingForData = true` and it fires `requestPage(pageIndex)`.
- `requestPage` avoids duplicate requests (`loading`), shows the spinner and **pauses the fling**; when the callback returns, it marshals to the UI thread with `Component.onUI(...)`, records the page, hides the spinner, forces a re-bind of the range (`invalidateRange`) and **resumes the fling**.
- The `cache` is a `LinkedHashMap` in access-order mode that discards the oldest page (LRU) when it exceeds `maxCachedPages`.

## Callbacks and events
| Callback | When it fires |
|---|---|
| `PageCallback.onPageLoaded(int pageIndex, Object[] items)` | You call it when done loading the page (from any thread) |

## States
- **`waitingForData`** — `true` while a page is loading; signals to the `VirtualListView` to **lock the scroll**.
- **LRU** — old pages are discarded; when scrolling back, they are reloaded (new spinner + request).

## Example — cast of a typed item
```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 of the raw Object
      ((Label) view).setText(c.getNome());
   }
};
VirtualListView list = new VirtualListView(Relative.ofFont(2.4f), adapter);
```

## Best practices
- The page items arrive as `Object[]` — cast the `item` to your type in `bindItem`.
- **Do not** call UI APIs inside `loadPage` before the callback — the adapter already switches to the UI thread internally upon receiving `onPageLoaded`.
- Adjust `maxCachedPages` to the scrolling pattern: a larger cache avoids reloads when returning, at the cost of memory.

## Common mistakes
- **Calling `onPageLoaded` with the wrong `pageIndex`** — the adapter uses that index to record in the cache and invalidate the correct range.
- **Blocking in `loadPage`** — it must be asynchronous; the UI thread cannot wait for the network.

## See also
- Area guide: [`ui/`](/docs/ui) · Recipes: [`cookbook/`](/docs/cookbook)
- Related components: [`VirtualListView`](/docs/ui/widgets/virtuallistview)
