Firestore Query Document's Subcollection - flutter

I'm learning Flutter/Dart and trying to connect to my Firestore database. I want to query a collection for a document, then a collection within that document. I'm still learning Dart but what I've found is that async stream building should be the best method.
I have tried db.instance.collect('').document('').collection('').snapshot() but streambuilder doesn't seem to support this and I get the error
The method 'collection' isn't defined for the type 'Query'.
I have to end my query after db.instance.collection('').where('name' isEqualto: 'Name').snapshots()
Below is an example.
My goal is to query all of the classes and present them in a list.
Example - Teachers <-- 1st
Collection   Teacher Name <--Document within collection
  Classes <---Collection 2    Class1 <-- Query This    Class2 <---Query
This
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class ClassListPage extends StatelessWidget{
#override
Widget build(BuildContext context){
return Material(
child: new StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('Teachers').
where('name', isEqualTo: 'Dr. Who')
//.collection('Classes') <--This is not working
.snapshots(),
//HERE is where I want to query for the 'classes' collection then query
//for the documents within
builder:(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot){
if(!snapshot.hasData)return new Text('..Loading');
return new ListView(
children: snapshot.data.documents.map((document){
return new ListTile(
title: new ListTile(
title: new Text(document['name']),
),
);
}).toList(),
);
}
),
);
}
}

In your question, you say that you're using db.instance.collection('').document('').collection('').snapshot(), but in your code, there is no call to document(). Here's what I see:
Firestore.instance
.collection('Teachers')
.where('name', isEqualTo: 'Dr. Who')
.collection('Classes')
.snapshots()
This isn't going to work because where() returns a Query, and Query doesn't have a collection() method. It sounds like what you need to do instead is execute that query, look at the documents in the result set (there could be any number, not just 1), then make anoterh query for each document's subcollections.

Related

Does Streambuilder store the data after app restarts?

I have a simple streambuilder that reads the users document, and I use it to show some of the user's data. My question is, would this streambuilder re-read the document everytime the user restarts the app? If, yes is there any way to prevent the streambuilder from re-reading it everytime the user restarts the app unless there is a change in the document?
StreamBuilder(
stream: _firestore
.collection('users')
.doc(_auth.currentUser!.uid)
.snapshots(),
builder:
(context, AsyncSnapshot<DocumentSnapshot<Object?>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator(
color: isDarkMode ? Colors.white : Colors.black,
);
}
if (snapshot.hasData) {
if (snapshot.data!.exists) {
snapshot.data!['serviceEnabled'] == true
? startServices()
: null;
return Center(
child: Column(
This streambuilder is on the homepage of the app, I show some of the user's data on the homepage.
How would the database know whether there's a change in the document without reading that document?
If you can answer that, you can probably write a query to match that same condition.
For example, if each document has a lastUpdated field, you could just get the updated document with:
_firestore
.collection('users')
.where('lastUpdated', '>', timestampWhenYouLastReadDocuments)
.get()
Aside from that query to update the cache, you could then get the documents from the cache in other places in your app.

Need I do pagination for flutter StreamBuilder?

I'm new to flutter, and now I'm creating an app which has a feed page, I'm using StreamBuilder + firestore to do this, the code is like this:
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('posts')
.orderBy('createdAt', descending: true)
.snapshots(),
builder: (context,
AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(
color: primaryColor,
),
);
}
return ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) => Container(
child: createPostViewFromSnapShot(snapshot.data!.docs[index]),// it returns a widget
),
);
},
);
You can see from the code I didn't do pagination, I want to know when the code runs, it fetch all the post from firestore ? Or it will fetch data by block or something like pagination ?
I want to know if it's necessary to do pagination for StreamBuilder?
Avoid creating Widgets in Methods. That way you cause your App to
become I'm-performant. Because the Widget is not sat directly in the
Tree, but a method, every build that methods gets called, no matter
if the resulting widget would have to be actually be rebuilt.
That stream will not always emit events where data is not null. You will most likely get an exception for using snapshot.data! bang operator
Take a closer look at the documentation: https://firebase.flutter.dev/docs/firestore/usage/
FlutterFire provides support for dealing with realtime changes to
collections and documents. A new event is provided on the initial
request, and any subsequent changes to collection/document whenever a
change occurs (modification, deleted or added).
For what you are trying to achieve, it would be better to initially fetch a limited set of documents from your collection.
collectionReference.limit(42).get()
You can than fetch the next elements by using https://firebase.flutter.dev/docs/firestore/usage/#start--end-cursors

Flutter querying different levels of data in Streambuilder

I am trying to access two levels of information in Firebase. The top level collection is called messages and the second level is called chats. I am having trouble accessing the data snapshot retrieved of the Chats collection. Here is a screen print of my collections:
And here please find the code:
return StreamBuilder<QuerySnapshot>(
stream: _firestore
.collection('messages')
.where("senderId", isEqualTo: currentUserId)
.where("receiverId", isEqualTo: member.id)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(
backgroundColor: Colors.lightBlueAccent,
),
);
}
final messageHeaders = snapshot.data!.docs.reversed;
print("messages first = ${messageHeaders.first.id}");
List<MessageBubble> messageBubbles = [];
for (var messageHeader in messageHeaders) {
print(messageHeader["senderId"]);
for (var messageDetail in messageHeader["chats"]) {
print(messageDetail["text"]);
}
So in the print statements above. The one to print the "senderId" is working fine. But not the one to print the "text"
Any help with this would be gratefully received.
Firestore queries are shallow, meaning that you don't get the entire subtree when you query a document. In your case, chats is a subcollection, therefore it will not be queried with the document.
So after getting the document snapshot, you have to make another Firestore query to get chats subcollection for that specific document in messages collection.

Retrieve data with StreamBuilder from collection and subCollection in same time Flutter cloud Firestore

I have a collection called UserCollection,
into this collection I have a subcollection with other specific documents called 'UserSubCollection'
with StreamBuilder I am trying to get these collections unsuccesfully
StreamBuilder(
stream: FirebaseFirestore.instance
.collection('UserCollection')
.doc(firebaseUser.uid).collection('UserSubCollection')
.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return Center(child: Text("Error"));
else if (snapshot.data.docs.isEmpty) //also check if empty! show loader?
return Center(child: Text('Errors'));
else
return new ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (BuildContext context, int index) {
I am able to get just one or a collection or the subcollection.
there is a way to get them both?
First question: do your items need to update automatically over time? Otherwise, I suggest you a FutureBuilder instead of a StreamBuilder.
To get both I suggest you using a separate method like this:
Future<List</*INSERT RETURN TYPE*/>> _retrieveUsers() async{
//get all the users
QuerySnapshot query = await FirebaseFirestore.instance
.collection('UserCollection').get();
Map<String,Map<String,dynamic>> userMap = Map();
//add each user to the map
query.docs.forEach(doc => userMap.putIfAbsent(doc.id, doc.data());
FutureGroup<QuerySnapshot> queries = FutureGroup(); //add async package in pubspec.yaml [there might be version issues]
//retrieve sub collections and bind them to the corresponding user
query.docs.forEach((doc) => queries.add(_retrieveSubCollection(userMap,doc)));
//Close queries
queries.close();
//Await for all the queries to be completed
await queries.future;
//now you have for each user a structure containing its data plus everything in its sub collection. Do whatever you want and then return a list or a map according to what you need
return /*YOUR RETURN*/;
}
Future<Void> _retrieveSubCollection( Map<String,Map<String,dynamic>> userMap, DocumentSnapshot doc) async{
//retrieve the subcollection of a document using its id
QuerySnapshot query = query FirebaseFirestore.instance
.collection('UserCollection')
.doc(doc.id).collection('UserSubCollection').get();
//bind the subcollection to the relative user
Map<String,dynamic> userData = userMap[doc.id];
userData.add("subcollection", query.docs.map((subDoc) =>subDoc.data()).toList());
userMap.update(doc.id,(value)=> userData);
return;
}
Now just use a future builder to display the result of your queries.
FutureBuilder(future: _retrieveUsers(), builder: (context,snapshot) {/*YOUR CODE*/}
I actually didn't try the code in a real environment, so if you find issues on updating the user map there might be problems due to asynchronous updates. However, since every query target a specific element in the map, I don't think there will be issues

How can I query another collection inside a streambuilder that queries a different collection?

I'm trying to query a document field from a different firestore collection than the one of the streambuilder. Right now, with the streambuilder I am able to query one of the documents i need, however I don't how i'm to query another collection in that same stream builder. I thought about using a global variable from another class where I had already queried the document I want but that didn't seem to work.
This is the stream builder
Container(
height: MediaQuery.of(context).size.height,
margin: EdgeInsets.only(top: 0),
padding: EdgeInsets.only(top: 0),
child: StreamBuilder(
stream: Firestore.instance
.collection('Tournaments')
.document('Fortnite Tournaments')
.collection('Fortnite Tourneys')
.document(docID)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return new Text("Loading");
}
var tourneyDetails = snapshot.data;
return ListView(
children: <Widget>[
..........
The collection i'm trying to query is called "users"
Why i need to query a field in a different collection is because i'm writing an if statement that needs both of the fields
if (int.parse("Field_i_am_trying_to_get_from_another_collection") > int.parse(tourneyDetails[ 'tourneycost']))
print('You have the money');
else {
print('You're broke');
}
That condition is going to be executed when the raised button is clicked. Essentially, My problem is that I am currently not sure how I can query the data from that other collection.
Not sure if i explained that well but comment if you need more context or code, Thanks.
You can use provider instead of creating global variable.
see Provider package: https://pub.dev/packages/provider
if the current user is the user you want, then you can take the current user data from firestore when the app is loading and provide the user to where you want to use.
see this article if you don't know what provider is:
Flutter StateManagement with Provider