How do I fetch data from multiple documents in a Firebase collection - firebase

I'm getting the user's location then using that location to fetch documents containing that location then displaying the data within the documents on the page.
The structure of my Firebase Database is /posts/{userId}/userPosts/{postId}. I want it to search through all the posts (meaning through all the different postIds).
Future<void> initState() {
super.initState();
getListings();
}
getListings() async {
placemark = await getUserLocation();
getLocationListings(placemark);
}
getLocationListings(placemark) async {
setState(() {
isLoading = true;
});
QuerySnapshot snapshot = await FirebaseFirestore.instance.collection('posts')
// .doc(userId)
// .collection('userPosts')
.where('location', isGreaterThanOrEqualTo: placemark.locality)
.get();
setState(() {
posts = snapshot.docs.map((doc) => Post.fromDocument(doc)).toList();
isLoading = false;
});
// print(placemark.locality);
}
When I run the code as it is (.doc(userId) and .collection('userPosts') commented out), the list List<Post> posts = []; remains empty. However, when I uncomment them and provide a userId, I get all the documents containing placemark.locality. How can I go round this?

Related

I can't fetch data from two different collection consecutively in Firebase with Flutter

I was trying to fetch from two different collection but I got a weird situation. First, I want to fetch a userID from posts collection. Then with that userID, I want to fetch data from users collection.
So, when I fetch from only the posts collection, print command works perfectly fine and prints the userID.
But when I add the users fetch statement that I showed in the code below it doesn't fetch it and shows an empty string (''), and users collection sends an error because I couldn't search the userID. What am I missing here?
class _ProductDetail extends State<ProductDetail> {
String getTitle = '';
String getLocation = '';
String getPrice = '';
String getImage = '';
String getUniversity = '';
String getProfileImage = '';
String getUserName = '';
String getSellerUserID = '';
#override
Widget build(BuildContext context) {
FirebaseFirestore.instance
.collection('posts')
.doc(widget.postID)
.get()
.then((incomingData) {
setState(() {
getTitle = incomingData.data()!['title'];
getPrice = incomingData.data()!['price'];
getImage = incomingData.data()!['postImage'];
});
});
FirebaseFirestore.instance
.collection('posts')
.doc(widget.postID)
.get()
.then((incomingData) {
setState(() {
getSellerUserID = incomingData.data()!['userID'];
});
});
print(getSellerUserID); //statement that will print the userID
//////////////////////IF I DELETE THIS SECTION, IT PRINTS THE USER ID//////////////////
FirebaseFirestore.instance
.collection('users')
.doc(getSellerUserID)
.get()
.then((incomingData) {
setState(() {
getUserName = incomingData.data()!['username'];
getProfileImage = incomingData.data()!['profileImage'];
getUniversity = incomingData.data()!['university'];
getLocation = incomingData.data()!['location'];
});
});
///////////////////////////////////////////////////////////////////////////////////////////////
return Scaffold(
....... rest of the code
Since data is loaded from Firestore asynchronously, the code inside your then blocks is called (way) later then the line after the call to get().
To see this most easily, add some logging like this:
print("Before calling Firestore")
FirebaseFirestore.instance
.collection('posts')
.doc(widget.postID)
.get()
.then((incomingData) {
print("Got data")
});
print("After calling Firestore")
If you run this code, it'll print:
Before calling Firestore
After calling Firestore
Got data
This is probably not the order you expected, but does explain why your next load from the database doesn't work: the getSellerUserID = incomingData.data()!['userID'] line hasn't been run yet by that time.
For this reason: any code that needs the data from Firestore, needs to be inside the then (or onSnapshot) handler, be called from there, or be otherwise synchronized.
So the simplest fix is to move the next database call into the `then:
FirebaseFirestore.instance
.collection('posts')
.doc(widget.postID)
.get()
.then((incomingData) {
var sellerUserID = incomingData.data()!['userID'];
setState(() {
getSellerUserID = sellerUserID;
});
print(sellerUserID);
FirebaseFirestore.instance
.collection('users')
.doc(sellerUserID)
.get()
.then((incomingData) {
setState(() {
getUserName = incomingData.data()!['username'];
getProfileImage = incomingData.data()!['profileImage'];
getUniversity = incomingData.data()!['university'];
getLocation = incomingData.data()!['location'];
});
});
});

Using Transactions but still getting same objects from Firebase

I have a Firebase document "coupon" that has 2 fields inside: an array of strings and an integer as seen below
Currently if a user clicks on a button to get a coupon, it will remove the 0 index at Firebase and show that removed array as coupon code in a Text widget, but if two or more users click on the button at the same time they all get the same string from the array.
This is my button on click code currently:
try {
await FirebaseFirestore.instance
.runTransaction((transaction) async {
DocumentReference
couponCollectionReference =
FirebaseFirestore.instance
.collection('coupons')
.doc(widget.couponNumber);
DocumentReference userCollectionReference =
FirebaseFirestore.instance
.collection('users')
.doc(getUserID());
setState(() {
couponTitle = couponCode[0];
couponBlur = 0.0;
isButtonWorking = false;
});
transaction
.update(couponCollectionReference, {
'coupon_code': FieldValue.arrayRemove(
[couponCode[0]]),
});
transaction
.update(couponCollectionReference, {
'coupons_available':
FieldValue.increment(-1),
});
transaction
.update(userCollectionReference, {
'current_points':
FieldValue.increment(-100),
});
await screenshotController
.capture(
pixelRatio: 2,
delay: Duration(milliseconds: 20))
.then((capturedImage) async {
ShowCapturedWidget(
context, capturedImage!);
}).catchError((onError) {
print(onError);
});
});
} catch (e)
Are transactions the way to go and I'm just not implementing them right or am I using a totally wrong approach ?
In order for the coupon document to be considered part of the transaction, you have to read it from the database through the transaction object (and use the value of coupons from there).
In your code that'd be something like this:
await FirebaseFirestore.instance
.runTransaction((transaction) async {
DocumentReference
couponCollectionReference =
FirebaseFirestore.instance
.collection('coupons')
.doc(widget.couponNumber);
DocumentSnapshot couponDoc = await transaction.get(couponCollectionReference); // 👈
couponCode = (couponDoc.data() as Map<String, dynamic>)['coupons'];
...

Firebase query doesn't get documents with a specific field

I'm running a query to get all documents from firebase containing a field of location contains, say 'Nairobi'. The structure of my database is /posts/[userId]/userPosts/[postId].
Now, when I specify the userId in my code, I get the documents.
QuerySnapshot snapshot = await FirebaseFirestore.instance.collection('posts')
.doc("1092987983578079255")
.collection('userPosts')
.where('location', isGreaterThanOrEqualTo: '${placemark.locality}')
.get();
setState(() {
posts = snapshot.docs.map((doc) => Post.fromDocument(doc)).toList();
isLoading = false;
});
However, when I try to get all the documents within the entire posts collection, I get null results.
QuerySnapshot snapshot = await FirebaseFirestore.instance.collection('posts')
.where('location', isGreaterThanOrEqualTo: '${placemark.locality}')
.get();
setState(() {
posts = snapshot.docs.map((doc) => Post.fromDocument(doc)).toList();
});
I have tried using the code
QuerySnapshot snapshot = await FirebaseFirestore.instance
.collectionGroup("userPosts")
.where('location', isGreaterThanOrEqualTo: '${placemark.locality}')
.get();
setState(() {
posts = snapshot.docs.map((doc) => Post.fromDocument(doc)).toList();
isLoading = false;
});
but I get the error Operation was rejected because the system is not in a state required for the operation's execution. If performing a query, ensure it has been indexed via the Firebase console.
given that, it is only querying through one criterion, I don't believe that a composite index is necessary but I, still, have created one.
adb logcat doesn't give me the link to create an index even if I wrap the code in try to catch the error e.
What am I missing?

Flutter FIrebase trying to add users to the users list but it is not appending the list, the list has only the newly added value

So I was trying to add users to the users list but it is not appending the list, the list has only the newly added value.
I cant find what I am doing wrong.
Here is the code
sendToChatroom({String chatRoomId}) {
List<dynamic> users = List();
databaseMethods.getMap(chatRoomId).then((value) async {
users = await value.data()['users'];
print(users);
});
if (!users.contains(Constants.myName)) {
users = users + [Constants.myName];
print(users);
}
Map<String, dynamic> chatRoomMap = {
"users": users,
"chatRoomId": chatRoomId
};
databaseMethods.setMap(chatRoomId, chatRoomMap);
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => GroupChat(chatRoomId)));
}
Database methods
getMap(String groupChatName) async {
return await FirebaseFirestore.instance
.collection("groupChat")
.doc(groupChatName)
.get();
}
setMap(String groupChatName, Map groupChatMap) async {
return await FirebaseFirestore.instance
.collection("groupChat")
.doc(groupChatName)
.set(groupChatMap);
}
If you want to append the array, you should use FieldValue.arrayUnion
Example:
Map<String, dynamic> chatRoomMap = {
"users": FieldValue.arrayUnion(users),
"chatRoomId": chatRoomId
};
With this, users would be appended to the current array value.

Merge Firestore's separate queries Streams in Dart

I'm implementing a Todo Application in Flutter. I need to merge a double query in client, in order to perform an OR request in Firestore.
One hand, I have the following code that performs the double queries.
Future<Stream> combineStreams() async {
Stream stream1 = todoCollection
.where("owners", arrayContains: userId)
.snapshots()
.map((snapshot) {
return snapshot.documents
.map((doc) => Todo.fromEntity(TodoEntity.fromSnapshot(doc)))
.toList();
});
Stream stream2 = todoCollection
.where("contributors", arrayContains: userId)
.snapshots()
.map((snapshot) {
return snapshot.documents
.map((doc) => Todo.fromEntity(TodoEntity.fromSnapshot(doc)))
.toList();
});
return StreamZip(([stream1, stream2])).asBroadcastStream();
}
And other hand, I have the following code that will perform the update of view with the Bloc pattern.
Stream<TodosState> _mapLoadTodosToState(LoadTodos event) async* {
_todosSubscription?.cancel();
var res = await _todosRepository.todos(event.userId);
_todosSubscription = res.listen(
(todos) {
dispatch(
TodosUpdated(todos));
},
);
}
I have the following error.
flutter: Instance of '_AsBroadcastStream<List<List<Todo>>>'
[VERBOSE-2:ui_dart_state.cc(148)] Unhandled Exception: type 'List<List<Todo>>' is not a subtype of type 'List<Todo>'
I tried to look for more infos with the debugger and it turned out that my StreamZip source contains the 2 stream separately.
For the moment I can get one stream at a time.
I don't know how to proceed in order to get the 2 streams and display them.
You're doing a map of a map, which returns a List of another List.
You should zip the QuerySnapshot streams and do the mapping after creating the subscription, and then you can create a new Stream<List<Todo>> from it.
///private method to zip QuerySnapshot streams
Stream<List<QuerySnapshot>> _combineStreams() async {
Stream stream1 = todoCollection
.where("owners", arrayContains: userId)
.snapshots()
});
Stream stream2 = todoCollection
.where("contributors", arrayContains: userId)
.snapshots()
});
return StreamZip(([stream1, stream2])).asBroadcastStream();
}
///exposed method to be consumed by repository
Stream<List<Todo>> todosStream() {
var controller = StreamController<List<Todo>>();
_combineStreams().listen((snapshots) {
List<DocumentSnapshot> documents;
snapshots.forEach((snapshot) {
documents.addAll(snapshot.documents);
});
final todos = documents.map((document) {
return Todo.fromEntity(TodoEntity.fromSnapshot(doc));
}).toList();
controller.add(todos);
});
return controller.stream;
}

Resources