Accessing nested collections and document in Firebase (Flutter) - firebase

If you cannot understand the title I am very sorry. I am new to NoSQL don't know how to address this problem.
i am having a nested collection inside my firebase database.
first collection
clicking on '145'
clicking on 'location'
in the chargepoints method I have hardcoded (.doc('WPHW9669')) instead of that I have to loop through the 145 collection and get the document first. after that I have to go inside that looped document to access the 'location' collection.
this is the code which needs to be modified:
chargePoints() {
_db
.collection('bus')
.doc('Routes')
.collection('145')
.doc('WPHW9669') // this the place where the first iteration have to occur
.collection('location')
.orderBy('updatedTime', descending: true)
.limit(1)
.get()
.then((docs) {
if (docs.docs.isNotEmpty) {
for (int i = 0; i < docs.docs.length; i++) {
LatLng position = LatLng(
docs.docs[i].data()['position']['geopoint'].latitude,
docs.docs[i].data()['position']['geopoint'].longitude);
print(docs.docs[i].data()['position']['geopoint'].latitude);
initMarker(position, docs.docs[i].id);
// notifyListeners();
// initMarker(docs.docs[i].data(), docs.docs[i].id);
}
}
});
}

So I found the answer that I am looking for.
instead of creating a complicated collection, I change my collection to be simple.
new collection URL - 145/(Bus NO)/location/
final result = FirebaseFirestore.instance.collection('145');
busPoint() {
result.get().then((snapshot) {
snapshot.docs.forEach((element) {
chargePoints(element.id.toString());
print(element.id);
});
});
}
the above method will loop through the '145' collection and call the chargePoints() method for each bus no's found(i registered the bus no as the document id).
chargePoints(String element) {
_db
.collection('145')
.doc(element)
.collection('location')
.orderBy('updatedTime', descending: true)
.limit(1)
.get()
.then((docs) {
if (docs.docs.isNotEmpty) {
for (int i = 0; i < docs.docs.length; i++) {
LatLng position = LatLng(
docs.docs[i].data()['position']['geopoint'].latitude,
docs.docs[i].data()['position']['geopoint'].longitude);
initMarker(position, docs.docs[i].id);
}
}
});
}

Related

Flutter Firebase async query not retrieving data inside a stream function

I am trying to query a User from firebase within another query but for some reason but I can't get the code to work
The function the wont run is await usersRef.doc(uid).get(); and can be found here:
static getUserData(String uid) async {
return await usersRef.doc(uid).get();
}
static DirectMessageListModel getDocData(QueryDocumentSnapshot qdoc, String uid) {
Userdata postUser = Userdata.fromDoc(getUserData(uid));
return DirectMessageListModel.fromDoc(qdoc, postUser);
}
static DirectMessageListModel fromDoc(QueryDocumentSnapshot doc, Userdata altUser) {
return DirectMessageListModel(
doc['chatId'],
doc['lastMsgContent'],
doc['lastMsgType'],
altUser
);
}
parent function:
Stream<List<DirectMessageListModel>> getMeassageList(){
var snaps = FirebaseFirestore.instance.collection('directMessages').where('users', arrayContains: userdata!.uid).snapshots();
List<String> usersListElement = [];
return snaps.map((event) { return event.docs.map((e) {
usersListElement = [e.get('users')[0], e.get('users')[1]];
usersListElement.remove(userdata!.uid);
return DirectMessageListModel.getDocData(e, usersListElement.first);
}).toList();
});
}
You forgot to wait for the future getUserData(uid) to complete.
Try this:
static Future<DocumentSnapshot<Object>> getUserData(String uid) async {
return await usersRef.doc(uid).get();
}
static DirectMessageListModel getDocData(
QueryDocumentSnapshot qdoc,
String uid,
) async {
Userdata postUser = Userdata.fromDoc(await getUserData(uid)); // await here
return DirectMessageListModel.fromDoc(qdoc, postUser);
}
..
// parent function.
// Also wait for the future in the parent function.
// UPDATE BELOW! Define the parent function like this:
Stream<List<Future<DirectMessageListModel>>> getMeassageList() {
var snaps = FirebaseFirestore.instance
.collection('directMessages')
.where('users', arrayContains: userdata!.uid)
.snapshots();
List<String> usersListElement = [];
return snaps.map((event) {
return event.docs.map((e) async {
usersListElement = [e.get('users')[0], e.get('users')[1]];
usersListElement.remove(userdata!.uid);
return await DirectMessageListModel.getDocData(e, usersListElement.first);
}).toList();
});
}
NB: You are fetching user data (either sender/receiver) for each message in directMessages collection. It might be better to store just sender/receiver name in directMessages collection and simply display that. Then if the user clicks on a message, you can then fetch the full sender/receiver data.

Retrieve data of an array

I want to retrieve data of an array that stores url's and place them, each one, in an item in a list. In my example, all url's are stored in the first item. Is there a way to manipulate the field indexes todo this?
[https://i.stack.imgur.com/KnIF6.png][1].
Here's my code:
Future<void> getImages() async {
int i = 0;
listImgs = List(4);
await firestoreInstance
.collection('dados_inst')
.where('cod_inst', isEqualTo: codInst)
.get()
.then((value) {
value.docs.forEach((snapshot) {
listImgs[i++] = snapshot.get('list_view');
});
});
for (i = 0; i < 4; i++) {
print(listImgs[i]);
}
}
A simple way is to convert the value.docs list to a map, and use the forEach method of that, which gets both the index and the value:
value.docs.asMap().forEach((index, snapshot) {
listImgs[index] = snapshot.get('list_view');
});
See also:
Enumerate or map through a list with index and value in Dart

Flutter Return Length of Documents from Firebase

Im trying to return the length of a list of documents with this function:
Future totalLikes(postID) async {
var respectsQuery = Firestore.instance
.collection('respects')
.where('postID', isEqualTo: postID);
respectsQuery.getDocuments().then((data) {
var totalEquals = data.documents.length;
return totalEquals;
});
}
I'm initialize this in the void init state (with another function call:
void initState() {
totalLikes(postID).then((result) {
setState(() {
_totalRespects = result;
});
});
}
However, when this runs, it initially returns a null value since it doesn't have time to to fully complete. I have tried to out an "await" before the Firestore call within the Future function but get the compile error of "Await only futures."
Can anyone help me understand how I can wait for this function to fully return a non-null value before setting the state of "_totalRespsects"?
Thanks!
I think you're looking for this:
Future totalLikes(postID) async {
var respectsQuery = Firestore.instance
.collection('respects')
.where('postID', isEqualTo: postID);
var querySnapshot = await respectsQuery.getDocuments();
var totalEquals = querySnapshot.documents.length;
return totalEquals;
}
Note that this loads all documents, just to determine the number of documents, which is incredibly wasteful (especially as you get more documents). Consider keeping a document where you maintain the count as a field, so that you only have to read a single document to get the count. See aggregation queries and distributed counters in the Firestore documentation.
Perfect code for your problem:
int? total;
getLength() async {
var getDocuments = await DatabaseHelper.registerUserCollection
.where("register", isEqualTo: "yes")
.get();
setState(() {
total = getDocuments.docs.length;
});
}
#override
void initState() {
super.initState();
getLength();
if (kDebugMode) {
print(total);
}
}

Can Firestore update multiple documents matching a condition, using one query?

In other words, I'm trying to figure out what is the Firestore equivalent to this in SQL:
UPDATE table SET field = 'foo' WHERE <condition>`
Yes, I am asking how to update multiple documents, at once, but unlike the linked questions, I'm specifically asking how to do this in one shot, without reading anything into memory, because there's no need to do so when all you want is to set a flag on all documents matching a condition.
db.collection('table')
.where(...condition...)
.update({
field: 'foo',
});
is what I expected to work, CollectionReference doesn't have an .update method.
The
Transactions and Batched Writes documentation mentions transactions and batched writes. Transactions are out because "A transaction consists of any number of get() operations followed by any number of write operations" Batched writes are also not a solution because they work document-by-document.
With MongoDB, this would be
db.table.update(
{ /* where clause */ },
{ $set: { field: 'foo' } }
)
So, can Firestore update multiple documents with one query, the way SQL database or MongoDB work, i.e. without requiring a round-trip to the client for each document? If not, how can this be done efficiently?
Updating a document in Cloud Firestore requires knowings its ID. Cloud Firestore does not support the equivalent of SQL's update queries.
You will always have to do this in two steps:
Run a query with your conditions to determine the document IDs
Update the documents with individual updates, or with one or more batched writes.
Note that you only need the document ID from step 1. So you could run a query that only returns the IDs. This is not possible in the client-side SDKs, but can be done through the REST API and Admin SDKs as shown here: How to get a list of document IDs in a collection Cloud Firestore?
Frank's answer is actually a great one and does solve the issue.
But for those in a hurry maybe this snippet might help you:
const updateAllFromCollection = async (collectionName) => {
const firebase = require('firebase-admin')
const collection = firebase.firestore().collection(collectionName)
const newDocumentBody = {
message: 'hello world'
}
collection.where('message', '==', 'goodbye world').get().then(response => {
let batch = firebase.firestore().batch()
response.docs.forEach((doc) => {
const docRef = firebase.firestore().collection(collectionName).doc(doc.id)
batch.update(docRef, newDocumentBody)
})
batch.commit().then(() => {
console.log(`updated all documents inside ${collectionName}`)
})
})
}
Just change what's inside the where function that queries the data and the newDocumentBody which is what's getting changed on every document.
Also don't forget to call the function with the collection's name.
The simplest approach is this
const ORDER_ITEMS = firebase.firestore().collection('OrderItems')
ORDER_ITEMS.where('order', '==', 2)
.get()
.then(snapshots => {
if (snapshots.size > 0) {
snapshots.forEach(orderItem => {
ORDER_ITEMS.doc(orderItem.id).update({ status: 1 })
})
}
})
For Dart / Flutter user (editted from Renato Trombini Neto)
// CollectionReference collection = FirebaseFirestore.instance.collection('something');
// This collection can be a subcollection.
_updateAllFromCollection(CollectionReference collection) async {
var newDocumentBody = {"username": ''};
User firebaseUser = FirebaseAuth.instance.currentUser;
DocumentReference docRef;
var response = await collection.where('uid', isEqualTo: firebaseUser.uid).get();
var batch = FirebaseFirestore.instance.batch();
response.docs.forEach((doc) {
docRef = collection.doc(doc.id);
batch.update(docRef, newDocumentBody);
});
batch.commit().then((a) {
print('updated all documents inside Collection');
});
}
If anyone's looking for a Java solution:
public boolean bulkUpdate() {
try {
// see https://firebase.google.com/docs/firestore/quotas#writes_and_transactions
int writeBatchLimit = 500;
int totalUpdates = 0;
while (totalUpdates % writeBatchLimit == 0) {
WriteBatch writeBatch = this.firestoreDB.batch();
// the query goes here
List<QueryDocumentSnapshot> documentsInBatch =
this.firestoreDB.collection("student")
.whereEqualTo("graduated", false)
.limit(writeBatchLimit)
.get()
.get()
.getDocuments();
if (documentsInBatch.isEmpty()) {
break;
}
// what I want to change goes here
documentsInBatch.forEach(
document -> writeBatch.update(document.getReference(), "graduated", true));
writeBatch.commit().get();
totalUpdates += documentsInBatch.size();
}
System.out.println("Number of updates: " + totalUpdates);
} catch (Exception e) {
return false;
}
return true;
}
Combining the answers from Renato and David, plus async/await syntax for batch part. Also enclosing them a try/catch in case any promise fails:
const updateAllFromCollection = async (collectionName) => {
const firebase = require('firebase-admin');
const collection = firebase.firestore().collection(collectionName);
const newDocumentBody = { message: 'hello world' };
try {
const response = await collection.where('message', '==', 'goodbye world').get();
const batch = firebase.firestore().batch();
response.docs.forEach((doc) => {
batch.update(doc.ref, newDocumentBody);
});
await batch.commit(); //Done
console.log(`updated all documents inside ${collectionName}`);
} catch (err) {
console.error(err);
}
return;
}
I like some of the answers but I feel this is cleaner:
import * as admin from "firebase-admin";
const db = admin.firestore();
const updates = { status: "pending" }
await db
.collection("COLLECTION_NAME")
.where("status", "==", "open")
.get()
.then((snap) => {
let batch = db.batch();
snap.docs.forEach((doc) => {
const ref = doc.ref;
batch.update(ref, updates);
});
return batch.commit();
});
It uses batched updates and the "ref" from the doc.
If you have already gathered uids for updating collections, simply do these steps.
if(uids.length) {
for(let i = 0; i < uids.length; i++) {
await (db.collection("collectionName")
.doc(uids[i]))
.update({"fieldName": false});
};
};

Firestore get sub-collection of dynmically generated id? [duplicate]

Say I have this minimal database stored in Cloud Firestore. How could I retrieve the names of subCollection1 and subCollection2?
rootCollection {
aDocument: {
someField: { value: 1 },
anotherField: { value: 2 }
subCollection1: ...,
subCollection2: ...,
}
}
I would expect to be able to just read the ids off of aDocument, but only the fields show up when I get() the document.
rootRef.doc('aDocument').get()
.then(doc =>
// only logs [ "someField", "anotherField" ], no collections
console.log( Object.keys(doc.data()) )
)
It is not currently supported to get a list of (sub)collections from Firestore in the client SDKs (Web, iOS, Android).
In server-side SDKs this functionality does exist. For example, in Node.js you'll be after the ListCollectionIds method:
var firestore = require('firestore.v1beta1');
var client = firestore.v1beta1({
// optional auth parameters.
});
// Iterate over all elements.
var formattedParent = client.anyPathPath("[PROJECT]", "[DATABASE]", "[DOCUMENT]", "[ANY_PATH]");
client.listCollectionIds({parent: formattedParent}).then(function(responses) {
var resources = responses[0];
for (var i = 0; i < resources.length; ++i) {
// doThingsWith(resources[i])
}
})
.catch(function(err) {
console.error(err);
});
It seems like they have added a method called getCollections() to Node.js:
firestore.doc(`/myCollection/myDocument`).getCollections().then(collections => {
for (let collection of collections) {
console.log(`Found collection with id: ${collection.id}`);
}
});
This example prints out all subcollections of the document at /myCollection/myDocument
Isn't this detailed in the documentation?
/**
* Delete a collection, in batches of batchSize. Note that this does
* not recursively delete subcollections of documents in the collection
*/
function deleteCollection(db, collectionRef, batchSize) {
var query = collectionRef.orderBy('__name__').limit(batchSize);
return new Promise(function(resolve, reject) {
deleteQueryBatch(db, query, batchSize, resolve, reject);
});
}
function deleteQueryBatch(db, query, batchSize, resolve, reject) {
query.get()
.then((snapshot) => {
// When there are no documents left, we are done
if (snapshot.size == 0) {
return 0;
}
// Delete documents in a batch
var batch = db.batch();
snapshot.docs.forEach(function(doc) {
batch.delete(doc.ref);
});
return batch.commit().then(function() {
return snapshot.size;
});
}).then(function(numDeleted) {
if (numDeleted <= batchSize) {
resolve();
return;
}
// Recurse on the next process tick, to avoid
// exploding the stack.
process.nextTick(function() {
deleteQueryBatch(db, query, batchSize, resolve, reject);
});
})
.catch(reject);
}
This answer is in the docs
Sadly the docs aren't clear what you import.
Based on the docs, my code ended up looking like this:
import admin, { firestore } from 'firebase-admin'
let collections: string[] = null
const adminRef: firestore.DocumentReference<any> = admin.firestore().doc(path)
const collectionRefs: firestore.CollectionReference[] = await adminRef.listCollections()
collections = collectionRefs.map((collectionRef: firestore.CollectionReference) => collectionRef.id)
This is of course Node.js server side code. As per the docs, this cannot be done on the client.

Resources