Flutter - Getting data from firestore in a loop - firebase

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

Related

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.

Query from Cloud Firestore using UID - No Return No Error

I'm quite new to flutter and dart. I have started to build an app to help understand the languages better. So far I can authenticate users, log them in, log them out and perform actions based on the status of the user etc.... but I have been struggling for hours on something that I imagine is quite a basic concept. I have tried countless code examples from SO and Flutter Dev but nothing seems to work.
I want to query my Cloud Firestore, searching for any documents that have the document name of 'uid' that matches the current logged in users user ID. This code brings me no errors in Android Studio, and the app runs fine and displays the 'page' but does not redirect or print anything.
As this is really just saying if it's empty do x but if it's not empty do y - even if the syntax was wrong (even though AndroidStuid says it's valid) I would still expect it to change the path to either 'Home' or 'Hometwo' and print one of the two statements, but it does nothing.
If I simply print 'user' it does this ok, and displays the current users ID - but seems to 'break' when querying the collection?
What am I doing wrong here?
void checkCurrentUser() async {
// final FirebaseFirestore database = FirebaseFirestore.instance;
final user = await _auth.currentUser.uid;
if (user == null) {
navigateToSubPageMyAccount(context);
} else {
FirebaseFirestore.instance
.collection('samplecollection')
.doc(user)
.get()
.then((DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
navigateToSubPageHome(context);
print('Document exists on the database: ${documentSnapshot.data()}');
} else {
navigateToSubPageHomeTwo(context);
print('Document does not exist');
}
});
}
}
Update:
I found the answer as per my comment below. If anyone else stumbles upon this thread, the reason none of this code was working is because I had commented it out in error from within initState during some other testing:
void initState() {
super.initState();
// checkCurrentUser();
}
...and simply removed the commenting so it was included when the app ran:
void initState() {
super.initState();
checkCurrentUser();
}

How do I make a firestore query to be used with both get() and valueChanges()?

I am using Angular 8 and have a form where a user can choose what he wants to query the database for and then click either of two buttons - one to view data in realtime on the website, and the other to download the data.
I thought I could make use of one function to make a query and then call different functions depending on what button the user clicked, using get() for the download and valueChanges() for the realtime data view. But when I try this, I get the following errors in the browser console. (This is with query as type any - if I specify the type as AngularFirestoreCollection I get errors regarding my type for the get() part in VSCode)
ERROR Error: "Uncaught (in promise): TypeError: this.query.get is not
a function
I can add that I previously had two completely separate (working) functions for downloading and viewing in realtime. And for downloading I used the below query. I gather this is actually a Firestore Query, whereas the "query" I'm trying to use in my updated code is an AngularFirestoreCollection. But is there a way I can make some kind of Query/Collection that will work for both get() and valueChanges()?
Old (working) query:
var query = this.afs.collection(collection).ref.where('module', 'in', array_part);
Trying a common function makeQuery():
onSubmit(value, buttonType): void {
if (buttonType=='realtime') {
this.getRealTimeData(value);
}
if (buttonType=='download') {
this.downloadCsv(value);
}
}
async downloadCsv(value) {
this.query = this.makeQuery(value);
this.dataForDownload = await this.getDataForDownload();
this.dataForDownload = JSON.stringify(this.dataForDownload['data']);
console.log('Data: ', this.dataForDownload);
var date = new Date();
var date_str = this.datePipe.transform(date, 'yyyy-MM-ddTHH-mm');
this.makeFileService.downloadFile(this.dataForDownload, 'OPdata-' + date_str);
}
getDataForDownload() {
return this.query.get()
.then(function (querySnapshot) {
var jsonStr = '{"data":[]}';
var dataObj = JSON.parse(jsonStr); //making object we can push to
querySnapshot.forEach(function (doc) {
JSON.stringify(doc.data()), ', id: ', doc.id);
dataObj['data'].push(doc.data());
});
return dataObj;
})
.catch(function (error) {
console.log("Error getting documents: ", error);
});
}
async getRealTimeData(value) {
this.query = await this.makeQuery(value);
this.data = this.query.valueChanges();
}
async makeQuery(value) {
var collection: string;
return this.query = this.afs.collection<DataItem>('CollectionName', ref => ref.where('datetime', '>=', '2020-01-15T09:51:00.000Z').orderBy('datetime', 'desc').limit(100));
}
The valueChanges() is a method used in angularfire to retrieve data from firestore, while the get() method is used to retrieve from firestore but using the vanilla javascript.
Mixing both methods will return an error as you have seen in your code. Therefore, since angularfire was created above the javascript firebase code, then you should be able to use valueChanges() to view data in realtime on the website, and to download the data.

Angular Firestore document update causing infinite loop in subscription

I understand the issue but can't figure out the workaround. I am querying a specific document to extract an array of token strings. I need to append a new token to the end of this string and then update the current document with this new token array.
To do this, I have subscribed to a query and within, I update that document. But of course, when you update the same object, the subscription runs again thus creating an infinite loop. I tried incorporating a take(1) pipe rxjs operator but that did not change anything. Any suggestions?
Here's my code:
this.afs.collection('users').doc(user.userUID).valueChanges().pipe(take(1)).subscribe((user: userModel) => {
const currentTokens: string[] = user.notifTokens ? user.notifTokens : [];
//token variable is provided outside this query
currentTokens.push(token);
//this next lines causes the subscription to trigger again
userRef.doc(user.userUID).update({notifTokens: currentTokens})
})
I would recommend you avoid using a subscription in this situation, for exactly this reason. I realize the Angularfire2 docs don't list this method, but the base Firebase package includes a .get() method... and while the AF2 docs don't mention the .get() method... the source code shows that it is supported.
Try something like:
this.afs.collection('users').doc(user.userUID).get().then( (user: userModel) => {
if (user.exists) {
console.log("Document data:", user.data());
// Do stuff with the info you get back here
const currentTokens: string[] = user.data().notifTokens ? user.data().notifTokens : [];
currentTokens.push(token);
userRef.doc(user.data().userUID).update({notifTokens: currentTokens})
} else {
// user.data() will be undefined in this case
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});

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.

Resources