I am new to Flutter/Dart. I am trying to add pagination to my list. I get my Data through RestAPI. All the example I have been seeing online on pagination does not use Future Builder. Here is my code:
I have been able to detect when the scroll gets to the bottom of the page but I am finding it difficult to load more transactions to the old lists.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:xsocial/utils/my_drawer.dart';
import 'package:xsocial/utils/transaction_history_tile.dart';
import 'package:http/http.dart' as http;
class TransactionHistory extends StatefulWidget {
const TransactionHistory({super.key});
@override
State<TransactionHistory> createState() => _TransactionHistoryState();
}
class _TransactionHistoryState extends State<TransactionHistory> {
final _controller = ScrollController();
int page = 1;
Future<List> getListData() async{
final res = await http.get(Uri.parse('http://194.143.45.195/myappi/cash-transaction-history'));
var resBody = jsonDecode(res.body);
return resBody;
}
@override
void initState() {
super.initState();
//Setup the listener.
_controller.addListener(() {
if (_controller.position.atEdge) {
bool isTop = _controller.position.pixels == 0;
if (isTop) {
print('At the top');
} else {
print('At the bottom');
//Load more data
page = page + 1;
getData(int page);
}
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Transaction History',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
drawer: MyDrawer(),
body: Column(
mainAxisSize: MainAxisSize.max,
children: [
//Search bar
SizedBox(height: 15.0),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15.0),
child: Container(
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(7),
),
child: TextField(
decoration: InputDecoration(
prefixIcon: Icon(Icons.search),
border: InputBorder.none,
hintText: 'Search transactions by ID',
),
),
),
),
SizedBox(height: 15.0),
FutureBuilder<List>(
//future: getListData(),
future: getListData(),
builder: (ctx, snapshot) {
// Checking if future is resolved or not
if (snapshot.connectionState == ConnectionState.done) {
// If we got an error
if (snapshot.hasError) {
return Center(
child: Text(
'${snapshot.error} occurred',
style: TextStyle(fontSize: 18),
),
);
// if we got our data
} else if (snapshot.hasData) {
// Extracting data from snapshot object
final data = snapshot.data as List;
return TransactionHistoryTile(list: data, scrollController: _controller);
}
}
// Displaying LoadingSpinner to indicate waiting state
return Center(
child: CircularProgressIndicator(),
);
}
),
],
),
);
}
}
2
Answers
Use
StreamBuilder
rather thenFutureBuilder
the reason being you add new items in a stream and it will not affect the existing items in list and only add new items but it you useFutureBuilder
then everytime you add new items in list a new future need to call to reflect changes and that will cause the wholeFutureBuilder
to rebuild and it will show loader again. So it is better to useStreamBuilder
withStreamController
orBehaviorSubject
to implement pagination in listview or you can useChangeNotifier
but in that case you need to handle intial loading, error everything manually. So check out this article that usesStreamBuilder
to implement pagination.Don’t call your API with a FutureBuilder inside your build function because it will call your endpoint only once. (BTW your loading function doesn’t have a page parameter)
Also please add types to all your variables
Dart is a strongly typed language for a good reason.
Keep the List of your items as a property of your State, so that it will stay the same between rebuilds, and add your newly received items to that list inside a
setState()
call to make the UI update with the latest content of that list.You don’t even need a ScrollController for this if you let your
ListView.builder
call agetItemAtIndex(int index)
function like this (pseudo code)Then wrap your
ListView.builder
inside aValueListenableBuilder that observes the
itemCountproperty. Use
itemCount.valueas
itemCount:of your
ListView.builder`By doing this, every time you get to the end of the items you already have in your list, the next page will be loaded, and
itemCount
will get updated which will then trigger a rebuild of your ListView.By using a
ValueNotifier
and aValueNotifier
builder you don’t need to callsetState
and you could move this Logic easily into a separate data layer and access it with a state management solution like watch_it or riverpod.