how can I add lazy loading to this list? - flutter

This is the how I fetch the posts in postList from firebase firestore, I need a function that works to get more posts on scroll. This next set of posts have to start after the last post that is displayed in this initial list and add to this list as the user scrolls as long as there are posts in the firestore.
class _FeedScreenState extends State<FeedScreen> {
List<Post> _posts = [];
ScrollController _controller = ScrollController();
#override
void initState() {
super.initState();
_setupFeed();
_controller.addListener(_scrollListener);
}
_scrollListener() {
setState(() {
if (_controller.position.atEdge) {
if (_controller.position.pixels == 0) {
} else {
_getMore();
}
}
});
}
_setupFeed() async {
List<Post> posts = await DatabaseService.getFeedPosts(widget.currentUserId);
setState(() {
_posts = posts;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
title: Text(
'New List',
style: TextStyle(
color: Colors.black,
fontSize: 35.0,
),
),
),
body: RefreshIndicator(
onRefresh: () => _setupFeed(),
child: ListView.builder(
controller: _controller,
itemCount: _posts.length,
itemBuilder: (BuildContext context, int index) {
Post post = _posts[index];
return FutureBuilder(
future: DatabaseService.getUserWithId(post.authorId),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return SizedBox.shrink();
}
User author = snapshot.data;
return PostView(
currentUserId: widget.currentUserId,
post: post,
author: author,
);
},
);
},
),
),
);
}
}
this is how i fetch the list of posts
static Future<List<Post>> getFeedPosts(String userId) async {
QuerySnapshot feedSnapshot = await feedsRef
.document(userId)
.collection('userFeed')
.orderBy('timestamp', descending: true)
.limit(30)
.getDocuments();
List<Post> posts =
feedSnapshot.documents.map((doc) => Post.fromDoc(doc)).toList();
return posts;
}

na2axl answer was almost right. I will add here an explanation and example of how to use startAfter()
If you check the documentation on pagination, you will see that you need to use startAfter() referencing whatever filter you used. In your case you are ordering using timestamp so your next query should look like this:
static Future<List<Post>> getNextFeedPosts(String userId, TimeStamp timestamp) async {
QuerySnapshot feedSnapshot = await feedsRef
.document(userId)
.collection('userFeed')
.orderBy('timestamp', descending: true)
//Here you need to let Firebase know which is the last document you fetched
//using its timesTamp
.startAfter(timestamp)
.limit(30)
.getDocuments();
List<Post> posts =
feedSnapshot.documents.map((doc) => Post.fromDoc(doc)).toList();
return posts;
}
This means that your next query will still be ordered by a timestamp but the first document retrieved will be after the timestamp on startAfter
I hope this helps, however, you can check the documentation as there are other examples!

I think doing this will solve your issue:
You have to edit your getFeedPosts to collect your posts starting at a given index:
I'm not familiar to FireStore, I've found the startAt() method on docs
EDIT: I've misunderstood a Firestore concept, so I've change startAt() to startAfter() following Francisco Javier Snchez advice
static Future<List<Post>> getFeedPosts(String userId, TimeStamp start) async {
QuerySnapshot feedSnapshot = await feedsRef
.document(userId)
.collection('userFeed')
.orderBy('timestamp', descending: true)
.startAfter(start)
.limit(30)
.getDocuments();
List<Post> posts =
feedSnapshot.documents.map((doc) => Post.fromDoc(doc)).toList();
return posts;
}
Now you can query it like this:
_getMore() async {
// You have to give the timestamp of the last post here
// Change this line by the right way...
List<Post> posts = await DatabaseService.getFeedPosts(widget.currentUserId, _posts[_posts.length - 1].timestamp);
setState(() {
// Do += instead of =, += will add fetched posts to the current list, = will overwrite the whole list
_posts += posts;
});
}
Hope this will help!

Related

StreamBuilder updating the stream only after hot restart

I am building an app where it collects all the orders and order details placed from Firebase. I have to get 2 things
Salon details from contactnumber which I saved using singleton method once the user logs in
Customer details from CustID
What happens right now is that during debugging I created this button, on pressing it fetches the salon details from database. But now, the details will only get fetched when I
Click the button first
Hot restart the app
Only then the streambuilder fetched the data
Here are my code snippets causing the problem :
Future<void> getSalonFromContact(String saloonContact) async {
await for (var docs in firestore.collection('Saloon').snapshots()) {
// final loop = snap.data!.docs;
for (var variable in docs.docs) {
if (variable.get(FieldPath(['Contact'])) == saloonContact) {
aadhar = variable.get(FieldPath(['Aadhar']));
getOrdersList(aadhar);
}
}
}
}
Future<void> getOrdersList(String aadhar) async {
ordersList.clear();
await for (var docs in firestore
.collection('orders')
.where('SalonID', isEqualTo: aadhar)
.snapshots()) {
for (var variable in docs.docs) {
if (variable.get('SalonID') == aadhar) {
ordersList.add(variable.data());
print('My orderlist is $ordersList');
} else {
continue;
}
}
}
}
Future<void> getCustomerDetails(String custID) async {
await for (var docs in firestore
.collection('Customers')
.where('Customer_Uid', isEqualTo: custID)
.snapshots()) {
// final loop = snap.data!.docs;
for (var variable in docs.docs) {
print(variable.data());
if (variable.get(FieldPath(['Customer_Uid'])) == custID) {
customerDetails.add(variable.data());
print('My customer details are ${customerDetails}');
}
}
}
}
#override
void didChangeDependencies() async {
await getSalonFromContact(contactNumber);
for (int i = 0; i < ordersList.length; i++) {
await getCustomerDetails(ordersList[i]['CustomerID']);
}
// TODO: implement didChangeDependencies
super.didChangeDependencies();
}
These codes are for finding out the details.
And this is my StreamBuilder code :
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('orders')
.where('SalonID', isEqualTo: aadhar)
.snapshots(),
builder: (context, snapshot) {
didChangeDependencies();
if (snapshot.connectionState ==
ConnectionState.waiting) {
return Text('Loading...');
} else {
List<AppointmentCard> listitems = [];
return ListView(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
children: snapshot.data!.docs
.asMap()
.map((index, DocumentSnapshot document) {
getCustomerDetails(document['CustomerID']);
return MapEntry(
index,
AppointmentCard(
isCompleted: document['Status'],
name: customerDetails[index]['Name'],
contact: customerDetails[index]
['Contact'],
services: Flexible(
child: ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(8),
itemCount:
document['Requested_Service']
.length,
itemBuilder: (BuildContext context,
int index) {
return Text(
document['Requested_Service']
[index]['name']);
}),
// child: Text(
// // "Text",
// " ${ordersList[i]['Requested_Service']} ",
//// .join(' '),
//
// softWrap: true,
// ),
),
),
);
})
.values
.toList(),
);
}
}),
Any idea what is going wrong and how I can fetch the data without the button and hot restart?
You use getCustomerDetails(document['CustomerID']); in before MapEntry and it is an asynchronous function. It will return probably after the MapEntry is built. You have to await getCustomerDetails function before put your variables which is updating in getCustomerDetails function.

How to order Firestore documents in list form based on int values in flutter

I currently have a list of documents that each contain an int value called plastics. Currently, the list only displays the documents in order by when it was added to the collection, but I want to be able to order the documents based on the int value within each one. I've looked around on the web and I've only found tutorials mostly on ordering timestamps. Is there any documentation or sources on this matter? Here is the code situation I'm working with:
Firstly, in my app users can join groups, and when they do so they bring along their name and int data which is then stored in documents for each user.
Future<String> joinGroup(String groupId, String userUid, String displayName,
String plastics) async {
String retVal = 'error';
List<String> members = List();
try {
members.add(displayName);
await _firestore.collection('Groups').doc(groupId).update({
'members': FieldValue.arrayUnion(members),
});
final uid = FirebaseAuth.instance.currentUser.uid;
await _firestore.collection('UserNames').doc(uid).update({
'groupId': groupId,
});
//Below me is the code for doing so
await _firestore
.collection("Groups")
.doc(groupId)
.collection("Members")
.doc(userUid)
.set({'displayName': displayName, 'plastics': plastics});
retVal = 'success';
} catch (e) {}
return retVal;
}
I then take that code access the documents and put them in a list.
#override
Widget build(BuildContext context) {
final CollectionReference users = firestore.collection('UserNames');
final String uid = auth.currentUser.uid;
return FutureBuilder(
future: users.doc(uid).get(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final result = snapshot.data;
final groupId = result.data()['groupId'];
return FutureBuilder<QuerySnapshot>(
// <2> Pass `Future<QuerySnapshot>` to future
future: FirebaseFirestore.instance
.collection('Groups')
.doc(groupId)
.collection('Members')
.get(),
builder: (context, snapshot) {
if (snapshot.hasData) {
// <3> Retrieve `List<DocumentSnapshot>` from snapshot
final List<DocumentSnapshot> documents = snapshot.data.docs;
return ListView(
children: documents
.map((doc) => Card(
child: ListTile(
title: Text(doc['displayName']),
subtitle: Text(doc['plastics'].toString()),
),
))
.toList());
} else if (snapshot.hasError) {
return Text('Its Error!');
}
});
}
});
}
Is there a specific function needed so that the documents in the Member collection are listed based on the numerical value of the plastics?
You can use orderBy to sort your results.
FirebaseFirestore.instance
.collection('Groups')
.doc(groupId)
.collection('Members')
.orderBy('plastics', descending: true)
.get()

Flutter How to apply search filter in a querysnapshot used in Listview builder

This is my firebase data request code
and this is my future builder based on the qn.docs
How to search within the snapshot and ListView.builder to use the filtered set.
previously I was using a Local List and used to search as on Itemchanged
thanks in advance for your guidance.
I am new to flutter. so please explain with an example
Future <QuerySnapshot> getSpeakernames() async {
QuerySnapshot qn = await
FirebaseFirestore.instance.collection('speakernames').orderBy('speakername').get();
return qn;
}
Center(
child: Container(
child: ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (BuildContext context, int index) {
DocumentSnapshot data = snapshot.data.docs[index];
return ListTile(title: Text(data.get("speakername")),
subtitle: Text(data.get("speakerdegree")), )
onItemChanged(String value) {
setState(() {
data.get("speakername").
newspeakernames = speakernames
.where((string) =>
string.toLowerCase().contains(value.toLowerCase()))
.toList();
});
}
you can use ".Where" property of Firestore
like below
Future <QuerySnapshot> getSpeakernames() async {
QuerySnapshot qn = await FirebaseFirestore.instance.collection('speakernames')
.where('speakername' , isEqualTo : SubramanianV).orderBy('speakername').get();
return qn;
}
You can use isGreaterThanOrEqualTo.
Example:
class doctorName {
getDoctorByName(String doctorName, String last) async {
return await Firestore.instance
.collection("Doctor")
.where("name", isGreaterThanOrEqualTo: doctorName)
.where("name", isLessThan: last)
//.where("name", isEqualTo: doctorName)
.getDocuments();
}
}

How to NOT show the current user in a Grid View?

I have a function called getAllUsers() that returns all users from a database. The problem is that I want GridView.builder() to display all the users except the current user, but despite all the research I did, nothing seems to work out.
If i use the if condition like if(snapshot.data.documents[i].data["username"] != currentUserId within itemBuilder:, it returns a blank tile which represents the current user which creates a gap within the grid view. Thus, it makes the grid view look really bad.
I believe this problem could have been solved if I knew how to include the inequality query in the getAllUsers() method. But my understanding is that Firestore has yet to provide this function/argument.
HomeFragment class
Database _database = Database();
Stream _stream;
String currentUserId;
#override
void initState() {
getCurrentUserId();
getAllUsers();
super.initState();
}
getAllUsers() async {
return await _database.getAllUsers().then((val) {
if (mounted)
setState(() => _stream = val);
});
}
getCurrentUserId() async {
FirebaseUser currentUser = await FirebaseAuth.instance.currentUser();
currentUserId = currentUser.uid;
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _stream,
builder: (context, snapshot) {
return snapshot.data == null ? Center(child: CircularProgressIndicator())
: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child:
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8.0,
mainAxisSpacing: 8.0,
),
itemCount: snapshot.data.documents.length,
itemBuilder: (context, i) {
return Container(
child: Text(snapshot.data.documents[i].data["username"])
);
}
// etc etc..
Database class
getAllUsers() async {
return await _firestore.collection("users").snapshots();
}
I tried to use this, but _stream2 returns null
Stream _stream, _stream2;
getAllUsers() async {
return await _database.getAllUsers().then((val) {
if (mounted) {
List<String> list;
setState(() {
_stream = val;
_stream2 = _stream.where((snapshot) {
_querySnapshot = snapshot;
for (int i = 0; i < _querySnapshot.documents.length; i++)
list.add(_querySnapshot.documents[i].data["userId"]);
return list.contains(currentUserId) == false;
});
});
}
});
}
I also tried this, it is not working
getAllUsers() async {
Stream<QuerySnapshot> snapshots = await _database.getAllUsers();
_stream = snapshots.map((snapshot) {
snapshot.documents.where((documentSnapshot) {
return documentSnapshot.data["userId"] != currentUserId;
});
});
}
Maybe you can try something like this. You filter the query result:
getAllUsers() async {
final Stream<QuerySnapshot> snapshots = await _firestore.collection("users").snapshots();
return snapshots.map((snapshot) {
final result = snapshot.documents
.map((snapshot) => User.fromMap(snapshot.data)
.where((user) => user.id != currentUser.id)
.toList();
return result;
}
}
If you do not have an User class, you can replace some lines with this. But the result will be a list of Map<String, dynamic> instead of a list of User objects.
return snapshots.map((snapshot) {
final result = snapshot.documents
.map((snapshot) => snapshot.data
.where((user) => user['id'] != currentUser.id)
.toList();
return result;
This solution worked well for me.
firestore.collection('your collection').where('x', isNotEqualTo: auth.currentUser!.uid).snapshots();

Flutter retrieve data from firestore as a list

I have been struggling for hours now to retrieve data from firestore as a list so I can show them in a search bar suggestion.
This below function will retrieve data from firestore and return some selected fields as a list.
Future<List> getNewsOnSearchBar() async {
final String _collection = 'news';
final Firestore _fireStore = Firestore.instance;
var newsList = [];
print("1");
Future<QuerySnapshot> getData() async {
print("2");
return await _fireStore.collection(_collection).getDocuments();
}
QuerySnapshot val = await getData();
if (val.documents.length > 0) {
print("3");
for (int i = 0; i < val.documents.length; i++) {
newsList.add(val.documents[i].data["headline"]);
}
} else {
print("Not Found");
}
print("4");
return newsList;
}
And below is my Search bar widget. It has an attribute searchList which is of type List<dynamic>. It accept values such as:
var list = ["a", "b", "c"];
searchList: list
So I want to call that above function getNewsOnSearchBar() and set the list to the attribute searchList. I tried below and it doesn't work.
Widget _showSearchBar(BuildContext context) {
return FutureBuilder(
future: getNewsOnSearchBar(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError || !snapshot.hasData) {
return _progressIndicator();
} else {
return GFSearchBar(
searchList: [], //how can I assign the list return from `getNewsOnSearchBar()` here?
searchQueryBuilder: (query, list) {
return list
.where((item) =>
item.toLowerCase().contains(query.toLowerCase()))
.toList();
},
overlaySearchListItemBuilder: (item) {
return Container(
padding: const EdgeInsets.all(3),
child: Text(
item,
style: const TextStyle(fontSize: 18),
),
);
},
onItemSelected: (item) {},
);
}
});
}
Could you help me, Please?
Since your function getNewsOnSearchBar() is returning a list, you can use snapshot.data.
so your function becomes something like this
return GFSearchBar(
searchList: snapshot.data,
searchQueryBuilder: (query, list) {
return list
.where((item) =>
item.toLowerCase().contains(query.toLowerCase()))
.toList();
},
You can do it in two ways.
1-you can retrieve the documents for firebase and then you can use the Map function to create a list.
2-You can create a Firebase Functions to retrieve the information as you expect.