I'm new at flutter and I have been searching for proper result of pagination for 2 days.
I followed this code Flutter ListView lazy loading But didn't achieve what I want. Look at below code.
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:convert';
main() => runApp(InitialSetupPage());
class InitialSetupPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "API Pagination",
debugShowCheckedModeBanner: false,
theme: ThemeData(primarySwatch: Colors.green),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int pageNum = 1;
bool isPageLoading = false;
List<Map<String, dynamic>> arrayOfProducts;
Future<List<Map<String, dynamic>>> _callAPIToGetListOfData() async {
isPageLoading = true;
final responseDic =
await http.post(urlStr, headers: accessToken, body: paramDic);
Map<String, dynamic> dicOfRes = json.decode(responseDic.body);
List<Map<String, dynamic>> temArr = List<Map<String, dynamic>>.from(dicOfRes["data"]["products"]);
if (pageNum == 1) {
arrayOfProducts = temArr;
} else {
arrayOfProducts.addAll(temArr);
}
return arrayOfProducts;
}
ScrollController controller;
@override
void initState() {
controller = new ScrollController()..addListener(_scrollListener);
super.initState();
}
_scrollListener() {
print(controller.position.extentAfter);
if (controller.position.extentAfter <= 0 && isPageLoading == false) {
_callAPIToGetListOfData().then((data){
setState(() {
});
});
}
}
@override
void dispose() {
controller.removeListener(_scrollListener);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Online Products"),
centerTitle: true,
),
body: Container(
child: FutureBuilder<List<Map<String, dynamic>>>(
future: _callAPIToGetListOfData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.active:
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
case ConnectionState.done:
if (snapshot.hasError) {
Text(
'YOu have some error : ${snapshot.hasError.toString()}');
} else if (snapshot.data != null) {
isPageLoading = false;
pageNum++;
print(arrayOfProducts);
return Scrollbar(
child: ListView.builder(
itemCount: arrayOfProducts.length,
controller: controller,
physics: AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) {
return ListTile(
title: Text(
'$index ${arrayOfProducts[index]['title']}'),
);
}),
);
}
}
}),
));
}
}
So, When I reach at bottom of the page my _scrollListener
method get call and there I have set setState(()....
method to reload widget. Issue is I load my actual position and It starts with top of the list. So where I'm going wrong? Actually I want like. https://github.com/istyle-inc/LoadMoreTableViewController/blob/master/screen.gif
Final code: (Guide by @Rémi Rousselet)
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:convert';
main() => runApp(InitialSetupPage());
class InitialSetupPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "API Pagination",
debugShowCheckedModeBanner: false,
theme: ThemeData(primarySwatch: Colors.green),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int pageNum = 1;
bool isPageLoading = false;
List<Map<String, dynamic>> arrayOfProducts;
ScrollController controller;
Future<List<Map<String, dynamic>>> future;
int totalRecord = 0;
@override
void initState() {
controller = new ScrollController()..addListener(_scrollListener);
future = _callAPIToGetListOfData();
super.initState();
}
@override
void dispose() {
controller.removeListener(_scrollListener);
super.dispose();
}
@override
Widget build(BuildContext context) {
final constListView = ListView.builder(
itemCount: arrayOfProducts == null ? 0 : arrayOfProducts.length,
controller: controller,
physics: AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) {
return Column(
children: <Widget>[
ListTile(
title: Text('$index ${arrayOfProducts[index]['title']}'),
leading: CircleAvatar(backgroundImage: NetworkImage(arrayOfProducts[index]['thumbnail'] ?? "")),
),
Container(
color: Colors.black12,
height: (index == arrayOfProducts.length-1 && totalRecord > arrayOfProducts.length) ? 50 : 0,
width: MediaQuery.of(context).size.width,
child:Center(
child: CircularProgressIndicator()
),
)
],
);
});
return Scaffold(
appBar: AppBar(
title: Text("Online Products"),
centerTitle: true,
),
body: Container(
child: FutureBuilder<List<Map<String, dynamic>>>(
future: future,
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.active:
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
case ConnectionState.done:
if (snapshot.hasError) {
Text(
'YOu have some error : ${snapshot.hasError.toString()}');
} else if (snapshot.data != null) {
isPageLoading = false;
print(arrayOfProducts);
return constListView;
}
}
}),
));
}
Future<List<Map<String, dynamic>>> _callAPIToGetListOfData() async {
isPageLoading = true;
final responseDic =
await http.post(urlStr, headers: accessToken, body: paramDic);
Map<String, dynamic> dicOfRes = json.decode(responseDic.body);
List<Map<String, dynamic>> temArr =
List<Map<String, dynamic>>.from(dicOfRes["data"]["products"]);
setState(() {
if (pageNum == 1) {
totalRecord = dicOfRes["total_record"];
print('============>>>>>>> $totalRecord');
arrayOfProducts = temArr;
} else {
arrayOfProducts.addAll(temArr);
}
pageNum++;
});
return arrayOfProducts;
}
_scrollListener() {
if (totalRecord == arrayOfProducts.length) {
return;
}
print(controller.position.extentAfter);
if (controller.position.extentAfter <= 0 && isPageLoading == false) {
_callAPIToGetListOfData();
}
}
}
Thats working but Is it right/good way? little bit confused because if I reach at end of the page and scroll up/down it seems little bit sticky while scrolling..
Add this to your listener of controller
if (scrollController.position.maxScrollExtent == scrollController.offset) {
/***
* we need to get new data on page scroll end but if the last
* time when data is returned, its count should be Constants.itemsCount' (10)
*
* So we calculate every time
*
* productList.length >= (Constants.itemsCount*pageNumber)
*
* list must contain the products == Constants.itemsCount if the page number is 1
* but if page number is increased then we need to calculate the total
* number of products we have received till now example:
* first time on page scroll if last count of productList is Constants.itemsCount
* then increase the page number and get new data.
* Now page number is 2, now we have to check the count of the productList
* if it is==Constants.itemsCount*pageNumber (20 in current case) then we have
* to get data again, if not then we assume, server has not more data then
* we currently have.
*
*/
if (productList.length >= (Constants.itemsCount * pageNumber) &&
!isLoading) {
pageNumber++;
print("PAGE NUMBER $pageNumber");
print("getting data");
getProducts(false); // Hit API to get new data
}
}
https://pub.dev/packages/pagination_view is the package I found the more intuitive to work with that needed the least changes from a page view and felt the more intuitive:
It's easy to move from a listview to a PaginationView, the class that holds the widget needs to be stateful or it won't work:
PaginationView<User>(
preloadedItems: <User>[
User(faker.person.name(), faker.internet.email()),
User(faker.person.name(), faker.internet.email()),
],
itemBuilder: (BuildContext context, User user, int index) => ListTile(
title: Text(user.name),
subtitle: Text(user.email),
leading: IconButton(
icon: Icon(Icons.person),
onPressed: () => null,
),
),
paginationViewType: PaginationViewType.listView // optional
pageFetch: pageFetch,
onError: (dynamic error) => Center(
child: Text('Some error occured'),
),
onEmpty: Center(
child: Text('Sorry! This is empty'),
),
bottomLoader: Center( // optional
child: CircularProgressIndicator(),
),
initialLoader: Center( // optional
child: CircularProgressIndicator(),
),
),
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With