# Performance and large lists

> Keeping 60 fps with lots of data — virtualization (VirtualListView), pagination, and cheap onPaint.

Source: https://crossfly.dev/docs/best-practices/performance-e-listas-grandes — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# Performance and large lists

How to keep 60 fps — even on low-end GPUs — with lots of data or heavy drawing. The "why" (single paint
thread, on-demand render) is in [The paint thread](/docs/concepts/the-paint-thread) and
[The rendering pipeline](/docs/concepts/rendering-pipeline).

## Virtualize: `VirtualListView`, not `ListView`
`ListView` keeps **all** items in memory. For hundreds/thousands, use `VirtualListView` — it
**recycles views** (renders only what's visible). The adapter separates **create** (once, recycled) from
**bind** (cheap, runs a lot):
```java
VirtualListView lista = new VirtualListView(Relative.ofFont(3f), new DataAdapter() {
   public int getCount() { return dados.size(); }
   public Component createView() throws Exception {         // created only a few times (pool)
      return new Label("");
   }
   public void bindView(Component v, int i) throws Exception {  // runs A LOT on scroll → cheap and idempotent
      ((Label) v).setText(dados.get(i).nome);
   }
});
// when the data changes:
lista.notifyDataChanged();
```
> `bindView` must be **cheap and idempotent** — it runs on every recycle during scrolling. No I/O,
> allocation, or expensive computation there.

**Why recycle:** the real cost is `createView()`. By recycling, memory stays **constant** regardless of
`getCount()` — 10 or 10,000 items use the same handful of views.

## Remote pagination: `PagedDataAdapter`
For data that comes in by page (network), use `PagedDataAdapter` (internal **LRU** cache):
```java
var adapter = new PagedDataAdapter(50, 4) {   // 50 items/page, 4 pages cached
   public int getTotalCount() { return total; }
   public void loadPage(int page, PageCallback cb) {
      new Thread(() -> {
         Object[] itens = api.buscarPagina(page, pageSize);   // off the paint thread
         Component.onUI(() -> cb.onPageLoaded(page, itens));   // back to the UI
      }).start();
   }
   public Component createView() throws Exception { return new Label(""); }
   public void bindItem(Component v, Object item, int i) throws Exception {
      ((Label) v).setText(((Cliente) item).nome);
   }
};
```

## Drawing: cheap `onPaint` and on-demand render
- **Don't repaint every frame.** Call `repaint()` **only when the state changes** — the screen doesn't
  redraw itself (on-demand render).
- **Don't allocate in `onPaint`.** It runs for the whole tree every frame; reuse objects (`GRect`,
  `Font`) in fields, don't create them per frame.
- **Never block the paint thread** — database/network I/O on a worker thread + `onUI`.

## Best practices
- `VirtualListView` from ~hundreds of items on; `ListView` only for small/fixed lists.
- Lean `bindView`/`bindItem`; load images/data outside and bind the ready-made result.
- Large images: resize once (destructive scaling) instead of scaling per frame.

## Common mistakes
- `ListView` with thousands of items (blows up memory / freezes).
- `bindView` doing I/O or allocating (stutters the scroll).
- Repainting in a loop/per frame instead of on demand.

## See also
- [VirtualListView](/docs/ui/widgets/virtuallistview) · [PagedDataAdapter](/docs/ui/widgets/paged-data-adapter)
