hello i am trying to use pagination in my list. and the list data is coming from firebase. i am not sure how to paginate data using stream builder. now i am doing the pagination by calling getdocuments in the didChangeDependencies() function. but after adding new data the list is not updated. if anyone can help then it will be great for me. here is what i am doing now..
didChangeDependencies() {
super.didChangeDependencies();
getProducts();
_scrollController.addListener(() {
double maxScroll = _scrollController.position.maxScrollExtent;
double currentScroll = _scrollController.position.pixels;
double delta = MediaQuery.of(context).size.height * 0.20;
if (maxScroll - currentScroll <= delta) {
getProducts();
}
});
}
getProducts() async {
if (!hasMore) {
return;
}
if (isLoading) {
return;
}
setState(() {
isLoading = true;
});
QuerySnapshot querySnapshot;
if (lastDocument == null) {
querySnapshot = await firestore
.collection('products')
.limit(documentLimit)
.orderBy('timestamp', descending: true)
.getDocuments();
} else {
querySnapshot = await firestore
.collection('products')
.startAfterDocument(lastDocument)
.limit(documentLimit)
.orderBy('timestamp', descending: true)
.getDocuments();
}
if (querySnapshot.documents.length < documentLimit) {
hasMore = false;
}
if (querySnapshot.documents.isNotEmpty) {
lastDocument =
querySnapshot.documents[querySnapshot.documents.length - 1];
products.addAll(querySnapshot.documents);
setState(() {
isLoading = false;
});
}
}
Take a try with below code:
class ProductList extends StatefulWidget {
@override
_ProductListState createState() => _ProductListState();
}
class _ProductListState extends State<ProductList> {
StreamController<List<DocumentSnapshot>> _streamController =
StreamController<List<DocumentSnapshot>>();
List<DocumentSnapshot> _products = [];
bool _isRequesting = false;
bool _isFinish = false;
void onChangeData(List<DocumentChange> documentChanges) {
var isChange = false;
documentChanges.forEach((productChange) {
if (productChange.type == DocumentChangeType.removed) {
_products.removeWhere((product) {
return productChange.document.documentID == product.documentID;
});
isChange = true;
} else {
if (productChange.type == DocumentChangeType.modified) {
int indexWhere = _products.indexWhere((product) {
return productChange.document.documentID == product.documentID;
});
if (indexWhere >= 0) {
_products[indexWhere] = productChange.document;
}
isChange = true;
}
}
});
if(isChange) {
_streamController.add(_products);
}
}
@override
void initState() {
Firestore.instance
.collection('products')
.snapshots()
.listen((data) => onChangeData(data.documentChanges));
requestNextPage();
super.initState();
}
@override
void dispose() {
_streamController.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.maxScrollExtent == scrollInfo.metrics.pixels) {
requestNextPage();
}
return true;
},
child: StreamBuilder<List<DocumentSnapshot>>(
stream: _streamController.stream,
builder: (BuildContext context,
AsyncSnapshot<List<DocumentSnapshot>> snapshot) {
if (snapshot.hasError) return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return new Text('Loading...');
default:
log("Items: " + snapshot.data.length.toString());
return ListView.separated(
separatorBuilder: (context, index) => Divider(
color: Colors.black,
),
itemCount: snapshot.data.length,
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.symmetric(vertical: 32),
child: new ListTile(
title: new Text(snapshot.data[index]['name']),
subtitle: new Text(snapshot.data[index]['description']),
),
),
);
}
},
));
}
void requestNextPage() async {
if (!_isRequesting && !_isFinish) {
QuerySnapshot querySnapshot;
_isRequesting = true;
if (_products.isEmpty) {
querySnapshot = await Firestore.instance
.collection('products')
.orderBy('index')
.limit(5)
.getDocuments();
} else {
querySnapshot = await Firestore.instance
.collection('products')
.orderBy('index')
.startAfterDocument(_products[_products.length - 1])
.limit(5)
.getDocuments();
}
if (querySnapshot != null) {
int oldSize = _products.length;
_products.addAll(querySnapshot.documents);
int newSize = _products.length;
if (oldSize != newSize) {
_streamController.add(_products);
} else {
_isFinish = true;
}
}
_isRequesting = false;
}
}
}
JSON for product item:
{
"index": 1,
"name": "Pork",
"description": "Thịt heo/lợn"
}
Link Github example: https://github.com/simplesoft-duongdt3/flutter_firestore_paging
@sourav-das, please have look this code
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Firestore firestore = Firestore.instance;
List<DocumentSnapshot> products = [];
bool isLoading = false;
bool hasMore = true;
int documentLimit = 10;
DocumentSnapshot lastDocument;
ScrollController _scrollController = ScrollController();
StreamController<List<DocumentSnapshot>> _controller =
StreamController<List<DocumentSnapshot>>();
Stream<List<DocumentSnapshot>> get _streamController => _controller.stream;
@override
void initState() {
super.initState();
getProducts();
_scrollController.addListener(() {
double maxScroll = _scrollController.position.maxScrollExtent;
double currentScroll = _scrollController.position.pixels;
double delta = MediaQuery.of(context).size.height * 0.20;
if (maxScroll - currentScroll <= delta) {
getProducts();
}
});
}
getProducts() async {
if (!hasMore) {
print('No More Products');
return;
}
if (isLoading) {
return;
}
setState(() {
isLoading = true;
});
QuerySnapshot querySnapshot;
if (lastDocument == null) {
querySnapshot = await firestore
.collection('products')
.orderBy('name')
.limit(documentLimit)
.getDocuments();
} else {
querySnapshot = await firestore
.collection('products')
.orderBy('name')
.startAfterDocument(lastDocument)
.limit(documentLimit)
.getDocuments();
print(1);
}
if (querySnapshot.documents.length < documentLimit) {
hasMore = false;
}
lastDocument = querySnapshot.documents[querySnapshot.documents.length - 1];
products.addAll(querySnapshot.documents);
_controller.sink.add(products);
setState(() {
isLoading = false;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Pagination with Firestore'),
),
body: Column(children: [
Expanded(
child: StreamBuilder<List<DocumentSnapshot>>(
stream: _streamController,
builder: (sContext, snapshot) {
if (snapshot.hasData && snapshot.data.length > 0) {
return ListView.builder(
controller: _scrollController,
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(
contentPadding: EdgeInsets.all(5),
title: Text(snapshot.data[index].data['name']),
subtitle: Text(snapshot.data[index].data['short_desc']),
);
},
);
} else {
return Center(
child: Text('No Data...'),
);
}
},
),
),
isLoading
? Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.all(5),
color: Colors.yellowAccent,
child: Text(
'Loading',
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
)
: Container()
]),
);
}
}
I did something similar with chats instead of products.
See if this code is helpful:
class _MessagesState extends State<Messages> {
ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.offset >=
(_scrollController.position.maxScrollExtent) &&
!_scrollController.position.outOfRange) {
_getChats();
}
});
}
final StreamController<List<DocumentSnapshot>> _chatController =
StreamController<List<DocumentSnapshot>>.broadcast();
List<List<DocumentSnapshot>> _allPagedResults = [<DocumentSnapshot>[]];
static const int chatLimit = 10;
DocumentSnapshot? _lastDocument;
bool _hasMoreData = true;
Stream<List<DocumentSnapshot>> listenToChatsRealTime() {
_getChats();
return _chatController.stream;
}
void _getChats() {
final CollectionReference _chatCollectionReference = FirebaseFirestore
.instance
.collection("ChatRoom")
.doc(widget.chatRoomId)
.collection("channel");
var pagechatQuery = _chatCollectionReference
.orderBy('createdAt', descending: true)
.limit(chatLimit);
if (_lastDocument != null) {
pagechatQuery = pagechatQuery.startAfterDocument(_lastDocument!);
}
if (!_hasMoreData) return;
var currentRequestIndex = _allPagedResults.length;
pagechatQuery.snapshots().listen(
(snapshot) {
if (snapshot.docs.isNotEmpty) {
var generalChats = snapshot.docs.toList();
var pageExists = currentRequestIndex < _allPagedResults.length;
if (pageExists) {
_allPagedResults[currentRequestIndex] = generalChats;
} else {
_allPagedResults.add(generalChats);
}
var allChats = _allPagedResults.fold<List<DocumentSnapshot>>(
<DocumentSnapshot>[],
(initialValue, pageItems) => initialValue..addAll(pageItems));
_chatController.add(allChats);
if (currentRequestIndex == _allPagedResults.length - 1) {
_lastDocument = snapshot.docs.last;
}
_hasMoreData = generalChats.length == chatLimit;
}
},
);
}
@override
Widget build(BuildContext context) {
return Container(
child: StreamBuilder<List<DocumentSnapshot>>(
stream: listenToChatsRealTime(),
builder: (ctx, chatSnapshot) {
if (chatSnapshot.connectionState == ConnectionState.waiting ||
chatSnapshot.connectionState == ConnectionState.none) {
return chatSnapshot.hasData
? Center(
child: CircularProgressIndicator(),
)
: Center(
child: Text("Start a Conversation."),
);
} else {
if (chatSnapshot.hasData) {
final chatDocs = chatSnapshot.data!;
final user = Provider.of<User?>(context);
return ListView.builder(
controller: _scrollController,
reverse: true,
itemBuilder: (ctx, i) {
Map chatData = chatDocs[i].data() as Map;
return MessageBubble(
username: chatData['username'],
message: chatData['text'],
isMe: chatData['senderId'] == user!.uid,
key: ValueKey(chatDocs[i].id));
},
itemCount: chatDocs.length,
);
} else {
return CircularProgressIndicator();
}
}
}),
);
}
}
I referred to this answer: Pagination in Flutter with Firebase Realtime Database
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