# Paginated ListView

The **Paginated ListView** is a specialized list widget designed for handling large datasets by loading data in pages or chunks. Instead of loading all items at once, it fetches data incrementally as the user scrolls, providing a smooth infinite scroll experience.

This widget is essential for building feeds, search results, product listings, and any interface where the total dataset could be very large or infinite.

{% embed url="<https://youtu.be/Hr6MmvaS_PU?si=G3HtKEvO4wPIDYj2>" %}

Watch this on Youtube: <https://www.youtube.com/watch?v=Hr6MmvaS_PU>

{% hint style="info" %}
Paginated ListView automatically handles the complexity of pagination, including loading states, error handling, and triggering new page loads when the user scrolls near the end of the list.
{% endhint %}

### Use Cases

Paginated ListView is ideal for:

* **Social Media Feeds**: Load posts, comments, or notifications incrementally as users scroll.
* **E-commerce Product Listings**: Display thousands of products without loading everything upfront.
* **Search Results**: Show search results page by page, improving initial load time.
* **News Articles**: Load articles in batches for better performance.
* **User Directories**: Display large lists of users, contacts, or team members.
* **Transaction History**: Show financial transactions or order history with pagination.
* **Image Galleries**: Load images progressively to reduce initial bandwidth usage.

{% hint style="success" %}
**Performance Benefit:** By loading data in pages, you reduce initial load time, bandwidth usage, and memory consumption—especially critical for mobile apps with limited resources.
{% endhint %}

***

### How to Set Up a Paginated ListView

Setting up a Paginated ListView involves three main steps:

#### Step 1: Create a Paginated API Call

1. Navigate to the **API** section in your project.
2. Create a new API call that supports pagination.
3. Configure the API endpoint with pagination parameters (see [Pagination Variables](#pagination-variables) section below).
4. Test the API to ensure it returns paginated data correctly.

#### Step 2: Add the Paginated ListView Widget

1. Drag the **Paginated ListView** widget onto your canvas.
2. In the properties panel, select your paginated API in the **API Data Source** field.
3. Configure the pagination type and variables (explained below).

#### Step 3: Design the Item Template

1. Add a child widget to serve as the template for each list item.
2. Use `${currentItem}` to access data for each item.
3. Design your item layout (e.g., a container with text, images, buttons).

#### Step 4: Configure Loading Indicators

1. Add a `firstPageLoadingIndicator` widget (e.g., a circular progress indicator).
2. Add a `newPageLoadingIndicator` widget (e.g., a smaller loading spinner).
3. These will be shown while data is being fetched.

***

### Pagination Variables

Pagination variables control how the Paginated ListView communicates with your API to fetch successive pages of data. There are **three types of pagination** supported:

#### 1. Page-Based Pagination

In page-based pagination, you request data using a **page number**.

**How it works:**

* The API accepts a `page` parameter (e.g., `page=1`, `page=2`, `page=3`).
* Each request returns a specific page of results.
* The response typically includes the current page number and total pages.

**API Example:**

```
GET /api/products?page=1&limit=20
GET /api/products?page=2&limit=20
```

**Configuration:**

<table><thead><tr><th width="200">Property</th><th>Description</th></tr></thead><tbody><tr><td><code>First Page Key</code></td><td>Expression that returns the starting page number. Typically <code>1</code>.</td></tr><tr><td><code>Next Page Key</code></td><td>Expression to extract the next page number from the API response. Example: <code>${response.body.nextPage}</code> or <code>${sum(response.body.currentPage, 1)}</code>.</td></tr><tr><td><code>Transform Items</code></td><td>Expression to extract the array of items from the response. Example: <code>${response.body.data}</code> or <code>${response.body.products}</code>.</td></tr></tbody></table>

**Example Response Structure:**

```json
{
  "currentPage": 1,
  "totalPages": 10,
  "nextPage": 2,
  "data": [
    {"id": 1, "name": "Product 1"},
    {"id": 2, "name": "Product 2"}
  ]
}
```

**When to use:** When your backend API uses page numbers and returns structured pagination metadata.

***

#### 2. Offset-Based Pagination

In offset-based pagination, you request data using an **offset** value (how many items to skip) and a **limit** (how many items to return).

**How it works:**

* The API accepts `offset` and `limit` parameters.
* `offset` tells the API how many records to skip.
* `limit` specifies how many records to return in this batch.
* You calculate the next offset by adding the limit to the current offset.

**API Example:**

```
GET /api/users?offset=0&limit=20   // First page: items 1-20
GET /api/users?offset=20&limit=20  // Second page: items 21-40
GET /api/users?offset=40&limit=20  // Third page: items 41-60
```

**Configuration:**

<table><thead><tr><th width="200">Property</th><th>Description</th></tr></thead><tbody><tr><td><code>First Page Key</code></td><td>The starting offset value. Typically <code>0</code>.</td></tr><tr><td><code>Next Page Key</code></td><td>Expression to calculate the next offset. Example: <code>${sum(response.body.offset, response.body.limit)}</code> or <code>${sum(currentOffset, 20)}</code>.</td></tr><tr><td><code>Transform Items</code></td><td>Expression to extract the items array. Example: <code>${response.body.results}</code>.</td></tr></tbody></table>

**Example Response Structure:**

```json
{
  "offset": 0,
  "limit": 20,
  "total": 150,
  "results": [
    {"id": 1, "username": "user1"},
    {"id": 2, "username": "user2"}
  ]
}
```

**When to use:** Common with REST APIs and databases that support SQL-style LIMIT/OFFSET queries. Ideal for predictable datasets where you can calculate exact positions.

***

#### 3. Cursor-Based Pagination

In cursor-based pagination, you request data using a **cursor** token that points to the next set of results.

**How it works:**

* The API returns a cursor (or token) pointing to the next page.
* You pass this cursor in the next request to get the following batch.
* Cursors are typically opaque strings or tokens.
* More efficient than offset-based for large, frequently changing datasets.

**API Example:**

```
GET /api/posts?cursor=initial
GET /api/posts?cursor=eyJpZCI6MTAwfQ==
GET /api/posts?cursor=eyJpZCI6MjAwfQ==
```

**Configuration:**

<table><thead><tr><th width="200">Property</th><th>Description</th></tr></thead><tbody><tr><td><code>First Page Key</code></td><td>The initial cursor value. Can be <code>null</code>, <code>""</code>, or <code>"initial"</code> depending on your API.</td></tr><tr><td><code>Next Page Key</code></td><td>Expression to extract the next cursor from the response. Example: <code>${response.body.nextCursor}</code> or <code>${response.body.pagination.next}</code>.</td></tr><tr><td><code>Transform Items</code></td><td>Expression to extract the items array. Example: <code>${response.body.items}</code>.</td></tr></tbody></table>

**Example Response Structure:**

```json
{
  "nextCursor": "eyJpZCI6MTAwfQ==",
  "hasMore": true,
  "items": [
    {"id": 50, "title": "Post Title 1"},
    {"id": 51, "title": "Post Title 2"}
  ]
}
```

**When to use:**

* Large datasets where items are frequently added/deleted (social feeds, real-time data).
* When you need consistent results even if data changes between requests.
* APIs that use cursor/token-based pagination (Facebook, Twitter, Stripe, etc.).

{% hint style="warning" %}
**Important:** The API must return `null`, `undefined`, or an empty cursor when there are no more pages. This tells the Paginated ListView to stop loading new pages.
{% endhint %}

***

### Properties

#### Data Source Configuration

<table><thead><tr><th width="200">Property</th><th>Description</th></tr></thead><tbody><tr><td><code>API Data Source</code></td><td>(Required) The paginated API call to use for fetching data. This API should support pagination parameters.</td></tr><tr><td><code>Data Source</code></td><td>Optional static data or expression for testing. When using an API, this is typically not needed.</td></tr><tr><td><code>Transform Items</code></td><td>(Required) An expression that extracts the array of items from the API response. Example: <code>${response.body.data}</code>, <code>${response.body.results}</code>, or <code>${response.body.items}</code>.</td></tr></tbody></table>

#### Pagination Control

<table><thead><tr><th width="200">Property</th><th>Description</th></tr></thead><tbody><tr><td><code>First Page Key</code></td><td>(Required) The initial value for the pagination parameter. For page-based: <code>1</code>. For offset-based: <code>0</code>. For cursor-based: <code>null</code> or initial token.</td></tr><tr><td><code>Next Page Key</code></td><td>(Required) Expression to extract or calculate the key for the next page from the API response. This determines what value gets sent in the next API request.</td></tr></tbody></table>

#### Scrolling Behavior

<table><thead><tr><th width="200">Property</th><th>Description</th></tr></thead><tbody><tr><td><code>Allow Scroll</code></td><td>If <code>true</code>, the list will be scrollable. Default is <code>true</code>.</td></tr><tr><td><code>Scroll Direction</code></td><td>The direction in which the list scrolls. Can be <code>Vertical</code> (default) or <code>Horizontal</code>.</td></tr><tr><td><code>Reverse</code></td><td>If <code>true</code>, reverses the order in which items are displayed. For vertical lists, the first item appears at the bottom instead of the top. For horizontal lists, the first item appears on the right instead of the left. The scroll direction remains the same.</td></tr><tr><td><code>Initial Scroll Position</code></td><td>The initial scroll position when the list loads. Can be <code>Start</code> (default) or <code>End</code>.</td></tr><tr><td><code>Shrink Wrap</code></td><td>If <code>true</code>, the list sizes itself to fit its children. Use with caution as it can impact performance.</td></tr></tbody></table>

#### Loading Indicators

<table><thead><tr><th width="250">Property</th><th>Description</th></tr></thead><tbody><tr><td><code>Show First Page Progress Indicator</code></td><td>If <code>true</code>, displays the <code>firstPageLoadingIndicator</code> widget while the initial page is loading.</td></tr><tr><td><code>Show New Page Progress Indicator</code></td><td>If <code>true</code>, displays the <code>newPageLoadingIndicator</code> widget while loading subsequent pages.</td></tr></tbody></table>

***

### Children Slots

The Paginated ListView widget has three child slots that you must configure:

#### 1. `children` (Item Template)

**Purpose:** The widget template used to render each item in the list.

**Configuration:**

* Add a single child widget (e.g., Container, Card, or custom component).
* This widget will be repeated for each item in the data.
* Use `${currentItem}` to access the data for each item.
* Use `${currentIndex}` to access the item's position in the list.

**Example:** A Container with:

* Text widget displaying `${currentItem.title}`
* Image widget showing `${currentItem.imageUrl}`
* Button with onClick action

#### 2. `firstPageLoadingIndicator`

**Purpose:** Widget displayed while the first page of data is being loaded (initial load).

**Configuration:**

* Typically a centered circular progress indicator.
* Can include loading text, skeleton screens, or placeholder content.
* Shown only on the very first load when no data is available yet.

**Example Widgets:**

* Circular Progress Indicator
* Linear Progress Bar
* Skeleton loader (multiple placeholder containers)
* Custom loading animation

#### 3. `newPageLoadingIndicator`

**Purpose:** Widget displayed at the bottom of the list while loading additional pages.

**Configuration:**

* Usually a smaller progress indicator.
* Appears at the end of the list when the user scrolls near the bottom.
* Should be subtle so it doesn't disrupt the user's scrolling experience.

**Example Widgets:**

* Small Circular Progress Indicator
* Horizontal loading bar
* Text: "Loading more..."
* Custom pagination loader

***

### Complete Setup Example

Here's a comprehensive example of setting up a Paginated ListView for a product catalog using page-based pagination:

#### 1. API Configuration

**Endpoint:** `https://api.example.com/products` **Method:** GET **Query Parameters:**

* `page`: Variable - `${pageNumber}` (integer)
* `limit`: Fixed value - `20`

#### 2. Paginated ListView Properties

* **API Data Source:** `getProducts` (your API call)
* **First Page Key:** `1`
* **Next Page Key:** `${response.body.pagination.nextPage}`
* **Transform Items:** `${response.body.data}`
* **Show First Page Progress Indicator:** `true`
* **Show New Page Progress Indicator:** `true`

#### 3. Children Configuration

**Item Template (`children`):**

* Container with padding
  * Row
    * Image: `${currentItem.imageUrl}`
    * Column
      * Text: `${currentItem.name}`
      * Text: `$${currentItem.price}`

**First Page Indicator (`firstPageLoadingIndicator`):**

* Column (centered)
  * Circular Progress Indicator
  * Text: "Loading products..."

**New Page Indicator (`newPageLoadingIndicator`):**

* Container (centered, small padding)
  * Circular Progress Indicator (smaller size)

***

### Accessing Item Data

Inside the `children` template, you have access to special variables:

<table><thead><tr><th width="180">Variable</th><th>Description</th><th>Example</th></tr></thead><tbody><tr><td><code>currentItem</code></td><td>The data object for the current list item.</td><td><code>${currentItem.name}</code>, <code>${currentItem.id}</code></td></tr><tr><td><code>currentIndex</code></td><td>The zero-based index of the current item in the list.</td><td><code>${currentIndex}</code> returns 0, 1, 2...</td></tr></tbody></table>

**For object arrays:**

```javascript
${currentItem.title}
${currentItem.user.name}
${currentItem.price}
```

**For simple arrays:**

```javascript
${currentItem}  // The value itself
```

***

### Handling End of Data

The Paginated ListView needs to know when there are no more pages to load. This is determined by the `Next Page Key` expression:

**When to stop loading:**

* When `Next Page Key` returns `null`
* When `Next Page Key` returns `undefined`
* When `Next Page Key` is empty or invalid

**Examples:**

**Page-based with hasMore flag:**

```javascript
${if(response.body.hasMore, response.body.nextPage, null)}
```

**Offset-based with total count:**

```javascript
${if(lt(sum(response.body.offset, response.body.limit), response.body.total), sum(response.body.offset, 20), null)}
```

**Cursor-based:**

```javascript
${response.body.nextCursor}  // API returns null when no more data
```

{% hint style="info" %}
Make sure your API properly signals when there are no more results by returning null for the next page/cursor, or by providing a flag like `hasMore: false`.
{% endhint %}

***

### Error Handling

When the API call fails (network error, timeout, or API error):

* The loading indicator disappears.
* The user can pull to refresh to retry.
* Already loaded items remain visible.
* Consider adding error state widgets for better UX.

**Best Practice:** Add error handling in your API call configuration and show appropriate error messages to users.

***

### Guides

For comprehensive examples and real-world implementations, see:

{% content-ref url="/pages/5DLvZ6mbyAaRiwZbXH5h" %}
[Broken mention](broken://pages/5DLvZ6mbyAaRiwZbXH5h)
{% endcontent-ref %}

This guide includes:

* **Social Media Feed** - Build infinite scroll feeds with cursor-based pagination
* **E-Commerce Product Catalog** - Create product listings with offset-based pagination
* **Search Results** - Implement search with page-based pagination
* Performance optimization tips and best practices

### Best Practices

* **Choose the right page size**: Balance between fewer requests (larger pages) and faster initial load (smaller pages). 20-50 items per page is common.
* **Show loading states clearly**: Users should always know when data is being fetched.
* **Handle errors gracefully**: Network issues are common on mobile. Provide retry mechanisms.
* **Test with slow networks**: Ensure loading indicators work properly even with slow connections.
* **Optimize item templates**: Keep item widgets lightweight for smooth scrolling performance.
* **Use cursor-based pagination** for real-time feeds where data changes frequently.
* **Implement pull-to-refresh**: Allow users to manually refresh the list (use Refresh Indicator widget).
* **Consider caching**: For offline support, cache paginated data locally.

***

### Default Properties

The Paginated ListView widget supports the **Layout** and **Appearance** sections from [Default Properties](/ui-building-blocks/widgets/default-properties.md).


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.digia.tech/ui-building-blocks/widgets/scrolling-widgets/paginated-listview.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
