15

I am new to Flutter. I want to do the pagination with a REST API. My question is how to add an infinite scroll and then load the data to the next page. How can I load to "https://MY_API_URL?page=2", page 3 and so on?

Mark Rotteveel
110k240 gold badges160 silver badges232 bronze badges
asked Dec 8, 2019 at 9:44
1
  • 1
    call your getJSONData(int page) as a result of calling itemBuilder and cache it somewhere (for example using MapCache) Commented Dec 8, 2019 at 9:49

6 Answers 6

14

Edit change sendPagesDataRequest to the following should work
if json string you gave me is correct

Future<PagesData> sendPagesDataRequest(int page) async {
 print('page ${page}');
 try {
 /*String url = Uri.encodeFull(
 'http://api.worldbank.org/v2/country?page=$page&format=json');*/
 String url = Uri.encodeFull("https://MY_API_URL?page=$page");
 http.Response response = await http.get(url);
 print('body ${response.body}');
 /*String responseString = '''
 {"current_page": 1, 
"data": [ 
 { "id": 1, "title": "Germa", "likes": 5, "image": "https://picsum.photos/250?image=8"}, 
 { "id": 2, "title": "Jepun", "likes": 3, "image": "https://picsum.photos/250?image=9"} 
 ], 
 "first_page_url": "https:/API_URL?page=1", 
 "from": 1, 
 "last_page": 30, 
 "last_page_url": "https:/API_URLpage=30", 
 "next_page_url": "https:/API_URL?page=2"
}
 ''';*/
 PagesData pagesData = pagesDataFromJson(response.body);
 return pagesData;
 } catch (e) {
 if (e is IOException) {
 /*return CountriesData.withError(
 'Please check your internet connection.');*/
 } else {
 print(e.toString());
 /*return CountriesData.withError('Something went wrong.');*/
 }
 }
 }

Edit
full code with new sendPagesDataRequest

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_paginator/flutter_paginator.dart';
import 'package:flutter_paginator/enums.dart';
import 'package:cached_network_image/cached_network_image.dart';
// To parse this JSON data, do
//
// final pagesData = pagesDataFromJson(jsonString);
import 'dart:convert';
PagesData pagesDataFromJson(String str) => PagesData.fromJson(json.decode(str));
String pagesDataToJson(PagesData data) => json.encode(data.toJson());
class PagesData {
 int currentPage;
 List<Datum> data;
 String firstPageUrl;
 int from;
 int lastPage;
 String lastPageUrl;
 String nextPageUrl;
 PagesData({
 this.currentPage,
 this.data,
 this.firstPageUrl,
 this.from,
 this.lastPage,
 this.lastPageUrl,
 this.nextPageUrl,
 });
 factory PagesData.fromJson(Map<String, dynamic> json) => PagesData(
 currentPage: json["current_page"],
 data: List<Datum>.from(json["data"].map((x) => Datum.fromJson(x))),
 firstPageUrl: json["first_page_url"],
 from: json["from"],
 lastPage: json["last_page"],
 lastPageUrl: json["last_page_url"],
 nextPageUrl: json["next_page_url"],
 );
 Map<String, dynamic> toJson() => {
 "current_page": currentPage,
 "data": List<dynamic>.from(data.map((x) => x.toJson())),
 "first_page_url": firstPageUrl,
 "from": from,
 "last_page": lastPage,
 "last_page_url": lastPageUrl,
 "next_page_url": nextPageUrl,
 };
}
class Datum {
 int id;
 String title;
 int likes;
 String image;
 Datum({
 this.id,
 this.title,
 this.likes,
 this.image,
 });
 factory Datum.fromJson(Map<String, dynamic> json) => Datum(
 id: json["id"],
 title: json["title"],
 likes: json["likes"],
 image: json["image"],
 );
 Map<String, dynamic> toJson() => {
 "id": id,
 "title": title,
 "likes": likes,
 "image": image,
 };
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
 return MaterialApp(
 title: 'Flutter Paginator',
 home: HomePage(),
 );
 }
}
class HomePage extends StatefulWidget {
 @override
 State<StatefulWidget> createState() {
 return HomeState();
 }
}
class HomeState extends State<HomePage> {
 GlobalKey<PaginatorState> paginatorGlobalKey = GlobalKey();
 @override
 Widget build(BuildContext context) {
 return Scaffold(
 appBar: AppBar(
 title: Text('Flutter Paginator'),
 actions: <Widget>[
 IconButton(
 icon: Icon(Icons.format_list_bulleted),
 onPressed: () {
 paginatorGlobalKey.currentState
 .changeState(listType: ListType.LIST_VIEW);
 },
 ),
 IconButton(
 icon: Icon(Icons.grid_on),
 onPressed: () {
 paginatorGlobalKey.currentState.changeState(
 listType: ListType.GRID_VIEW,
 gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
 crossAxisCount: 2),
 );
 },
 ),
 IconButton(
 icon: Icon(Icons.library_books),
 onPressed: () {
 paginatorGlobalKey.currentState
 .changeState(listType: ListType.PAGE_VIEW);
 },
 ),
 ],
 ),
 body: Paginator.listView(
 key: paginatorGlobalKey,
 pageLoadFuture: sendPagesDataRequest,
 pageItemsGetter: listItemsGetterPages,
 listItemBuilder: listItemBuilder,
 loadingWidgetBuilder: loadingWidgetMaker,
 errorWidgetBuilder: errorWidgetMaker,
 emptyListWidgetBuilder: emptyListWidgetMaker,
 totalItemsGetter: totalPagesGetter,
 pageErrorChecker: pageErrorChecker,
 scrollPhysics: BouncingScrollPhysics(),
 ),
 floatingActionButton: FloatingActionButton(
 onPressed: () {
 paginatorGlobalKey.currentState.changeState(
 pageLoadFuture: sendCountriesDataRequest, resetState: true);
 },
 child: Icon(Icons.refresh),
 ),
 );
 }
 Future<CountriesData> sendCountriesDataRequest(int page) async {
 print('page ${page}');
 try {
 String url = Uri.encodeFull(
 'http://api.worldbank.org/v2/country?page=$page&format=json');
 http.Response response = await http.get(url);
 print('body ${response.body}');
 return CountriesData.fromResponse(response);
 } catch (e) {
 if (e is IOException) {
 return CountriesData.withError(
 'Please check your internet connection.');
 } else {
 print(e.toString());
 return CountriesData.withError('Something went wrong.');
 }
 }
 }
 Future<PagesData> sendPagesDataRequest(int page) async {
 print('page ${page}');
 try {
 /*String url = Uri.encodeFull(
 'http://api.worldbank.org/v2/country?page=$page&format=json');*/
 String url = Uri.encodeFull("https://MY_API_URL?page=$page");
 http.Response response = await http.get(url);
 print('body ${response.body}');
 /*String responseString = '''
 {"current_page": 1, 
"data": [ 
 { "id": 1, "title": "Germa", "likes": 5, "image": "https://picsum.photos/250?image=8"}, 
 { "id": 2, "title": "Jepun", "likes": 3, "image": "https://picsum.photos/250?image=9"} 
 ], 
 "first_page_url": "https:/API_URL?page=1", 
 "from": 1, 
 "last_page": 30, 
 "last_page_url": "https:/API_URLpage=30", 
 "next_page_url": "https:/API_URL?page=2"
}
 ''';*/
 PagesData pagesData = pagesDataFromJson(response.body);
 return pagesData;
 } catch (e) {
 if (e is IOException) {
 /*return CountriesData.withError(
 'Please check your internet connection.');*/
 } else {
 print(e.toString());
 /*return CountriesData.withError('Something went wrong.');*/
 }
 }
 }
 List<dynamic> listItemsGetter(CountriesData countriesData) {
 List<String> list = [];
 countriesData.countries.forEach((value) {
 list.add(value['name']);
 });
 return list;
 }
 List<dynamic> listItemsGetterPages(PagesData pagesData) {
 List<Datum> list = [];
 pagesData.data.forEach((value) {
 list.add(value);
 });
 return list;
 }
 Widget listItemBuilder(dynamic item, int index) {
 return Container(
 decoration: BoxDecoration(
 color: Colors.blue[50]
 ),
 margin: const EdgeInsets.all(8),
 child: Column(
 children: <Widget>[
 new CachedNetworkImage(
 imageUrl: item.image,
 placeholder: (context, url) => new CircularProgressIndicator(),
 errorWidget: (context, url, error) => new Icon(Icons.error),
 ),
 ListTile(title: Text(item.title), subtitle: Text('Likes: ' + item.likes.toString()),),
 ],),
 );
 }
 Widget loadingWidgetMaker() {
 return Container(
 alignment: Alignment.center,
 height: 160.0,
 child: CircularProgressIndicator(),
 );
 }
 Widget errorWidgetMaker(PagesData countriesData, retryListener) {
 return Column(
 mainAxisAlignment: MainAxisAlignment.center,
 children: <Widget>[
 Padding(
 padding: const EdgeInsets.all(16.0),
 child: Text("error"),
 ),
 FlatButton(
 onPressed: retryListener,
 child: Text('Retry'),
 )
 ],
 );
 }
 Widget emptyListWidgetMaker(PagesData countriesData) {
 return Center(
 child: Text('No countries in the list'),
 );
 }
 int totalPagesGetter(PagesData pagesData) {
 return pagesData.lastPage;
 }
 bool pageErrorChecker(PagesData pagesData) {
 //return countriesData.statusCode != 200;
 return false;
 }
}
class CountriesData {
 List<dynamic> countries;
 int statusCode;
 String errorMessage;
 int total;
 int nItems;
 CountriesData.fromResponse(http.Response response) {
 this.statusCode = response.statusCode;
 List jsonData = json.decode(response.body);
 countries = jsonData[1];
 total = jsonData[0]['total'];
 nItems = countries.length;
 }
 CountriesData.withError(String errorMessage) {
 this.errorMessage = errorMessage;
 }
}

Edit
you need to change sendPagesDataRequest, I use static string
Assume your json string like this

{"current_page": 1, 
"data": [ 
 { "id": 1, "title": "Germa", "likes": 5, "image": "image url"}, 
 { "id": 2, "title": "Jepun", "likes": 3, "image": "image url"} 
 ], 
 "first_page_url": "https:/API_URL?page=1", 
 "from": 1, 
 "last_page": 30, 
 "last_page_url": "https:/API_URLpage=30", 
 "next_page_url": "https:/API_URL?page=2"
}

Edit working demo

enter image description here

Edit full code

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_paginator/flutter_paginator.dart';
import 'package:flutter_paginator/enums.dart';
import 'package:cached_network_image/cached_network_image.dart';
// To parse this JSON data, do
//
// final pagesData = pagesDataFromJson(jsonString);
import 'dart:convert';
PagesData pagesDataFromJson(String str) => PagesData.fromJson(json.decode(str));
String pagesDataToJson(PagesData data) => json.encode(data.toJson());
class PagesData {
 int currentPage;
 List<Datum> data;
 String firstPageUrl;
 int from;
 int lastPage;
 String lastPageUrl;
 String nextPageUrl;
 PagesData({
 this.currentPage,
 this.data,
 this.firstPageUrl,
 this.from,
 this.lastPage,
 this.lastPageUrl,
 this.nextPageUrl,
 });
 factory PagesData.fromJson(Map<String, dynamic> json) => PagesData(
 currentPage: json["current_page"],
 data: List<Datum>.from(json["data"].map((x) => Datum.fromJson(x))),
 firstPageUrl: json["first_page_url"],
 from: json["from"],
 lastPage: json["last_page"],
 lastPageUrl: json["last_page_url"],
 nextPageUrl: json["next_page_url"],
 );
 Map<String, dynamic> toJson() => {
 "current_page": currentPage,
 "data": List<dynamic>.from(data.map((x) => x.toJson())),
 "first_page_url": firstPageUrl,
 "from": from,
 "last_page": lastPage,
 "last_page_url": lastPageUrl,
 "next_page_url": nextPageUrl,
 };
}
class Datum {
 int id;
 String title;
 int likes;
 String image;
 Datum({
 this.id,
 this.title,
 this.likes,
 this.image,
 });
 factory Datum.fromJson(Map<String, dynamic> json) => Datum(
 id: json["id"],
 title: json["title"],
 likes: json["likes"],
 image: json["image"],
 );
 Map<String, dynamic> toJson() => {
 "id": id,
 "title": title,
 "likes": likes,
 "image": image,
 };
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
 return MaterialApp(
 title: 'Flutter Paginator',
 home: HomePage(),
 );
 }
}
class HomePage extends StatefulWidget {
 @override
 State<StatefulWidget> createState() {
 return HomeState();
 }
}
class HomeState extends State<HomePage> {
 GlobalKey<PaginatorState> paginatorGlobalKey = GlobalKey();
 @override
 Widget build(BuildContext context) {
 return Scaffold(
 appBar: AppBar(
 title: Text('Flutter Paginator'),
 actions: <Widget>[
 IconButton(
 icon: Icon(Icons.format_list_bulleted),
 onPressed: () {
 paginatorGlobalKey.currentState
 .changeState(listType: ListType.LIST_VIEW);
 },
 ),
 IconButton(
 icon: Icon(Icons.grid_on),
 onPressed: () {
 paginatorGlobalKey.currentState.changeState(
 listType: ListType.GRID_VIEW,
 gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
 crossAxisCount: 2),
 );
 },
 ),
 IconButton(
 icon: Icon(Icons.library_books),
 onPressed: () {
 paginatorGlobalKey.currentState
 .changeState(listType: ListType.PAGE_VIEW);
 },
 ),
 ],
 ),
 body: Paginator.listView(
 key: paginatorGlobalKey,
 pageLoadFuture: sendPagesDataRequest,
 pageItemsGetter: listItemsGetterPages,
 listItemBuilder: listItemBuilder,
 loadingWidgetBuilder: loadingWidgetMaker,
 errorWidgetBuilder: errorWidgetMaker,
 emptyListWidgetBuilder: emptyListWidgetMaker,
 totalItemsGetter: totalPagesGetter,
 pageErrorChecker: pageErrorChecker,
 scrollPhysics: BouncingScrollPhysics(),
 ),
 floatingActionButton: FloatingActionButton(
 onPressed: () {
 paginatorGlobalKey.currentState.changeState(
 pageLoadFuture: sendCountriesDataRequest, resetState: true);
 },
 child: Icon(Icons.refresh),
 ),
 );
 }
 Future<CountriesData> sendCountriesDataRequest(int page) async {
 print('page ${page}');
 try {
 String url = Uri.encodeFull(
 'http://api.worldbank.org/v2/country?page=$page&format=json');
 http.Response response = await http.get(url);
 print('body ${response.body}');
 return CountriesData.fromResponse(response);
 } catch (e) {
 if (e is IOException) {
 return CountriesData.withError(
 'Please check your internet connection.');
 } else {
 print(e.toString());
 return CountriesData.withError('Something went wrong.');
 }
 }
 }
 Future<PagesData> sendPagesDataRequest(int page) async {
 print('page ${page}');
 try {
 String url = Uri.encodeFull(
 'http://api.worldbank.org/v2/country?page=$page&format=json');
 http.Response response = await http.get(url);
 print('body ${response.body}');
 String responseString = '''
 {"current_page": 1, 
"data": [ 
 { "id": 1, "title": "Germa", "likes": 5, "image": "https://picsum.photos/250?image=8"}, 
 { "id": 2, "title": "Jepun", "likes": 3, "image": "https://picsum.photos/250?image=9"} 
 ], 
 "first_page_url": "https:/API_URL?page=1", 
 "from": 1, 
 "last_page": 30, 
 "last_page_url": "https:/API_URLpage=30", 
 "next_page_url": "https:/API_URL?page=2"
}
 ''';
 PagesData pagesData = pagesDataFromJson(responseString);
 return pagesData;
 } catch (e) {
 if (e is IOException) {
 /*return CountriesData.withError(
 'Please check your internet connection.');*/
 } else {
 print(e.toString());
 /*return CountriesData.withError('Something went wrong.');*/
 }
 }
 }
 List<dynamic> listItemsGetter(CountriesData countriesData) {
 List<String> list = [];
 countriesData.countries.forEach((value) {
 list.add(value['name']);
 });
 return list;
 }
 List<dynamic> listItemsGetterPages(PagesData pagesData) {
 List<Datum> list = [];
 pagesData.data.forEach((value) {
 list.add(value);
 });
 return list;
 }
 Widget listItemBuilder(dynamic item, int index) {
 return Container(
 decoration: BoxDecoration(
 color: Colors.blue[50]
 ),
 margin: const EdgeInsets.all(8),
 child: Column(
 children: <Widget>[
 new CachedNetworkImage(
 imageUrl: item.image,
 placeholder: (context, url) => new CircularProgressIndicator(),
 errorWidget: (context, url, error) => new Icon(Icons.error),
 ),
 ListTile(title: Text(item.title), subtitle: Text('Likes: ' + item.likes.toString()),),
 ],),
 );
 }
 Widget loadingWidgetMaker() {
 return Container(
 alignment: Alignment.center,
 height: 160.0,
 child: CircularProgressIndicator(),
 );
 }
 Widget errorWidgetMaker(PagesData countriesData, retryListener) {
 return Column(
 mainAxisAlignment: MainAxisAlignment.center,
 children: <Widget>[
 Padding(
 padding: const EdgeInsets.all(16.0),
 child: Text("error"),
 ),
 FlatButton(
 onPressed: retryListener,
 child: Text('Retry'),
 )
 ],
 );
 }
 Widget emptyListWidgetMaker(PagesData countriesData) {
 return Center(
 child: Text('No countries in the list'),
 );
 }
 int totalPagesGetter(PagesData pagesData) {
 return pagesData.lastPage;
 }
 bool pageErrorChecker(PagesData pagesData) {
 //return countriesData.statusCode != 200;
 return false;
 }
}
class CountriesData {
 List<dynamic> countries;
 int statusCode;
 String errorMessage;
 int total;
 int nItems;
 CountriesData.fromResponse(http.Response response) {
 this.statusCode = response.statusCode;
 List jsonData = json.decode(response.body);
 countries = jsonData[1];
 total = jsonData[0]['total'];
 nItems = countries.length;
 }
 CountriesData.withError(String errorMessage) {
 this.errorMessage = errorMessage;
 }
}

You can use package https://pub.dev/packages/flutter_paginator
It will auto call your REST with page parameter
In the following demo, I add print message , so you can see it auto call rest with page when scroll down
You can copy paste run full code below

code snippet

Future<CountriesData> sendCountriesDataRequest(int page) async {
 print('page ${page}');
 try {
 String url = Uri.encodeFull(
 'http://api.worldbank.org/v2/country?page=$page&format=json');
 http.Response response = await http.get(url);
 print('body ${response.body}');
 return CountriesData.fromResponse(response);
 } catch (e) {
 if (e is IOException) {
 return CountriesData.withError(
 'Please check your internet connection.');
 } else {
 print(e.toString());
 return CountriesData.withError('Something went wrong.');
 }
 }
 }

working demo

enter image description here

full demo code

 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
 import 'package:flutter/material.dart';
 import 'package:http/http.dart' as http;
 import 'package:flutter_paginator/flutter_paginator.dart';
 import 'package:flutter_paginator/enums.dart';
 void main() => runApp(MyApp());
 class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
 return MaterialApp(
 title: 'Flutter Paginator',
 home: HomePage(),
 );
 }
 }
 class HomePage extends StatefulWidget {
 @override
 State<StatefulWidget> createState() {
 return HomeState();
 }
 }
 class HomeState extends State<HomePage> {
 GlobalKey<PaginatorState> paginatorGlobalKey = GlobalKey();
 @override
 Widget build(BuildContext context) {
 return Scaffold(
 appBar: AppBar(
 title: Text('Flutter Paginator'),
 actions: <Widget>[
 IconButton(
 icon: Icon(Icons.format_list_bulleted),
 onPressed: () {
 paginatorGlobalKey.currentState
 .changeState(listType: ListType.LIST_VIEW);
 },
 ),
 IconButton(
 icon: Icon(Icons.grid_on),
 onPressed: () {
 paginatorGlobalKey.currentState.changeState(
 listType: ListType.GRID_VIEW,
 gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
 crossAxisCount: 2),
 );
 },
 ),
 IconButton(
 icon: Icon(Icons.library_books),
 onPressed: () {
 paginatorGlobalKey.currentState
 .changeState(listType: ListType.PAGE_VIEW);
 },
 ),
 ],
 ),
 body: Paginator.listView(
 key: paginatorGlobalKey,
 pageLoadFuture: sendCountriesDataRequest,
 pageItemsGetter: listItemsGetter,
 listItemBuilder: listItemBuilder,
 loadingWidgetBuilder: loadingWidgetMaker,
 errorWidgetBuilder: errorWidgetMaker,
 emptyListWidgetBuilder: emptyListWidgetMaker,
 totalItemsGetter: totalPagesGetter,
 pageErrorChecker: pageErrorChecker,
 scrollPhysics: BouncingScrollPhysics(),
 ),
 floatingActionButton: FloatingActionButton(
 onPressed: () {
 paginatorGlobalKey.currentState.changeState(
 pageLoadFuture: sendCountriesDataRequest, resetState: true);
 },
 child: Icon(Icons.refresh),
 ),
 );
 }
 Future<CountriesData> sendCountriesDataRequest(int page) async {
 print('page ${page}');
 try {
 String url = Uri.encodeFull(
 'http://api.worldbank.org/v2/country?page=$page&format=json');
 http.Response response = await http.get(url);
 print('body ${response.body}');
 return CountriesData.fromResponse(response);
 } catch (e) {
 if (e is IOException) {
 return CountriesData.withError(
 'Please check your internet connection.');
 } else {
 print(e.toString());
 return CountriesData.withError('Something went wrong.');
 }
 }
 }
 List<dynamic> listItemsGetter(CountriesData countriesData) {
 List<String> list = [];
 countriesData.countries.forEach((value) {
 list.add(value['name']);
 });
 return list;
 }
 Widget listItemBuilder(value, int index) {
 return ListTile(
 leading: Text(index.toString()),
 title: Text(value),
 );
 }
 Widget loadingWidgetMaker() {
 return Container(
 alignment: Alignment.center,
 height: 160.0,
 child: CircularProgressIndicator(),
 );
 }
 Widget errorWidgetMaker(CountriesData countriesData, retryListener) {
 return Column(
 mainAxisAlignment: MainAxisAlignment.center,
 children: <Widget>[
 Padding(
 padding: const EdgeInsets.all(16.0),
 child: Text(countriesData.errorMessage),
 ),
 FlatButton(
 onPressed: retryListener,
 child: Text('Retry'),
 )
 ],
 );
 }
 Widget emptyListWidgetMaker(CountriesData countriesData) {
 return Center(
 child: Text('No countries in the list'),
 );
 }
 int totalPagesGetter(CountriesData countriesData) {
 return countriesData.total;
 }
 bool pageErrorChecker(CountriesData countriesData) {
 return countriesData.statusCode != 200;
 }
 }
 class CountriesData {
 List<dynamic> countries;
 int statusCode;
 String errorMessage;
 int total;
 int nItems;
 CountriesData.fromResponse(http.Response response) {
 this.statusCode = response.statusCode;
 List jsonData = json.decode(response.body);
 countries = jsonData[1];
 total = jsonData[0]['total'];
 nItems = countries.length;
 }
 CountriesData.withError(String errorMessage) {
 this.errorMessage = errorMessage;
 }
 }

Output

I/flutter (20369): page 1
I/flutter (20369): body [{"page":1,"pages":7,"per_page":"50","total":304},[{"id":"ABW","iso2Code":"AW","name":"Aruba","region":{"id":"LCN","iso2code":"ZJ","value":"Latin America & Caribbean "},"adminregion":{"id":"","iso2code":"","value":""},"incomeLevel":{"id":"HIC","iso2code":"XD","value":"High income"},"lendingType":{"id":"LNX","iso2code":"XX","value":"Not classified"},"capitalCity":"Oranjestad","longitude":"-70.0167","latitude":"12.5167"},{"id":"AFG","iso2Code":"AF","name":"Afghanistan","region":{"id":"SAS","iso2code":"8S","value":"South Asia"},"adminregion":{"id":"SAS","iso2code":"8S","value":"South Asia"},"incomeLevel":{"id":"LIC","iso2code":"XM","value":"Low income"},"lendingType":{"id":"IDX","iso2code":"XI","value":"IDA"},"capitalCity":"Kabul","longitude":"69.1761","latitude":"34.5228"},{"id":"AFR","iso2Code":"A9","name":"Africa","region":{"id":"NA","iso2code":"NA","value":"Aggregates"},"adminregion":{"id":"","iso2code":"","value":""},"incomeLevel":{"id":"NA","iso2code":"NA","value":"Aggregates"},"lendingType":{"id":""
I/flutter (20369): page 2
I/flutter (20369): body [{"page":2,"pages":7,"per_page":"50","total":304},[{"id":"CIV","iso2Code":"CI","name":"Cote d'Ivoire","region":{"id":"SSF","iso2code":"ZG","value":"Sub-Saharan Africa "},"adminregion":{"id":"SSA","iso2code":"ZF","value":"Sub-Saharan Africa (excluding high income)"},"incomeLevel":{"id":"LMC","iso2code":"XN","value":"Lower middle income"},"lendingType":{"id":"IDX","iso2code":"XI","value":"IDA"},"capitalCity":"Yamoussoukro","longitude":"-4.0305","latitude":"5.332"},{"id":"CLA","iso2Code":"C6","name":"Latin America and the Caribbean (IFC classification)","region":{"id":"NA","iso2code":"NA","value":"Aggregates"},"adminregion":{"id":"","iso2code":"","value":""},"incomeLevel":{"id":"NA","iso2code":"NA","value":"Aggregates"},"lendingType":{"id":"","iso2code":"","value":"Aggregates"},"capitalCity":"","longitude":"","latitude":""},{"id":"CME","iso2Code":"C7","name":"Middle East and North Africa (IFC classification)","region":{"id":"NA","iso2code":"NA","value":"Aggregates"},"adminregion":{"id":"","iso2code":"","va
answered Dec 9, 2019 at 1:36
Sign up to request clarification or add additional context in comments.

25 Comments

thank you for your reply, the body part here Paginator.listView() cannot change or remove?
You can not remove. it belong to this plugin, it integrate all related actions to this plugin
alright, I am confuse about how to add this plugin code with my code
you can put _buildImageColumn(data[index]) in listItemBuilder
plugin just integrate all actions to it. like empty data use emptyListWidgetMaker and error use errorWidgetMaker . you can copy paste full code and change to yours.
|
7

Infinite Scrolling Pagination is a tough task.

Besides just fetching new items lazily, you want to keep the user posted on your current state. For example, if you're loading the first page, you might want to show a progress indicator in the middle of the screen. But, if you're loading a subsequent page, you probably want to show a progress indicator at the bottom. The same is true for error indicators.

You also need to stop requesting new pages if the list from the server is either empty or completed. Not even to mention that you probably want to add "retry" buttons for failed requests.

There's now a package called Infinite Scroll Pagination that can handle everything for you, and the usage is pretty simple. To showcase that, I'll use the same country list example from @chunhunghan answer:

class CountryListView extends StatefulWidget {
 @override
 _CountryListViewState createState() => _CountryListViewState();
}
class _CountryListViewState extends State<CountryListView> {
 static const _pageSize = 20;
 final PagingController<int, Country> _pagingController =
 PagingController(firstPageKey: 0);
 @override
 void initState() {
 _pagingController.addPageRequestListener((pageKey) {
 _fetchPage(pageKey);
 });
 super.initState();
 }
 void _fetchPage(int pageKey) {
 RemoteApi.getCountryList(pageKey, _pageSize).then((newItems) {
 final isLastPage = newItems.length < _pageSize;
 if (isLastPage) {
 _pagingController.appendLastPage(newItems);
 } else {
 final nextPageKey = pageKey + newItems.length;
 _pagingController.appendPage(newItems, nextPageKey);
 }
 }).catchError((error) {
 _pagingController.error = error;
 });
 }
 @override
 Widget build(BuildContext context) => PagedListView<int, Country>(
 pagingController: _pagingController,
 builderDelegate: PagedChildBuilderDelegate<Country>(
 itemBuilder: (context, item, index) => CountryListItem(
 country: item,
 ),
 ),
 );
 @override
 void dispose() {
 _pagingController.dispose();
 super.dispose();
 }
}

In the code above, all of the issues I listed in the beginning (and others) are addressed, and you can customize everything if you need.

Disclosure: I'm the package author, so feel free to message me with any doubts you may have.

answered Aug 27, 2020 at 9:02

2 Comments

Nice. Thank you for your reply. I will try to use this package
@Edson you did a great job with this library!
1

I have created a lightweight example of an infinite loading list with pagination. New items are requested as you reach the bottom of the list. Usage looks like this:

import 'package:flutter/material.dart';
class Example extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
 return InfiniteList(
 widgetBuilder: (item) {
 return Text(item);
 },
 loadMore: (lastLoaded) {
 if (lastLoaded == null) {
 //first load request
 return ["hello", "world"];
 } else {
 //subsequent load request(s)
 return [];
 }
 },
 onItemSelected: (item) {
 print(item);
 },
 );
 }
}

The idea is to paginate based on the last loaded item, lastLoaded rather than a page number. Doing this helps to ensure you don't miss or duplicate anything if the contents of page X+1 changes after you already loaded page X (i.e. when something is added or removed from the database).

If your API doesn't support that, or you don't want it, you could add a page number attribute to each of your items and then do:

something.load(page: lastLoaded.pageNumber + 1);

The implementation for InfiniteList looks like this:

import 'package:flutter/material.dart';
extension on List {
 Object lastOrNull() {
 return this.isNotEmpty ? this.last : null;
 }
}
typedef ItemWidgetBuilder = Widget Function(Object item);
typedef FutureItemsCallback = Future<List<Object>> Function(Object lastLoadedItem);
typedef ItemCallback = void Function(Object item);
class InfiniteList extends StatefulWidget {
 final ItemWidgetBuilder widgetBuilder;
 final FutureItemsCallback loadMore;
 final ItemCallback onItemSelected;
 InfiniteList({Key key, @required this.widgetBuilder, @required this.loadMore, this.onItemSelected}) : super(key: key);
 @override
 State<StatefulWidget> createState() {
 return InfiniteListState();
 }
}
class InfiniteListState extends State<InfiniteList> {
 List<Object> items = [];
 bool shouldTryToLoadMore = true;
 @override
 void initState() {
 super.initState();
 waitOnItems();
 }
 void waitOnItems() async {
 try {
 final items = await widget.loadMore(this.items.lastOrNull());
 this.shouldTryToLoadMore = items.isNotEmpty;
 setState(() {
 this.items.addAll(items);
 });
 } catch(error) {
 print(error);
 }
 }
 @override
 Widget build(BuildContext context) {
 if (items.isEmpty) {
 return initiallyLoading();
 } else {
 //TODO: show progress bar at the bottom if loading more
 return list();
 }
 }
 Widget list() {
 return ListView.builder(
 itemCount: shouldTryToLoadMore ? null : items.length,
 itemBuilder: (context, index) {
 if (shouldTryToLoadMore && index == items.length - 1) {
 waitOnItems();
 return null;
 } else if (index >= items.length) {
 return null;
 } else if (widget.onItemSelected != null) {
 return InkWell(
 onTap: () => {
 widget.onItemSelected(items[index])
 },
 child: widget.widgetBuilder(items[index]),
 );
 } else {
 return widget.widgetBuilder(items[index]);
 }
 }
 );
 }
 Widget initiallyLoading() {
 return Center(
 child: CircularProgressIndicator(),
 );
 }
}

A full gist is here: https://gist.github.com/tombailey/988f788493cec9b95e7e9e007b8a7a0d

answered Jun 3, 2020 at 15:25

1 Comment

Thank you for your reply, I will try it.
0

screen_movie_listing.dart

import 'package:flutter/material.dart';
class ScreenMovieListing extends StatefulWidget {
 const ScreenMovieListing({Key? key}) : super(key: key);
 @override
 State<ScreenMovieListing> createState() => _ScreenMovieListingState();
}
class _ScreenMovieListingState extends State<ScreenMovieListing> {
 final _scrollController = ScrollController();
 bool isLoading = false;
 bool isLastPage = false;
 int currentPage = 0;
 int totalPage = 0;
 int totalItems = 0;
 var moviesList = [];
 @override
 void initState() {
 _restAPICall(true);
 super.initState();
 _scrollController.addListener(() {
 if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
 if (!isLastPage) {
 _restAPICall(false);
 }
 }
 });
 }
 @override
 void dispose() {
 _scrollController.dispose();
 super.dispose();
 }
 void _restAPICall(bool clearList) async {
 if (clearList) {
 currentPage = 1;
 moviesList.clear();
 }
 setState(() {
 isLoading = true;
 });
 await RestAPIHelper.movieList(
 context: context,
 currentPage: currentPage.toString(),
 ).then((response) {
 setState(() {
 isLoading = false;
 });
 if (response.status!) {
 setState(() {
 currentPage = response.pagignation!.currentPage!;
 totalPage = response.pagignation!.totalPages!;
 totalItems = response.pagignation!.totalItems!;
 if (currentPage == totalPage) {
 isLastPage = true;
 } else {
 currentPage = currentPage + 1;
 }
 moviesList.addAll(response.moviesListFromAPIResponse!);
 if (_scrollController.position.extentAfter <= 0 && isLoading == false) {
 // This code will call the pagination API if more data is available and screen had covered content
 if (!isLastPage) {
 _restAPICall(false);
 }
 }
 });
 }
 }).onError((error, stackTrace) {
 setState(() {
 isLoading = false;
 });
 });
 }
 Widget _loadMoreIndicator() {
 return !isLastPage
 ? const Padding(
 padding: EdgeInsets.symmetric(vertical: 10),
 child: Center(
 child: CircularProgressIndicator(),
 ),
 )
 : const SizedBox(height: 0);
 }
 @override
 Widget build(BuildContext context) {
 return ListView.separated(
 controller: _scrollController,
 shrinkWrap: true,
 itemCount: moviesList.length + 1,
 separatorBuilder: (_ctx, _index) {
 return SizedBox(height: 20);
 },
 itemBuilder: (_ctx, _index) {
 if (_index == moviesList.length) {
 return _loadMoreIndicator();
 } else {
 return ListItemMovie(data: moviesList[_index]);
 }
 },
 );
 }
}

rest_api_helper.dart

class RestAPIHelper {
 static Future<MoviesListModel> movieList(
 {BuildContext? context, String? currentPage = ''}) async {
 MoviesListModel? data;
 var bodyData = {
 'user_id': '1',
 'current_page': currentPage,
 'item_per_page': '1',
 };
 Map<String, String>? headerData = {
 'token': '1234567890',
 };
 try {
 final response = await http.post('api_url', headers: headerData, body: bodyData);
 var decodedResult = jsonDecode(response.body);
 data = MoviesListModel.fromJson(decodedResult);
 return data;
 } on SocketException {
 return null;
 } catch (error) {
 return null;
 }
 return data!;
 }
}

API Response

{
 "status": true,
 "message": "Movies Listing",
 "pagignation": {
 "current_page": 1,
 "total_pages": 7,
 "total_items": 7
 },
 "data": [
 {
 "id": "1",
 "name": "Movie Name",
 "image": "",
 "genre": "",
 "language": "Hindi",
 "release_year": "2022"
 }
 ]
}

This example is based on API which have request parameter such as current_page, item_per_page & have response parameter such as current_page, total_pages, total_items

answered Apr 2, 2022 at 7:42

Comments

0
I use ScrollController in Pagination ::
ScrollController? scrollController;
inside initState initialize it and pass the scrollListener function::
scrollController = ScrollController()..addListener(_scrollListener);
here is _scrollListener ::
- when extentAfter becomes 0 which means reached the bottom of last item you 
 call API again but you should also check if last page reached so that you 
 do not call API again when you reach last page , it is hashed inside the 
 condition
void _scrollListener() {
 debugPrint("extentAfter ::::: " +
 scrollController!.position.extentAfter.toString());
 if (scrollController!.position.extentAfter <= 0 /*&&
 viewModel.pageNumber < viewModel.totalCount + 1*/) {
 if (!viewModel.lazyLoading) {
 viewModel.lazyLoading = true; //show a loading indicator
 // call APi again and inside getNewProducts "${pageNumber++}"
 viewModel.getNewProducts(viewModel.catId ?? ""); 
 }
 }
 }
then you pass scrollController to the listView/GridView controller ::
controller: scrollController,
answered Jun 7, 2022 at 13:20

Comments

0

If I had to implement the pagination myself, I would either use infinite_scroll_pagination library or, if the project is small and I just want it in one place and don't want to add yet another dependency, I would just wrap my ListView with NotificationListener<ScrollNotification> like below:

NotificationListener<ScrollNotification>(
 onNotification: (scrollNotification) {
 if (scrollNotification is ScrollEndNotification) {
 onScrollListener();
 }
 return true;
}
void onScrollListener() {
 if (reachedEnd) {
 return;
 }
 if (!isPageLoading) {
 isPageLoading = true;
 Future.microtask(() async {
 final newItems = await getItemsUseCase.get(page: nextPage);
 if (newItems.length < pageSize) {
 reachedEnd= true;
 } else {
 nextPage++;
 }
 allItems.addAll(newItems);
 isPageLoading = false;
 });
 }
}

I've added a complete code example here.

answered Jan 31, 2024 at 7:30

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.