Open In App

Flutter – Infinite Scroll Pagination

Last Updated : 14 Feb, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

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<dynamic>?> 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<void> _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<dynamic>(
              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

  • GridView: A widget for creating a scrollable grid layout of items.
  • SliverGrid: Similar to GridView, but used within a CustomScrollView for more advanced scrolling effects.
  • ListView: Displays a scrollable list of widgets in a single column.
  • SliverList: A list within a CustomScrollView for dynamic and optimized scrolling.
  • Custom Layout: Custome layout as per you requirement

In all this option seperated constructor is also available

Complete Source Code

Dart




class InfiniteScrollExample extends StatefulWidget {
  const InfiniteScrollExample({super.key});
  
  @override
  State<InfiniteScrollExample> createState() => _InfiniteScrollExampleState();
}
  
class _InfiniteScrollExampleState extends State<InfiniteScrollExample> {
  final int _pageSize = 20;
  
  final PagingController<int, dynamic> _pagingController =
      PagingController(firstPageKey: 1);
  
  @override
  void initState() {
    _pagingController.addPageRequestListener((pageKey) {
      _fetchPage(pageKey);
    });
    super.initState();
  }
  
  Future<void> _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<dynamic>(
              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<dynamic>?> 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:



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads