How to get subcollection data from firebase? - firebase

Im trying to calculating some data together . What I have is a videos collection and then each video has a doc id and and then some field for example the uid of the user that uploads this video. And also every video has a sub collection named uservotes. Inside their I saved user voting of this video. This is how It looks
And what I want is getting of one user the user votes rating field calculating together .
HERes how I get for one video the rating maybe that will help to understand
FutureBuilder(
future: firebase,
builder:
(BuildContext context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return Center(
child: Text("loading..."));
} else {
double summe = 0.0;
final docs = snapshot.data.docs;
for (int i = 0;
i < docs.length;
i++) {
summe += (docs[i]['rating']);
print(summe);
}
final possiblestars = docs.length * 5;
print(possiblestars);
return Text(
"${summe.toInt()}/${possiblestars}",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18),
);
}
}),
The firebase stream is this one
var firebase = FirebaseFirestore.instance
.collection('videos')
.doc(videos.data()['id'])
.collection('uservotes')
.get();
So instead of getting all user votes of one video I wanna get all uservotes of all videos form the same user. You can see that each video in videos collection has uid field and thats the uid of the user that upload the video I can get the current user id with that
final uid= FirebaseAuth.instance.currentUser.uid;
And every video that has saved this uid as uid value Iinside field "uid" in videoscollction is one off the videos that I needed
. Then I wanna get of each video the sub collection uservotes all rating to calculating they together . Dont get confused about the same uid doc inside user votes collection thats because the user that uploads this current video also rate his own video .
Hope anyone can help .

For your requirement, with your data model, you would need to query all the videos docs with the same uid, and then, for each doc, query all the doc of the uservotes subcollection.
You could simplify this queries by adding the video owner uid to the uservotes docs and use a collectionGroup query.
HOWEVER, it is not recommended at all to query an entire (sub)collection each time you want to get the number of documents, because you are billed for each document that you read. So it can very fast generate an important bill...
You should maintain a counter for each user (and maybe for each video?). For that the best is to use distributed counters. I would advise to use Cloud Functions to update the counters, this way end-users cannot modify the counters.

Unfortunately, it's impossible in a single query. You should get all videos firstly and then make a new call for each one to get subcollection.

Related

error in saving two users chat in flutter

I was making chat app in flutter with firebase. I was able to save the users chat in firebase by this function:
await FirebaseFirestore.instance
.collection("Chats")
.doc(uid! + FirebaseAuth.instance.currentUser!.uid)
.collection("messages")
.add({
FirebaseAuth.instance.currentUser!.uid:
chatController.text,
}).then((value) => () {
b = uid;
});
but when I save the one users chat. I was thinking that when I will get the chat of second user. It will return a chat from the doc I was saving the both users chat. But unfortunately when I save the users chat it saves a new doc named first users id and then second users id, in first users chat it saves doc named the second user id and first users id. I know what is the reason from which it was happening but how can I resolve that, I mean how can I save the both users chat in one doc
Update: the function I have tried based on Frank's answer:
var docId = uid
.toString()
.compareTo(FirebaseAuth.instance.currentUser!.uid) > 0;
the function used for printing docId:
IconButton(
icon: Icon(Icons.send),
iconSize: 20.0,
onPressed: () async { print(docId);}
)
You need to have a deterministic ID for the document.
One simple way to do this is to always alphabetically order the two UIDs with something like this:
const docId = uid!.compareTo(FirebaseAuth.instance.currentUser!.uid) > 0
? uid! + FirebaseAuth.instance.currentUser!.uid
: FirebaseAuth.instance.currentUser!.uid + uid!
await FirebaseFirestore.instance
.collection("Chats")
.doc(docId)
...
For more on this, see the examples in my answer here: Best way to manage Chat channels in Firebase

Realtime data from firestore flutter not working

I was following this tutorial on how to get realtime updates from flutter firestore, https://medium.com/firebase-tips-tricks/how-to-use-cloud-firestore-in-flutter-9ea80593ca40 and I scrolled down to Listen For Realtime Updates section and when I followed the tutorial, this is what I came up with,
String name = 'name here';
String children = 'children here';
String docId = '0';
#override
void initState() {
getUsers();
super.initState();
}
getUsers() async {
final FirebaseAuth auth = FirebaseAuth.instance;
final User? user = auth.currentUser;
final uid = user!.uid;
FirebaseFirestore.instance
.collection("userNames")
.where("uid", isEqualTo: uid)
.snapshots()
.listen((result) {
result.docs.forEach((result) {
print(result["firstName"]);
print(result["children"].toString());
name = result["firstName"];
children = result["children"].toString();
});
});
}
When I print the values to the console they update in realtime, but when I put them in variables and concatenate them into a Text widget like this:
Text('Children: $children'), //It does not update in realtime.
For instance, if in my document if I have children: 3 and I display in in my app, it shows 3, but when I manually change it, it does not update in realtime, I have to press hot reload. Another issue is that I have to initialize the variable before using them in the function, up ahead in the first 3 lines of code. When I hot restart, it shows the values of what I use to initialize them. For where it should show children, it says 'children here' and for where the name is, it puts 'name here', only when I hot reload the page, do the actual firestore values get inputed into them and show data from the firestore database. If there is a solution to any of these problems, I would much prefer an answer in code instead of a link or a brief explanation, I spend hours before I find a piece of code that utilizes the explanation. Thank you
I use snapshots().listen() to listen to change. Then I use ValueNotifier to notify the UI.
final itemsNotifier = ValueNotifier<List<Item>>([]);
FirebaseFirestore.instance
.collection("userNames")
.where("uid", isEqualTo: uid)
.snapshots()
.listen((event) {
itemsNotifier.value = event.docs
.map((doc) => Item.fromSnapshot(
doc as DocumentSnapshot<Map<String, dynamic>>))
.toList();
itemsNotifier.notifyListeners();
});
Since the data is loaded asynchronously, the data isn't available when Flutter first paints your Text widget. You'll need to tell Flutter that it has to repaint the UI when the data is available.
There are two common ways to do this:
Put the children variable in the state of your widget by calling setState(). This will tell Flutter to repaint the widget, and your text will then show the value.
You can also use a StreamBuilder widget, which does the above too - but it also handles all kinds of error states automatically.
I recommend reading about stateful widgets and setState and about the StreamBuilder class to learn more.

how to get data from firestore sub-collection

i have a firestore database in which i have "Singer" then inside every singer there is a sub-collection of "Song List" for every singer. i am able to get data from main collection but not from sub-collection. can you guys help?
stream Builder
StreamBuilder(
stream: Firestore.instance.collection('singers').snapshots(),
ListView.Builder
ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) => SingleChildScrollView(
this is where i want to show data from sub-collection
Text(
snapshot.data.documents[index]['name'],
style: TextStyle(
fontSize: 20, color: Colors.red[500]),
)
Database
You can get the data from the collection by accessing the subcollection inside the document like this
Firestore.instance.collection('singers').document('aryana sayeed').collection('song list').snapshots()
or
Firestore.instance.collection('singers/aryana sayeed/song list').snapshots()
You can read it like this:
Firestore.instance.collection('singers').snapshot().listen((val)=>{
val.documents.forEach((doc)=>{
doc.reference.collection('song list')
.getDocuments().then((res)=>{
res.douments.forEach((d)=>{
print(d.data);
})
})
})
});
Now this gives you a stream to all docs of collection singers and thier subcollection
To clarify what you are saying: you want a list of all the songs from all the singers,
Easy-Peasy. CollectionGroup is your friend.
I don't use your environment, but I can see that:
StreamBuilder(
stream: Firestore.instance.collectionGroup('song list').snapshots(),
is the start you need. collectionGroup treats all sub-collections with the name 'song list' as one collection.
NOTE (because this always comes up)
Each documentSnapshot returned include a field 'refpath' - which is a string with the entire path to that specific document. You can trivially parse the string to find the parent document(s) or collections(s). For example, a particular song with have in it's refPath ".../singers/{singerID}/songlist/{songID}"
btw, I HIGHLY HIGHLY HIGHLY recommend AGAINST using the singer name as the document ID. Queries are generally of fields, not documentID's, so it won't help your code find the artist, and they are neither unique enough nor randomly distributed enough to be efficient. Let Firestore generate unique documentID's for you, and put the artist name in a field.

Cloud firestore read count in StreamBuilder

Firebase structure:
Code:
I'm using a StreamBuilder for document uid like this:
#override
Widget build(BuildContext context) {
return StreamBuilder<User>(
stream: _stream(),
builder: (BuildContext _, AsyncSnapshot<User> snapshot) {
// this block may get called several times because of `build` function
if (snapshot.hasData) {
final user = snapshot.data;
return SomeWidget(user: user);
}
return CircularProgressIndicator();
},
);
}
Questions:
Since StreamBuilder's builder may get called several times because of the build() method, will that cost me a read every time builder gets called?
Is there any difference in terms of read-count when reading complete uid vs reading uid/education?
If I update age and name value, will that count as one-write or two-writes in terms of firebase write-count?
Firestore charges on every document read, write and delete therefore:
Since StreamBuilder's builder may get called several times because of the build() method, will that cost me a read every time builder gets called?
Yes, if you are reading(retrieving) one document each time, then you will be charged as one read.
Is there any difference in terms of read-count when reading complete uid vs reading uid/education
No difference. The read is done in the document, when you retrieve one document then you are doing one read.
If I update age and name value, will that count as one-write or two-writes in terms of firebase write-count?
If you update one document once (even if all the fields are updated), it will cost you one write operation.

Get all documents from collection in firestore

I'm trying to get all posts from my 'instagram' clone app. This is the path I have in firestore: posts > (unique ownerId) > userPosts > (unique postId)
How can I retrieve all posts using a stream builder? I tried doing so with
body:
StreamBuilder<QuerySnapshot>(
stream: postsRef.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return circularProgress();
}
List<Post> posts = snapshot.data.documents.map((doc) => Post.fromDocument(doc)).toList();
return ListView(children: posts);
},
)
I want to display only the posts' pictures in a stack of cards.
You can't use wildcards with listeners in Cloud Firestore. You need to name the specific document and collections in the path. So, if your (unique ownerId) is unknown at the time of the query, you will not be able to know anything about the documents changing in its subcollections.
As an alternative, on a backend you control, you can list subcollections of a document, then query those documents. Or, you can use a Cloud Functions trigger and notify interested client apps (maybe with Cloud Messaging) as changes happen.

Resources