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.

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)

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

// 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.
  • Widget guide: PagedDataAdapter · Best practices: Performance and large lists · Canonical source: Chapter 15
Was this page helpful?