How to order Firestore documents in list form based on int values in flutter - 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()

Related

StreamBuilder doesn't updates UI when Firestore data changes

My goal:
I want to retrieve a list of documents from the Firebase Firestore using a Stream to update the interface in real time when the Firestore data changes.
The problem:
I am able to download the data from Firestore with the structure I need, but when I make changes in firestore the interface does not update in real time. When I reload the page, it updates, but that is not the behavior I need.
This is the Stream I have created:
Stream<DayModel> getInstances(String selectedDay, String userUid) async* {
DayModel retval = DayModel();
List<InstanceModel> instances = [];
int index = 0;
try {
final QuerySnapshot<Map<String, dynamic>> querySnapshot =
await FirebaseFirestore.instance
.collection('instances')
.doc(selectedDay)
.collection('instancesUid')
.where("instanceUsersUid", arrayContains: userUid)
.get();
instances = querySnapshot.docs
.map((instance) => InstanceModel.fromSnapshot(instance))
.toList();
for (InstanceModel instance in instances) {
final DocumentSnapshot<Map<String, dynamic>> instanceQuery =
await FirebaseFirestore.instance
.collection('instances')
.doc(selectedDay)
.collection('instancesUid')
.doc(instance.uid)
.get();
instance = InstanceModel.fromMap(instanceQuery);
instances[index] = instance;
index++;
}
retval.instances = instances;
yield retval;
} on Exception catch (e) {
print(e);
}
}
StreamBuilder code:
body: StreamBuilder<DayModel>(
stream:
OurDatabase().getInstances(selectedDay, _currentUser!.uid!),
builder:
(BuildContext context, AsyncSnapshot<DayModel> snapshot) {
if (snapshot.hasError) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
return Center(
child: snapshot.data!.instances!.isNotEmpty
? Text(snapshot.data!.instances![0].uid!)
: Text('No tienes instancias!'),
);
})
Maybe it's because I'm not returning the Stream with a QuerySnapshot?
I have read in other similar posts that it could be a problem with the keys, but I have tried several different combinations and it has not worked.
Do you have any idea what could be happening?
Thank you for your time.

type 'Future<Null>' is not a subtype of type 'String' in type cast in flutter and Firebase

I am trying to query list by mentioned location in user document.
1. All_Data is a main Collection in Firebase.
2. User_In is a SubCollection inside a document in a main collection in Firebase.
3. Area is a Field in a Document which stores user Area in String form.
final user_loc = FirebaseFirestore.instance
.collection('All_Data')
.doc(user.uid)
.collection('User_In')
.doc(user.uid)
.get()
.then((value) async {
print(await value.get('Area'));
}) as String;
final Stream<QuerySnapshot> datastream = FirebaseFirestore.instance
.collection('All_Data')
.where("private", isEqualTo: false)
.where("Area", isEqualTo: user_loc)
.snapshots();
Here is What I want...
Every User has their own specific area mentioned in subcollection('User_In').doc(user.uid), I want Streambuilder to show only those documents from the main collection that contains Area is equal to the Area in subcollection('User_In').doc(user.uid).
Here is What I tried...
I have Mentioned the code above, I am trying to insert values to query them in the final stream datastream
Here is What I get...
Error I am Getting is "type 'Future' is not a subtype of type 'String' in type cast".
I am new to it please help me with what needs to be done, Or is it just fundamentally not possible with Firebase.
You can get it like
() async {
final value = await FirebaseFirestore.instance
.collection('All_Data')
.doc(user.uid)
.collection('User_In')
.doc(user.uid)
.get();
final user_loc = value.get('Area') as String?;
}
Edit:
class _FaState extends State<Fa> {
Future<String?> fetchUserloc() async {
final value = await FirebaseFirestore.instance
.collection('All_Data')
.doc(user.uid)
.collection('User_In')
.doc(user.uid)
.get();
final user_loc = value.get('Area') as String?;
return user_loc;
}
Future<Stream<QuerySnapshot>> getMyStream() async {
final user_loc = await fetchUserloc();
return FirebaseFirestore.instance
.collection('All_Data')
.where("private", isEqualTo: false)
.where("Area", isEqualTo: user_loc)
.snapshots();
}
late final userStreamFuture = getMyStream();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
FutureBuilder<Stream<QuerySnapshot>>(
future: userStreamFuture,
builder: (context, snapshot) {
if (snapshot.hasData) {
// you will get the steam here
}
return CircularProgressIndicator();
},
)
],
),
);
}
}

How to convert a Future String to an image URL in Flutter?

I can't get the image URL from Firestore, it's always returning Instance of Future String.
Here is my code:
class Animal_Data_Stream extends StatelessWidget {
User? user = FirebaseAuth.instance.currentUser;
UserModel loggedInUser = UserModel();
var url;
Future<String> getImg(String s) async {
final ref =
FirebaseStorage.instance.ref('animal_image/').child(s);
String abc = "abc";
try{
abc = await ref.getDownloadURL();
print(abc);
return abc;
}
catch(e){
print(e);
return "null";
}
}
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection("users")
.doc("LQ0PFtnsaxXU1c4tY0ZM")
.collection("Visitor")
.doc(user!.uid)
.collection("animals")
.snapshots(),
builder:
(context, AsyncSnapshot<QuerySnapshot> streamSnapshot){
List <Widget> Data = [];
var image_2;
final animal_data = streamSnapshot.data?.docs;
return
animal_data?.length !=0?
Column(
children: [
for( var data in animal_data!) kCard(context , getImg(data["animal_image"]) , data["animal_name"],
data["animal_kingdom"] , data["animal_class"] , getImg(data["animal_image"]).toString() ,data["animal_species"] )
]
):
Column();
});
}
}
);
}
I tried many times but couldn't get the desired result. What can I try next?
You have to think about data structure that you are providing to your UI part because for now, you have to make additional async operations to retrieve it.
But for your current example is only on the choice to use FutureBuilder inside of StreamBuilder like in example below:
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection("users")
.doc("LQ0PFtnsaxXU1c4tY0ZM")
.collection("Visitor")
.doc(user!.uid)
.collection("animals")
.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
List<Widget> Data = [];
var image_2;
final animal_data = streamSnapshot.data?.docs;
return animal_data?.length != 0
? Column(children: [
for (var data in animal_data!)
FutureBuilder<String>(
future: getImg(data["animal_image"]),
builder: (_, imageSnapshot) {
final imageUrl = imageSnapshot.data;
return imageUrl != null
? kCard(
context,
imageUrl,
data["animal_name"],
data["animal_kingdom"],
data["animal_class"],
imageUrl.toString(),
data["animal_species"])
: const SizedBox.shrink();
})
])
: Column();
});
}

How to get data from Firestore at Flutter

I am new at Flutter and Firebase, I just want to fetch data from Firestore, for example here, I just want to fetch the value of "KullaniciAdi" from the "11#gmail.com" document, as simply as possible.
StreamBuilder<QuerySnapshot>(
stream: viewDetails(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
for (int i = 0; i < snapshot.data!.docs.length; i++) {
if (snapshot.data!.docs[i].id == "11#gmail.com") {
final DocumentSnapshot e = snapshot.data!.docs[i];
dersiniz = "${e["Dersiniz"]}";
return Center(child: Text(dersiniz),);
}
}
},
)
static Stream<QuerySnapshot> viewDetails() {
CollectionReference notesItemCollection = FirebaseFirestore.instance.collection("KullaniciAdi");
return notesItemCollection.snapshots();
}
used like this
I think the structure of the folders is confusing, in general the documents are IDs, like this:
Example:
Collection (users) - Document (sALkf983l3j5RGjsk82lfds) - Fields (name, email, gender)
In this case you could make a call like this:
final FirebaseFirestore firestore = FirebaseFirestore.instance;
final DocumentSnapshot docUser =
await firestore.collection('users').doc('sALkf983l3j5RGjsk82lfds').get();
You can do it:
final FirebaseFirestore firestore = FirebaseFirestore.instance;
final DocumentSnapshot docUser =
await firestore.collection('KullaniciAdi').doc('11#gmail.com').get();
Editing:
To fetch a specific field value:
final ref = await FirebaseFirestore.instance
.doc('users/$userId').get();
final value = ref.get('name');

how can I add lazy loading to this list?

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!