# How to implement load-more (infinite scroll)

> Automatically append more items when the user reaches near the end of the virtual list.

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

# How to implement load-more (infinite scroll)

**Purpose.** Automatically load more data as the user approaches the end of the
list, appending items to the existing dataset (append). Unlike pagination with an LRU cache
([paginacao.md](/docs/cookbook/lists/paginacao)), here you control the dataset and the list only signals when it is time
to fetch more.

## APIs used
- `VirtualListView.onLoadMore(Runnable callback)` — fires when the scroll gets near the end. Only one trigger stays pending at a time (`loadMorePending`), preventing duplicate calls.
- `VirtualListView.loadMoreThreshold(int threshold)` — how many items **before the end** fire the callback. Default `5`.
- `VirtualListView.notifyDataChanged()` — **required** to call after appending the new items; it clears `loadMorePending`, hides the spinner, and re-measures the content.

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

import java.util.*;   // List / ArrayList

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
{
   final List<String> dados = new ArrayList<>();
   int proxima = 0;

   /** Appends a batch of 30 items to the dataset (simulates a server page). */
   private void carregarPagina()
   {
      for (int i = 0; i < 30; i++)
         dados.add("Registro " + (proxima++));
   }

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

      carregarPagina();   // populates the first batch

      VirtualListView lista = new VirtualListView(Relative.ofFont(3f), new VirtualListView.DataAdapter()
      {
         public int getCount()                    { return dados.size(); }
         public Component createView()            { return new Label(""); }
         public void bindView(Component v, int i) throws Exception
         {
            ((Label) v).setText(dados.get(i));
         }
      });

      lista.loadMoreThreshold(8);   // starts fetching 8 items before the end (default is 5)
      lista.onLoadMore(() ->
      {
         // fires ONCE when nearing the end; further fires only after notifyDataChanged
         onUI(() ->
         {
            carregarPagina();             // APPENDS (new indices at the end)
            lista.notifyDataChanged();    // releases the next load-more and hides the spinner
         });
      });

      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: VirtualListView.java (onLoadMore / loadMoreThreshold / notifyDataChanged)
final Array<String> dados = new Array<>();
carregarPagina(dados, 0);   // populates the first batch

VirtualListView lista = new VirtualListView(Relative.ofFont(3f), new VirtualListView.DataAdapter()
{
   public int getCount()                    { return dados.size(); }
   public Component createView()            { return new Label(""); }
   public void bindView(Component v, int i) throws Exception
   {
      ((Label) v).setText(dados.get(i));
   }
});

lista.loadMoreThreshold(8);   // starts fetching 8 items before the end (default is 5)
lista.onLoadMore(() ->
{
   // fires ONCE when nearing the end; further fires only after notifyDataChanged
   api.proximaPagina(novos ->
   {
      dados.addAll(novos);          // APPENDS (new indices at the end)
      lista.notifyDataChanged();    // releases the next load-more and hides the spinner
   });
});

layout.add(1f, lista);
```

## Go deeper
- The `VirtualListView` automatically shows a **spinner in the footer** while the load-more is pending (controlled by `VirtualListViewStyle.showSpinner`).
- If you do **not** call `notifyDataChanged()`, `loadMorePending` stays stuck at `true` and the callback never fires again.
- Need disposable pages with a bounded cache (instead of infinite append)? Use [paginacao.md](/docs/cookbook/lists/paginacao).
- Widget guide: [VirtualListView](/docs/ui/widgets/virtuallistview) · Sibling recipe: [lista-virtual-grande.md](/docs/cookbook/lists/lista-virtual-grande) · Canonical source: Chapter 15
