Flutter Infinite Scroll Pagination (original) (raw)

Last Updated : 23 Jul, 2025

Infinite lists are a way to display paginated data efficiently. When a user scrolls to the end of the current page, more data is fetched and added to the list. This is also known as endless scrolling pagination, infinite scrolling pagination, auto-pagination, lazy loading pagination, and progressive loading pagination. It is good because it loads data only when the user interacts with the list. When data in the list is bigger it is better to use this widget for the app's performance optimizations and Interactive UI for Users.

What Will We Make?

We will create a user list with his/her name and email address. We get these data from Punk API. We will call an API Call to get data from it. A sample video is given below to get an idea about what we are going to do in this article.

Step By Step Implementation

**Step 1: Create a New Project in Android Studio

To set up Flutter Development on Android Studio please refer to Android Studio Setup for Flutter Development, and then create a new project in Android Studio please refer to Creating a Simple Application in Flutter.

Step 2: Add Package in your pubspec.yaml file

Dart `

dependencies: infinite_scroll_pagination: ^4.0.0

`

Step 3: Import the library in your page

Dart `

import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';

`

Step 4: Add Controllers and page size

Pagination Controllers is controller to manage the data refresh it,add page listeners

Dart `

final int _pageSize = 20;

final PagingController<int, dynamic> _pagingController = PagingController(firstPageKey: 1);

`

Step 5: Add API Request and get data

To get the data from sample API we will use http package

Dart `

class RemoteApi { static Future<List?> getBeerList( // Page means on which page you are currently int page, // Limit per page you want to set int limit, ) async { try { // Request the API on url final response = await http.get( Uri.parse( 'https://api.punkapi.com/v2/beers?' 'page=$page' '&per_page=$limit', ), ); if (response.statusCode == 200) { // Decode the response final mybody = jsonDecode(response.body);

    return mybody;
  }
} catch (e) {
  print("Error $e");
}
return null;

} }

`

Step 6: Update the API data in controllers and add listener to it

We create a function which will add the data in page controller

Dart `

Future _fetchPage(int pageKey) async { try { // get api /beers list from pages final newItems = await RemoteApi.getBeerList(pageKey, _pageSize); // Check if it is last page final isLastPage = newItems!.length < _pageSize; // If it is last page then append // last page else append new page if (isLastPage) { _pagingController.appendLastPage(newItems); } else { // Appending new page when it is not last page final nextPageKey = pageKey + 1; _pagingController.appendPage(newItems, nextPageKey); } } // Handle error in catch catch (error) { print(_pagingController.error); // Sets the error in controller _pagingController.error = error; } }

`

We will add a listener in init state to call the previous function whenver user go to next page or when more data is required to load

Dart `

@override void initState() { _pagingController.addPageRequestListener((pageKey) { _fetchPage(pageKey); }); super.initState(); }

`

Step 7: We will add UI in body to show infinite scroll page view

Now we will add a paged listview in our screens

Dart `

Scaffold( // Page Listview with divider as a separation body: PagedListView<int, dynamic>.separated( pagingController: pagingController, builderDelegate: PagedChildBuilderDelegate( animateTransitions: true, itemBuilder: (, item, index) => ListTile( leading: CircleAvatar( radius: 20, backgroundImage: NetworkImage(item["image_url"]), ), title: Text(item["name"]), ), ), separatorBuilder: (_, index) => const Divider(), ), )

`

Step 8: Dispose the Controller

Dart `

@override void dispose() { _pagingController.dispose(); super.dispose(); }

`

Step 9: Refresh the listview on pulling down

Dart `

// Refrsh Indicator pull down RefreshIndicator( onRefresh: () => Future.sync( // Refresh through page controllers () => _pagingController.refresh(), ),child:...)

`

You are good to go!!!

Additional Tips

We have used the listview seperated here to show the data. You can use different widget available in

In all this option seperated constructor is also available

Complete Source Code

Dart `

class InfiniteScrollExample extends StatefulWidget { const InfiniteScrollExample({super.key});

@override State createState() => _InfiniteScrollExampleState(); }

class _InfiniteScrollExampleState extends State { final int _pageSize = 20;

final PagingController<int, dynamic> _pagingController = PagingController(firstPageKey: 1);

@override void initState() { _pagingController.addPageRequestListener((pageKey) { _fetchPage(pageKey); }); super.initState(); }

Future _fetchPage(int pageKey) async { try { // get api /beers list from pages final newItems = await RemoteApi.getBeerList(pageKey, _pageSize); // Check if it is last page final isLastPage = newItems!.length < _pageSize; // If it is last page then append last page else append new page if (isLastPage) { _pagingController.appendLastPage(newItems); } else { // Appending new page when it is not last page final nextPageKey = pageKey + 1; _pagingController.appendPage(newItems, nextPageKey); } } // Handle error in catch catch (error) { print(_pagingController.error); // Sets the error in controller _pagingController.error = error; } }

@override Widget build(BuildContext context) => // Refrsh Indicator pull down RefreshIndicator( onRefresh: () => Future.sync( // Refresh through page controllers () => pagingController.refresh(), ), child: Scaffold( appBar: AppBar( title: const Text("Pagination Scroll Flutter Template"), ), // Page Listview with divider as a separation body: PagedListView<int, dynamic>.separated( pagingController: pagingController, builderDelegate: PagedChildBuilderDelegate( animateTransitions: true, itemBuilder: (, item, index) => ListTile( leading: CircleAvatar( radius: 20, backgroundImage: NetworkImage(item["image_url"]), ), title: Text(item["name"]), ), ), separatorBuilder: (, index) => const Divider(), ), ), );

@override void dispose() { _pagingController.dispose(); super.dispose(); } }

class RemoteApi { static Future<List?> getBeerList( // Page means on which page you are currently int page, // Limit per page you want to set int limit, ) async { try { // Request the API on url final response = await http.get( Uri.parse( 'https://api.punkapi.com/v2/beers?' 'page=$page' '&per_page=$limit', ), ); if (response.statusCode == 200) { // Decode the response final mybody = jsonDecode(response.body); return mybody; } } catch (e) { print("Error $e"); } return null; } }

`

Output: