Flutter: ListView Pagination [Load More] example
Flutter
[ 318 Articles]
January 4, 2022
July 29, 2021
May 21, 2021
April 1, 2021
January 1, 2022
January 4, 2022
August 10, 2021
January 3, 2022
More
In real Flutter applications, we often load dynamic data from servers or databases instead of using hardcode/dummy data as we often see in the examples.
For example, you have an e-commerce app and need to load products from Rest API from the server-side. If we only have 10 or 20 products, it will be easy to fetch them all with just one get request. However, if your platform has thousands of products or even millions of products, you have to implement pagination to reduce the burden on the server and reduce latency and improve the apps performance as well.Advertisements
This article walks you through a complete example of creating a ListView with pagination in Flutter.
Table of Contents
- The Strategy
- App Preview
- The Code
- Conclusion
The Strategy
In the beginning, we send a get request and load only a fixed number of results [choose a number that makes sense in your case] and display them in a ListView.
When the user scrolls to the bottom of the ListView, we send another GET request and fetch another fixed number of results, and so on.
AdvertisementsTo know when the user scrolls near the bottom of a ListView, we will create a ScrollController and attach it to the ListView. When ScrollController.position.extendAfter has a value less than a certain level [200 or 300 is good to go], we will call a function that sends a new GET request.
You can find more information about ScrollController in the official docs. If you feel the words are too boring and confusing, just jump straight to the practical example below.
App Preview
This example will load some dummy blog posts from a public Pest API endpoint provided by jsonplaceholder.typicode.com [special thanks to the author for making an awesome tool for testing purposes]:
//jsonplaceholder.typicode.com/posts?_page=[page-number]&_limit=[some-number]When the app launches for the first time, we will fetch the first 20 posts. Next, every time we scroll near the bottom of the ListView, a function named _loadMore will be called and this function will load 20 more posts.
After all the posts from the API have been loaded, a message will appear letting us know that even if we scroll down, nothing will happen.
Progress indicators will also appear while we are sending requests to the server.
A demo is worth more than thousands of words:
The Code
1. Create a new Flutter project.
2. To send http requests with ease, we will the http package. Install it by running:
dart pub add http3. Remove all the default code in your main.dart and add the following:
// main.dart import 'package:flutter/material.dart'; import 'dart:convert'; import 'package:http/http.dart' as http; void main[] { runApp[MyApp[]]; } class MyApp extends StatelessWidget { @override Widget build[BuildContext context] { return MaterialApp[ // Remove the debug banner debugShowCheckedModeBanner: false, title: 'Kindacode.com', theme: ThemeData[ primarySwatch: Colors.pink, ], home: HomePage[]]; } } class HomePage extends StatefulWidget { const HomePage[{Key? key}] : super[key: key]; @override _HomePageState createState[] => _HomePageState[]; } class _HomePageState extends State { // We will fetch data from this Rest api final _baseUrl = '//jsonplaceholder.typicode.com/posts'; // At the beginning, we fetch the first 20 posts int _page = 0; int _limit = 20; // There is next page or not bool _hasNextPage = true; // Used to display loading indicators when _firstLoad function is running bool _isFirstLoadRunning = false; // Used to display loading indicators when _loadMore function is running bool _isLoadMoreRunning = false; // This holds the posts fetched from the server List _posts = []; // This function will be called when the app launches [see the initState function] void _firstLoad[] async { setState[[] { _isFirstLoadRunning = true; }]; try { final res = await http.get[Uri.parse["$_baseUrl?_page=$_page&_limit=$_limit"]]; setState[[] { _posts = json.decode[res.body]; }]; } catch [err] { print['Something went wrong']; } setState[[] { _isFirstLoadRunning = false; }]; } // This function will be triggered whenver the user scroll // to near the bottom of the list view void _loadMore[] async { if [_hasNextPage == true && _isFirstLoadRunning == false && _isLoadMoreRunning == false && _controller.position.extentAfter < 300] { setState[[] { _isLoadMoreRunning = true; // Display a progress indicator at the bottom }]; _page += 1; // Increase _page by 1 try { final res = await http.get[Uri.parse["$_baseUrl?_page=$_page&_limit=$_limit"]]; final List fetchedPosts = json.decode[res.body]; if [fetchedPosts.length > 0] { setState[[] { _posts.addAll[fetchedPosts]; }]; } else { // This means there is no more data // and therefore, we will not send another GET request setState[[] { _hasNextPage = false; }]; } } catch [err] { print['Something went wrong!']; } setState[[] { _isLoadMoreRunning = false; }]; } } // The controller for the ListView late ScrollController _controller; @override void initState[] { super.initState[]; _firstLoad[]; _controller = new ScrollController[]..addListener[_loadMore]; } @override void dispose[] { _controller.removeListener[_loadMore]; super.dispose[]; } @override Widget build[BuildContext context] { return Scaffold[ appBar: AppBar[ title: Text['Kindacode.com'], ], body: _isFirstLoadRunning ? Center[ child: CircularProgressIndicator[], ] : Column[ children: [ Expanded[ child: ListView.builder[ controller: _controller, itemCount: _posts.length, itemBuilder: [_, index] => Card[ margin: EdgeInsets.symmetric[vertical: 8, horizontal: 10], child: ListTile[ title: Text[_posts[index]['title']], subtitle: Text[_posts[index]['body']], ], ], ], ], // when the _loadMore function is running if [_isLoadMoreRunning == true] Padding[ padding: const EdgeInsets.only[top: 10, bottom: 40], child: Center[ child: CircularProgressIndicator[], ], ], // When nothing else to load if [_hasNextPage == false] Container[ padding: const EdgeInsets.only[top: 30, bottom: 40], color: Colors.amber, child: Center[ child: Text['You have fetched all of the content'], ], ], ], ], ]; } }Note: In many cases, the server-side gives us information about the total number of results it has or the total number of pages it has. In these scenarios, you can still do the same as the example above or modify the code a bit to check whether _loadMore should be called or not.
Conclusion
Weve examined an end-to-end example of implementing a ListView with pagination in a Flutter app. When you use your own API, there may be some differences in the URL structure, for example:
//www.example.com/api/products?currentPage=1&perPage=10 //www.kindacode.com/api/users?start=10&limit=20&order=descMake sure you understand the API youre going to get data from. If not, talk to your backend developers and ask them to provide the necessary information.
If youd like to learn more new stuff about Flutter, take a look at the following articles:
- Flutter & SQLite: CRUD Example
- Flutter and Firestore Database: CRUD example [null safety]
- Using GetX [Get] for Navigation and Routing in Flutter
- Using GetX [Get] for State Management in Flutter
- Flutter FutureBuilder example [null safety]
- Flutter StreamBuilder examples [null safety]
You can also take a tour around our Flutter topic page, or Dart topic page for the latest tutorials and examples.
how to handle this using provider?
There isnt much difference. Maybe I will write a new & standalone article about using provider.
Related Articles
December 28, 2021
December 28, 2021
December 28, 2021
January 3, 2022
December 24, 2021
December 24, 2021
December 23, 2021
December 25, 2021
December 21, 2021
December 20, 2021
December 18, 2021
December 18, 2021