Skip to content
CrossFly is 100% open sourceFollow us on GitHub
CrossFlyCrossFly
Get started

This page was machine-translated from Portuguese and may contain errors. The Portuguese version is canonical.

PagedDataAdapter

PagedDataAdapter is a paged DataAdapter for 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.
  • Small list → ListView.

Complete example (copy, paste and run)

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

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

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

Was this page helpful?