Firebase read document count using flutter - firebase

How many read in firebase if under one await I run a loop and I have 20 documents in total but my loop will filter out some after checking. After checking it will take 10 data. But for taking, it needs to read 20 documents. So how many read will it count? I heard one await one read. But my firebase dashboard is showing 5/6 times more. In the picture i have shown the code.
please tell me how many read will it count?
Future getUser() async {
final id = await FirebaseAuth.instance.currentUser();
userId=id.uid;
await Firestore.instance.collection('user').getDocuments().then((users) {
print("loop running");
for (var user in users.documents) {
if ((!seenList1.contains(user.documentID)) &&
(!chosenList1.contains(user.documentID)) &&
(!showId1.contains(user.documentID)) &&
(user.documentID != userId) &&
("male" == user['gender'])
) {
loc(user['Location'].latitude, user['Location'].longitude).then((result){
if(result==true){
setState(() {
showId1.add(user.documentID) ;
showName1.add(user['name']) ; }
});
}
}
});
return ;
}

The call to getDocuments() determines how many documents are read. So in your case:
Firestore.instance.collection('user').getDocuments()
This means that all documents from the user collection are read. If those documents are not in the local cache of the device yet, Firestore will have to read them on the server and return them to the client.
So say your cache is empty: that'd mean that you're charged a read for each document in the user collection, and the total bandwidth that is used for downloading those documents.

Related

Wait for Cloud Function to be finished

Is there any way to wait for a Cloud Function, that was triggered by a Firestore document write, to finish?
Context:
My app has groups. Owners can invite other users to a group via an invite code. Users can write themselves as member of a group if they have the right invite code. They do this by writing the groups/{groupId}/members/{userId} document that contains their profile info.
To make reading more efficient, this info is copied to array members in the groups/{groupId} document by a Cloud Function.
The Cloud Function that does that is triggered by the document write. It is usually finished after a couple of seconds, but there's no predictable execution time and it might take a bit longer if it is a cold start.
After the user has joined the group, I forward them to the groups view in my app which reads the group document. In order for the view to render correctly, the membership info needs to be available. So I would like to forward AFTER the Cloud Function has finished.
I found no way to track the execution of a Cloud Function that was triggered by a Firestore document write.
A fellow developer recommended to just poll the groups/{groupId} document until the info is written and then proceed but this doesn't seem like a clean solution to me.
Any ideas how this could be done better?
Is it possible to get a promise that resolves after the Cloud Function has finished? Is there a way to combine a Firestore document write and a Cloud Function execution into one transaction?
Thanks for the hints, I came up with the following ways to deal with the problem. The approach depends on if/when the user is allowed to read a document:
A) User is member and leaves the group > at the start of the transaction they are allowed to read the group > the moment they can't read anymore confirms that the membership was successfully revoked:
async function leaveGroup (groupId) {
await deleteDoc(doc(db, 'groups', groupId, 'members', auth.currentUser.uid))
// Cloud Function removes the membership info
// from the group doc...
await new Promise((resolve) => {
const unsubscribeFromSnapshot = onSnapshot(
doc(db, 'groups', groupId),
() => { }, // success callback
() => { // error callback
// membership info is not in the group anymore
// > user can't read the doc anymore
// > transaction was successful
// read access was revoked > transaction was successful:
unsubscribeFromSnapshot()
resolve()
}
)
})
}
B) User is not a member and wants to join the group > at the start of the transaction they are allowed to read the group > the moment they can read the group confirms that the membership was successfully confirmed (this is a simplified version that does not check the invite code):
async function joinGroup (groupId) {
try {
await setDoc(
doc(db, 'groups', groupId, 'members', auth.currentUser.uid),
{
userId: auth.currentUser.uid,
userDisplayName: auth.currentUser.displayName
}
)
// Cloud Function adds the membership
// information to the group doc ...
await new Promise((resolve) => {
let maxRetries = 10
const interval = setInterval(async () => {
try {
const docSnap = await getDoc(doc(db, 'groups', groupId))
if (docSnap.data().members.includes(auth.currentUser.uid)) {
// membership info is in the group doc
// > transaction was successful
clearInterval(interval)
resolve()
}
} catch (error) {
if (maxRetries < 1) {
clearInterval(interval)
}
}
maxRetries--
}, 2000)
})
}
Note: I went with polling here, but similar to what #samthecodingman suggested, another solution could be that the Cloud Function confirms the membership by writing back to the members document (which the user can always read) and you listen to snapshot changes on this document.
C) Most straightforward way: someone else (the group owner) removes a member from the group > they have read access through the whole transaction > directly listen to snapshot changes:
async function endMembership (groupId, userId) {
await deleteDoc(doc(db, 'groups', groupId, 'members', userId))
// Cloud Function removes the membership info
// from the group doc...
await new Promise((resolve) => {
const unsubscribe = onSnapshot(doc(db, 'groups', groupId), (doc) => {
if (!doc.data().members.includes(userId)) {
// membership info is not in the group doc anymore
// > transaction was successful
unsubscribe()
resolve()
}
})
})
}
In any case you should do proper error handling that covers other causes. I left them out to demonstrate how to use the error handlers when waiting for gaining/loosing read access.

Cannot check for "Long Named" Document existence in Firebase?

I've been working with Flutter and Firebase lately, I'm facing a confusing problem today and would like some help.
I have a Collection in Firebase Cloud Firestore that contains Documents which their names are quite long.
The photo below shows them:
I wrote a function in Dart to check the existence of a specific document, it's also pretty simple:
Future<String> checkForExistance(String mnemonic) async {
final String seedHex = bip39.mnemonicToSeedHex(mnemonic);
final snapShot = await FirebaseFirestore.instance
.collection('wallets')
.doc(seedHex)
.get();
if (!snapShot.exists) {
if (kDebugMode) {
print('SnapShot not exist');
}
return 'NO_IDEA';
} else {
if (kDebugMode) {
print('Snapshot Exist');
}
return seedHex;
}
}
The return result is always 'SnapShot not exist'.
The return is always 'SnapShot not exist' even though I've made sure I'm checking for the existence of exactly what I need.
Even when I hard coded the name of one of those documents, the result is 'SnapShot not exist' (this is what confuses me).
Future<String> checkForExistance(String mnemonic) async {
final String seedHex = bip39.mnemonicToSeedHex(mnemonic);
final snapShot = await FirebaseFirestore.instance
.collection('wallets')
.doc(**'hard coded'**)
.get();
if (!snapShot.exists) {
if (kDebugMode) {
print('SnapShot not exist');
}
return 'NO_IDEA';
} else {
if (kDebugMode) {
print('Snapshot Exist');
}
return seedHex;
}
}
So I tried creating a document with a shorter name (the 's' at the end of the image) and hard coded it, the results returned as expected which is 'Snapshot Exist'.
Is it because my documents name is too long that Firebase search is not working as expected?
Thanks for everyone's help.
From what I can see in the screenshot the document names are shown in italic, which means that there is no document with that name and the console merely shows it because there are subcollections under that path.
That also explains why you can't load those documents through the API, there are no documents to load.
You will either already have to know of the existence of these paths, or you can load the documents from the subcollections with a collection group query and then determine the parent path(s) from that.

Problem with saving data in Transaction in Firebase Firestore for Flutter

I have a problem with transactions in my web application created in Flutter. For database I use Firebase Firestore where I save documents via transaction.
Dependency:
cloud_firestore: 3.1.1
StudentGroup is my main document. It has 4 stages and each of them has 3-5 tasks. (Everything is in 1 document). I have to store game timer, so every 10 seconds I make an request to save time for current stage. (Every stage has different timer). I have a problem with saving task, because "Sometimes" when 2 requests are made in the same time, then I get some weird state manipulation.
Task is updated and "isFinished" is set to true
Timer is updated to correct value (with this update somehow previous task update is lost, "isFinished" is set to false
This is how I save task.
Future<Result> saveTask({required String sessionId, required String studentGroupId,
required Task task}) async {
print("trying to save task <$task>.");
try {
return await _firebaseFirestore.runTransaction((transaction) async {
final studentGroupRef = _getStudentGroupDocumentReference(
sessionId: sessionId,
studentGroupId: studentGroupId
);
final sessionGroupDoc = await studentGroupRef.get();
if (!sessionGroupDoc.exists) {
return Result.error("student group not exists");
}
final sessionGroup = StudentGroup.fromSnapshot(sessionGroupDoc);
sessionGroup.game.saveTask(task);
transaction.set(studentGroupRef, sessionGroup.toJson());
})
.then((value) => taskFunction(true))
.catchError((error) => taskFunction(false));
} catch (error) {
return Result.error("Error couldn't save task");
}
}
This is how I save my time
Future<Result> updateTaskTimer({required String sessionId,
required String studentGroupId, required Duration duration}) async {
print("trying to update first task timer");
try {
return await _firebaseFirestore.runTransaction((transaction) async {
final studentGroupRef = _getStudentGroupDocumentReference(
sessionId: sessionId,
studentGroupId: studentGroupId
);
final sessionGroupDoc = await studentGroupRef.get();
if (!sessionGroupDoc.exists) {
return Result.error("student group not exists");
}
final sessionGroup = StudentGroup.fromSnapshot(sessionGroupDoc);
switch (sessionGroup.game.gameStage) {
case GameStage.First:
sessionGroup.game.stages.first.duration = duration.inSeconds;
break;
case GameStage.Second:
sessionGroup.game.stages[1].duration = duration.inSeconds;
break;
case GameStage.Third:
sessionGroup.game.stages[2].duration = duration.inSeconds;
break;
case GameStage.Fourth:
sessionGroup.game.stages[3].duration = duration.inSeconds;
break;
case GameStage.Fifth:
sessionGroup.game.stages[4].duration = duration.inSeconds;
break;
}
transaction.set(
studentGroupRef,
sessionGroup.toJson(),
SetOptions(merge: true)
);
print("Did I finish task 4? ${sessionGroup.game.stages.first.tasks[3].isFinished}");
})
.then((value) => timerFunction(true))
.catchError((error) => timerFunction(false));
} catch (error) {
return Result.error("Error couldn't update task timer");
}
}
timerFunction and taskFunction print some messages in console and return Result.error or Result.success (for now it returns bool)
I don't know If I am doing something wrong with Firebase Firestore Transaction. I would like to have atomic operations for reading and writing data.
Transactions ensure atomicity - which means that if the transaction succeeds then all the reads and writes occur in a non-overlapping way with other transactions. This prevents the type of problem you are describing.
But this doesn't work if you spread your reads and writes over multiple transactions. In particular, it looks to me like you are writing a task which was obtained from outside the transaction. Instead, you should use ids or similar to track which documents you need to update, then do a read and a write inside the transaction.Alternatively firebase also provides Batched Writes, which specify the specific properties you want to update. These will ensure that any other properties are not changed.For batch writes example you can refer to the link

Is there a way to prevent having to await an async method returning a stream?

We currently have a method that returns a Future<Stream<Position>> just because internally we have to await the result of a method returning a Future before we can call another method that returns the Stream<Position> which we are actually interested in. Here is the code:
Future<Stream<Position>> getPositionStream(
[LocationOptions locationOptions = const LocationOptions()]) async {
PermissionStatus permission = await _getLocationPermission();
if (permission == PermissionStatus.granted) {
if (_onPositionChanged == null) {
_onPositionChanged = _eventChannel
.receiveBroadcastStream(
Codec.encodeLocationOptions(locationOptions))
.map<Position>(
(element) => Position._fromMap(element.cast<String, double>()));
}
return _onPositionChanged;
} else {
_handleInvalidPermissions(permission);
}
return null;
}
So what happens here is:
We await the _getLocationPermission() method so that we can test if the user grants us permission to access to the location services on their device (Android or iOS);
If the user grants us permission we return a Stream<Position> which will update every time the device registers a location change.
I have the feeling we can also handle this without doing an await and returning a Future. Something along the lines of:
Manually create and return an instance of the Stream<Position> class;
Handle the logic of checking the permissions and calling the _eventChannel.receiveBroadcastStream in the then() method of the Future<PermissionStatus> returned from the _getLocationPermission() method (so we don't have to await it);
Copy the events send on the stream from the _eventChannel.receiveBroadcastStream onto the earlier created (and returned) stream.
Somehow this seems to be possible, but also includes some overhead in managing the stream and make sure it closes and is cleaned up correctly during the live cycle of the plugin or when the user unsubscribes pass through the events to the _eventChannel etc.
So I guess the question would be, what would be the best way to approach this situation?
You can write the code as an async* function, which will return a Stream and still allows await in the body:
Stream<Position> getPositionStream(
[LocationOptions locationOptions = const LocationOptions()]) async* {
PermissionStatus permission = await _getLocationPermission();
if (permission == PermissionStatus.granted) {
if (_onPositionChanged == null) {
_onPositionChanged = _eventChannel
.receiveBroadcastStream(
Codec.encodeLocationOptions(locationOptions))
.map<Position>(
(element) => Position._fromMap(element.cast<String, double>()));
}
yield* _onPositionChanged;
} else {
_handleInvalidPermissions(permission);
}
}
Alternatively, if you are using a non-async function, you can also use StreamCompleter from package:async.
It allows you to return a Stream now, even if you only get the real stream later. When that happens, you "complete" the StreamCompleter with the real stream, and the original stream will behave as if it was the real stream.

Flutter - Getting data from firestore in a loop

So basically I have a collection a User and within each user there is a subcollection for the pending friend request that the user have, something like that:
/users/[auto-id]/friend_requests/[auto-id]/{user: ref to another user}
But one user can obviously have multiple requests at the same time and I have an hard time to get the data correctly.
What I'm correctly trying to do is to get a list of user that are in the subcollection "friend_requests":
_loadFriendRequests() async {
try {
this._users.addAll(await _dataService.fetchFriend());
} catch (e, stackTrace) {
printException(e, stackTrace, "Error loading friends");
}
}
And in dataService:
Future<List<User>> fetchFriend() async {
final querySnapshot =
await _currentUserDoc.reference.collection("friend_requests").getDocuments();
return await Future.wait(querySnapshot.documents.map((doc) async {
final user = await doc["user"].get();
User(
id: user["id"],
name: user["name"],
);
}).toList(growable: false));
}
This is just the last thing that I tried but I tried in so many ways, with Future.wait() too, with/without async/await, etc
I just can't seem to make it work...
You're missing a return statement after final user = await doc["user"].get();
This could become a lot easier if you use Streams or fancy rxdart

Resources