Search by key, order by value - firebase

Here is a sample of my Firebase data:
I need to be able to search userFavorites for a given user (here, afaapy...) and return the results ordered by the values (timestamps) to get all the user's favorites in order of the date added to the database.
I can search by key as follows, and retrieve all favorites for the given user:
databaseRef.child("userFavorites").queryOrderedByKey().queryEqual(toValue: user.uid).observe(...)
But these favorties are ordered by their keys. If I try to order by value as follows, I get "Cannot use multiple queryOrderedBy calls!":
databaseRef.child("userFavorites").queryOrderedByKey().queryEqual(toValue: user.uid).queryOrderedByValue().observe(...)
How can I retrieve the favorites for a given user sorted by their value?
Second question: is there an easier way to retrieve data in the order it was added to the database?

You can't order the same ref multiple times as documented here
When you use a order or a filter method, it returns a Query Interface. See it as a filtered reference containing only a subset of the original data. It means that
databaseRef.child("userFavorites").orderByKey().equalTo(user.uid)
will not return userFavorite/${user.uid} but userFavorite filtered to show only the user.uid entry. You can see it by doing
databaseRef.child("userFavorites").orderByKey().equalTo(user.uid).ref.key
That should return 'userFavorites'
In your case, I see two options:
Keep going with orderByKey().equalTo() and sort the results yourself
Or use directly child() to get the user, then sort via Firebase (and don't forget to use the Firebase's snapshot.forEach to be sure you get the data in the query order):
databaseRef.child(`userFavorites/${user.uid}`).orderByValue().once('value', (snapshot) => {
if (snapshot.exists()) {
snapshot.forEach((child) => {
console.log(`${child.key}: ${child.val()}`)
})
}
})

Related

Firestore : Update only a key-val pair

We have a firestore collection, where each document has only one field names.
names contains a map.
Now if, I just want to update a single key value pair in that map, is there a way, other than:
await FirebaseFirestore.instance
.collection(namesCollectionName)
.doc(docId)
.update(savedNames.toJson())
.whenComplete(() => {developer.log("Update saved names success")})
.catchError((error) {
developer.log("Update saved names failed: $error");
throw Exception("Update saved names failed: $error");
});
This code updates the entire map.
I am not sure if there is a way to update just a key value pair. I felt it would be more efficient!
Firestore processes each entries in the map you pass as a set operation on that field.
If you want to update nested fields use . notation to mark that field. So say you store the full name for each user keyed on the Stack Overflow ID as a map under the names field, that'd be:
users
.doc('ABC123')
.update({'names.13076945': 'Nithin Sai'})
If your names field is an array and you want to add a certain name if it doesn't exist in there yet, that'd be an array-union, which looks like this:
users
.doc('ABC123')
.update({'names': FieldValue.arrayUnion(['Nithin Sai'])});

Flutter getting a specific field from firebase and assigning it to list

I am trying to query data from firestore and assign it to a List.
My users collection is as follows
List<String> emailList = ['Select email', 'email1', 'email2'];
I am trying to populate the above emaillist with the emails of persons such that the list is populated by emails of the persons where the groupId is equal to groupId of current user.
I tried querying by Firebase.instance.collection.where but it gets an error saying it cant be assigned to a List.
Any idea on how to do that?
It sounds like the issue is how you are adding to the list. You have to loop through the snapshot query and for each document, add to the list.
Firestore.instance.collection('users').getDocuments().then((snapshot) => {
snapshot.documents.forEach((doc) {
emailList.add(doc.data['email']);
})
});
Hope this helps!

How to access an object into another object in a query database from firebase functions?

I am trying to access to a object into another object in my firebase database, i have a structure like this:
I want to get all the objects that have the email that i send by parameters, i am using .child to access to the childs into my object but i am not success with the query, this is my code
$ ref_db.child("/groups").child("members").orderByChild("email").equalTo(email).once("value", (snapshot)=>{
console.log(snapshot.val());
});
The snapshot.val() always is undefined.
could you help me with the query?
One efficient way to get "all the groups that have that email inside the members object" would be to denormalize you data and have another "main node" in your database where you store all "members" (i.e. their email) and the "groups" they belong to.
This means that each time you add a "member" node under a "group" (including its email) you will also add the group as a child of the member email, in this other "main node".
More concretely, here is how would be the database structure:
Your current structure:
- groups
- -LB9o....
...
- members
- -LB9qbd....
-email: xxxx#zzz.com
- -LBA7R....
-email: yyyyy#aaaa.com
And the extra structure:
- groupsByMembers
- xxxxxx#zzzcom
- Grupo1: true
- yyyyy#aaaacom
- Grupo1: true
- Grupo2: true
- bbbcccc#dddcom
- Grupo6: true
- Grupo8: true
Note that in the "extra structure" the dots within an email address are removed, since you cannot include a point in a node id. You will have to remove them accordingly when writing and querying.
This way you can easily query for the list of groups a member is belonging to, as shown below. Without the need to loop several times over several items. This dernomalization technique is quite classic in NoSQL databases.
const mailToSearchFor = xxxx.xx#zzz.com;
const ref = database.ref('/groupsByMembers/' + mailToSearchFor.replace(/\./g, ''));
ref.once('value', snapshot => {
const val = snapshot.val();
for (let key in val) {
if (val.hasOwnProperty(key)) {
console.log(key);
}
}
});
In order to write to the two database nodes simultaneously, use the update method as explained here https://firebase.google.com/docs/database/web/read-and-write#update_specific_fields
This is because you have a random key before members, you need to go through the path and not skip a node, to be able to access the values:
ref_db.child("groups").child("-LB9oWcnE0wXx8PbH4D").child("members").orderByChild("email").equalTo(email).once("value", (snapshot)=>{
console.log(snapshot.val());
});

How to use startAt() in Firebase query?

Let's suppose above firebase database schema.
What I want to is retrieve messages which after "15039996197" timestamp. Each of message object has a createdAt property.
How can I get messages only after specific timestamp? In this case, last two messages are what I want to retrieve.
I tried firebaseb.database().ref(`/rooms/$roomKey/messages`).startAt('15039996197') but failed. It return 0. How can I do this?
The case with your query is that it's expecting that the message node should have a number value to start with, in that case you want a child node with the name createdAt. So in that case you must specify that you want to order by createdAt, thus you need to do this
firebase.database().ref(`/rooms/$roomKey/messages`).orderByChild('createdAt').startAt('15039996197').on(//code);
This way it'll return all nodes inside message that has a child named createdAt an it starts at 15039996197. Ordering your query may be a little bad for performance, for that i sugest taking a look at .indexOn rule.
For more information take a look here.
Hope this helps.
Firebase Data Retrieval works node by node.
So whatever data you want to get, the entire node is traversed.
So in your case to get any message your complexity would be O(number of messages).
You would want to restructure the way you are storing the data and put createdAt in Node instead of Child.
If your database structure looks like that, you can use:
firebase.database()
.ref(`/rooms/$roomKey/messages`)
.orderByChild('createdAt')
.startAt('15039996197').on('value', snapshot => { /* your code here */ });
But if you work with a lot of data entries, you can also name the item key with the timestamp, instead of storing the timestamp in your data. This saves a little data on your database.
firebase.database().ref(`${rooms}/${roomKey}/${timestamp}`).set("value");
To retrieve the data in that case instead of orderByChild('createdAt'), you'll use orderByKey(). Because firebase keys are of the string type, you need to make shure to parse the timestamp in the .startAt() to a string.
It will then look something like this:
firebase.database()
.ref(`/rooms/$roomKey/messages`)
.orderByKey()
.startAt(`${15039996197}`).on('value', snapshot => { /* your code here */ });
You can do something like that:
firebase.database().ref('/rooms/$roomKey/messages')
.orderByChild('createdAt')
.startAt('150399')
.endAt('1503999\uf8ff')
.on('value', function (snapshot) {
var key = snapshot.key,
data = snapshot.val();
console.log(key + ': ' + JSON.stringify(data))
});
Be sure to set endAt().

Grabbing Keys with Firebase

I'm currently creating an app using ionic2 and firebase for a class and I've run into an issue.
I"m not posting code because I don't have anything relevant enough to the question.
In my database I have a 'group' that people can create. I need the 'group' to be able to store a list of users. The way I'm currently doing it is that users will click an addGroup button, and the currentUser will be added to the list of users in that group simply by typing in the name of the group. (yeah i know that's going to need a password or something similar later, people obviously shouldn't just be able to join by group name)
*Names of groups will all be different so I should be able to query the database with that and get a single reference point.
The problem is that I don't know how to properly query the database and get the key value of the group object I want.
I keep looking things up and cant find a simple way to get the key of an object. I feel like this is a dumb question but, I'm unfamiliar with typescript, firebase, and ionic2 so I'm pretty lost in everything.
Answer for the question in comment.
you can query the database like this
this.group_val:any="group1"
let group=db.list('/groups', {
query: {
orderByChild: GroupName, //here specify the name of field in groups
equalTo: this.group_val //here specify the value u want to search
}
});
group.subscribe(grp=> {
//you can use the grup object here
})
First, define your database structure. The below is probably best:
groups
<first group> (Firebase-generated ID)
name: "Cat lovers"
users:
user1ID: true
user2ID: true
<second group>
...
I assume that when the user clicks on the group he wants to join, you know the group ID. For instance, it could be the value of an option in a select.
Get the list of groups:
groups$ = this.angularFireDatabase.list('groups');
To populate a list of groups into a select:
<select>
<option *ngFor="let group of groups$ | async" [value]="group.$key">
{{group.name}}
</option>
</select>
To create a new group:
groups$.push({name: "Dog lovers"})
The select (drop-down) will update itself.
To add a user to a group:
addUserToGroup(groupId, userId) {
this.angularFireDatabase.list(`groups/${groupID}/users`).update(userID, true);
}
To find a group based on its name:
// Define the shape of a group for better type checking.
interface Group { name: string; users: {[userId: string]: boolean;}; }
// Keep local list of groups.
groups$: FirebaseListObservable<Group>;
groups: Group[];
// Keep an updated local copy of groups.
ngOnInit() {
this.angularFireDatabase.list('groups').subscribe(groups => this.groups = groups);
}
function findGroupByName(name) {
return this.groups.find(group => group.name === name);
}

Resources