How to get a group of objects from Firebase? - ios

In my React-Native app, I have an array of specific users whose values I want to pull from Firebase. What is the most efficient way to go about this? Currently I am looping through the array and making a new request for each (relevant code below):
const usersRef = new Firebase(`${ config.FIREBASE_ROOT }/users`)
for (var key in usersArray) {
var userRef = usersRef.child(key);
//do stuff here
}
However, I feel this isn't very efficient and it makes several requests to the database. Is there a way I can pass in the array and get those items from Firebase, all in one call? Thanks.
Firebase data structure:
{
"items" : [ {
"description" : "fuzzy socks",
"type" : "toy"
}, {
"description" : "bouncy ball",
"type" : "toy"
}, {
"description" : "scrabble",
"type" : "game"
}, {
"description" : "construction paper",
"type" : "crafts"
} ],
"users" : [ {
"itemList" : [ 1, 2, 3, 4 ],
"description" : "brown",
}, {
"itemList" : [ 5, 6, 7 ],
"description" : "green",
}, {
"itemList" : [ 8, 9, 10 ],
"description" : "blue",
}, {
"itemList" : [ 11, 12, 13 ],
"description" : "yellow",
} ]
}
Simplified use case: In one use case, I only want to get information about 2 of the users (out of all the users I have stored in Firebase--assume it's many more than just the 4 in the structure above). So, I have the array importantUsers:
var importantUsers = [0, 3]
Then, I want to send a request to Firebase that only queries the database for the values associated with these userID values (so somehow pass in the array to Firebase for a result). Return values would be something like this:
0: itemList: [1,2,3,4], description: brown
3: itemList: [11,12,13], description: yellow
My motivation for querying the database for multiple users at once (rather than creating a separate ref for user 0 and user 3) is to not have multiple calls made to Firebase. Is there any way to go about this?

So what you are after is an sql 'in' type query. Select in [0,2]. To select a number of users from a list.
The additional challenge in your question is that users you are interested in are random so you can't use .startAt and .endAt, and there is no other relation between the users.
Firebase does not have direct support for 'in', 'and' or 'or' kinds of query but there are a number of ways to make it happen.
How about this: flag the users you want and then with a single query, read them in.
First, you'll start with a typical Firebase /users node with the addition of a 'selected' child node (this can be omitted initially but I am showing it here as a placeholder)
users
uid_0
name: "Bud"
selected: false
uid_1
name: "Henry"
selected: false
uid_2
name: "Billy"
selected: false
Then, we need some random uid's, say uid_0 and uid_2 and store those in an array. Keep in mind that we would be using the Firebase generated uid but we'll use uid_0, uid_1 etc for simplicity.
With just two users, you could just observeSingleEventOfType on each of the two nodes, no big deal.
However, if we needed 100 random users or 1000, doing 1000 separate queries or observeSingleEvent's should be avoided. But, setValue is blisteringly fast (no returned data) so....
Get our users ref
let usersRef = myRootRef.ChildbyAppendingPath("users")
We know the path to each of the 100 users we want by iterating over the array to build those refs and set selected to true
for uid in uidArray {
let thisUserRef = usersRef.childByAppendingPath(uid)
let selectedRef = thisUserRef.childByAppendingPath("selected")
selectedRef.setValue(true)
}
Then, you can query for all users in the usersRef where the selected child = true.
Once you have them, to clean up, iterate over the returned users and set the selected to false or nil
The cool thing about this is that setValue can blast through 100 or 1000 users very quickly with no overhead, setting their selected child to true. Then a single query can return the values you want.
Seems almost reverse in logic to write out to then read back in but I am pretty sure it's considerably better than iterating over an array and generating 1000 queries or observers.
(Firebase folks can check me on that one)
One other thought is that if a user is say, clicking on other users in a list, you could set selected = true as they are clicking and then query for those when the user is done.

You could use bindAsArray or bindAsObject from ReactFire
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/items");
this.bindAsArray(ref, "items");
Then you could loop over that array/object as needed without extra queries

Related

Multiple handles in firebase

I'm building a chat app with rooms feature in iOS, and had built a Firebase data design like this:
"members" : {
"userId1" : {
"roomId1" : true
},
"userId2" : {
"roomId1" : true
}
}
"rooms" : {
"roomId1" : {
"lastMessage" : "Last message",
"timeStamp" : 1494483604,
//users in this room
"users" : {
"userId1" : true,
"usreId2" : true
}
}
}
So to show list of conversations of a user, firstly I observe single event of type value of path members/userId to get list of rooms that user take part in.
Then for each roomId, I observe rooms/roomId to get data to show on the UI.
The question is if a user takes part in a great deal of rooms, is observing changes for all of them best practice ?
For example, if I have 30 conversations from roomId1 to roomId30, I want to update the latest messages on the UI whenever changes happened, is observing 30 references makes sense ?
Thank you.
It's not best practice but it's a practice that works. However, based on your structure it would be simpler to generate a query on the rooms node for any users/userIdx: true.
That will add and observer to one node and notify the app of any changes to rooms the user is part of.
For example
Given a structure
rooms
room_0
room_name: "My Room"
users:
uid_0: true
uid_1: true
uid_2: true
room_1
room_name: "Romper Room"
users:
uid_0: true
uid_2: true
and some code to add an observer to watch for uid_1
let roomsRef = self.ref.child("rooms")
roomsRef.queryOrdered(byChild: "users/uid_1").queryEqual(toValue: true)
.observe(.childAdded, with: { (snapshot) in
let roomDict = snapshot.value as! [String: AnyObject]
let roomName = roomDict["room_name"] as! String
print(roomName)
})
When this code is first run, it will print out
My Room
because the user is part of My Room (room_0) and not part of room_1.
If you then add uid_1: true to room_1 it will print
Romper Room
You should use observe to update db changes, so you can update UI based on the observation.
Take a look at this document:
https://firebase.google.com/docs/database/ios/read-and-write

Inconsistent behavior for firebase friendslist

I'm currently trying to setup a friendslist relationship using Firebase and I'm experiencing some issues with the process of adding a friend for a user.
I currently have a schema like this:
users
-----
UID
->Name
->Friendslist
->UID of friend:true
userDisplays
------------
-Users Name:Users UID
addedFriends
------------
-UID of added user
->UID Of user that did the adding
My idea of adding a friend is using the name of the user and looking them up in the userDisplays tree, that lookup seems to work everytime. However, when I then try to read the uid value from the child in userDisplays it will sometimes return NULL, or will skip the adding entirely after it finds the uid value (by this I mean it will not add the uid to the adding user and not create a addedFriends child).
The same issue is when the adding user successfully adds the desired user and a addedFriends child is created linking the two, but again when the added user checks for recent addings and is given the option to 'add back' it will return NULL or skip the removal of the addedFriends child and not add the uid to the users list of friends.
I tried to copy the relationship example found in the docs, but I'm confused on where I am missing things, or if this is expected behavior?
Here is my structure after two users have been created. Before a friends list has been created.
{
"userDisplays" : {
"a" : "fed7738e-7dec-4ff3-9cc9-46dee3c189e6",
"t" : "c3b27833-d56b-40e9-b46f-c99128193f3d"
},
"users" : {
"c3b27833-d56b-40e9-b46f-c99128193f3d" : {
"displayName" : "t",
"email" : "test#test.com",
"provider" : "password"
},
"fed7738e-7dec-4ff3-9cc9-46dee3c189e6" : {
"displayName" : "a",
"email" : "test2#test.com",
"provider" : "password"
}
}
}
This is the swift code I'm using to perform the adding of a friend given a user name:
ref.userDisplayBaseSingleton.childByAppendingPath("/\(userName)").observeSingleEventOfType(.Value, withBlock: { snapshot in
if snapshot.value is NSNull{
print("null")
}else{
print(snapshot.value)
print("found val")
ref.userBaseSingleton.childByAppendingPath(snapshot.value as! String).observeSingleEventOfType(.Value, withBlock: { (snap) in
let friendData = [
snap.key:true
]
ref.userBaseSingleton.childByAppendingPath("\(currentUser!.id!)/friends").updateChildValues(friendData)
let currentAddData = [
snap.key:currentUser!.id!
]
let friendAddRef = ref.addBaseSingleton.childByAutoId()
friendAddRef.setValue(currentAddData)
})
}
})
Basically the issues I'm experiencing from this snippet is that sometimes the look up from userDisplays doesn't return a value, or if it does it doesn't add a addedFriends node. I've experienced a bug when retrying adding the username again, but it will actually generate a new uid of the users and then generate a new addedFriends child.
For example, if I was user t and I tried to add a a few kinds of behavior can happen.
userDisplay for a returns null
userDisplay returns a and returns a's uid but the addedFriends node isn't generated
a is properly added, and an addedFriends node is generated, however when user a is notified of the add and is given the option to add back t it seems to reproduce the same kind of errors.
I've noticed that after a period of time, the actual uid of t will update on the firebase and then the add will be successful. By this I mean that the initial uid that t is assigned will change and then I've noticed the code above will work. I'm also assuming the reason why its updated has to do with the errors thats happening, but I could be wrong.
Heres a snippet of the error happening
}
{
"addedFriends" : {
"-KH6pR5TV7vjVeADc7mc" : {
"22a3c5fa-e578-4ada-b3c1-355add80041e" : "098804cd-1ffa-4508-b353-6f6a4377661f"
}
},
"userDisplays" : {
"test" : "f0bb77a9-8156-4d44-ad74-6d6b01765802",
"test2" : "098804cd-1ffa-4508-b353-6f6a4377661f"
},
"users" : {
"098804cd-1ffa-4508-b353-6f6a4377661f" : {
"displayName" : "test2",
"email" : "test2#test.com",
"friends" : {
"22a3c5fa-e578-4ada-b3c1-355add80041e" : true
},
"provider" : "password"
},
"f0bb77a9-8156-4d44-ad74-6d6b01765802" : {
"displayName" : "test",
"email" : "test#test.com",
"provider" : "password"
}
}
}

Querying nested elements in Firebase

I am trying to make a query to a Firebase collection using swift.
My Collection is like this :
{
"networks" : {
"-KF9zQYGA1r_JiFam8gd" : {
"category" : "friends",
"members" : {
"facebook:10154023600052295" : true
},
"name" : "My friends",
"owner" : "facebook:10154023600052295",
"picture" : "https://my.img/img.png"
},
"-KF9zQYZX6p_ra34DTGh" : {
"category" : "friends",
"members" : {
"tototata" : true
},
"name" : "My friends2",
"owner" : "facebook:10154023600052295",
"picture" : "https://my.img/img.png"
}
}
and my query is like that :
let networksRef = ref.childByAppendingPath("networks")
.queryOrderedByChild("members")
.queryEqualToValue(true, childKey: uid)
Then I am populating a TableView using FirebaseUI.
the thing is that query doesn't return anything, I also tried using this kind of query :
let networksRef = ref.childByAppendingPath("networks")
.queryOrderedByChild("members")
.queryStartingAtValue(true, childKey: uid)
.queryEndingAtValue(true, childKey: uid)
And to be clear "uid" is the node name nested in the "members" node with true as value. (As on the picture)
I can't get all the nodes without filtering because of the amount of data to be downloaded.
I'm stuck on that for a day, and I'm going crazy.. :-)
If someone can help ?
The correct syntax would be:
let networksRef = ref.childByAppendingPath("networks")
.queryOrderedByChild("members/\(uid)")
.queryEqualToValue(true)
But note that you'll need an index for each uid for this to work efficiently:
{
"rules": {
"networks": {
".indexOn": ["facebook:10154023600052295", "tototata"]
}
}
}
Without these indexes, the Firebase client will download all data and do the filtering locally. The result will be the same, but it'll consume a lot of unneeded bandwidth.
A proper solution for this requires that you change your data structure around. In Firebase (and most other NoSQL databases) you often end up modeling the data in the way that your application wants to access it. Since you are looking for the networks that a user is part of, you should store the networks per user:
"networks_per_uid": {
"facebook:10154023600052295": {
"-KF9zQYGA1r_JiFam8gd": true
},
"tototata": {
"-KF9zQYZX6p_ra34DTGh": true
},
}
Adding this so-called index to your data structure is called denormalizing and is introduces in this blog post, the documentation on data structuring and this great article about NoSQL data modeling.

Update field of embedded documents on multiple Mongoid documents [duplicate]

This question already has answers here:
How to Update Multiple Array Elements in mongodb
(16 answers)
Closed 5 years ago.
I recently started using MongoDB and I have a question regarding updating arrays in a document.
I got structure like this:
{
"_id" : ObjectId(),
"post" : "",
"comments" : [
{
"user" : "test",
"avatar" : "/static/avatars/asd.jpg",
"text" : "....."
}
{
"user" : "test",
"avatar" : "/static/avatars/asd.jpg",
"text" : "....."
}
{
"user" : "test",
"avatar" : "/static/avatars/asd.jpg",
"text" : "....."
}
...
]
}
I'm trying to execute the following query:
update({"comments.user":"test"},{$set:{"comments.$.avatar": "new_avatar.jpg"}},false,true)
The problem is that it update all documents, but it update only the first array element in every document. Is there any way to update all array elements or I should try to do it manually?
Thanks.
You cannot modify multiple array elements in a single update operation. Thus, you'll have to repeat the update in order to migrate documents which need multiple array elements to be modified. You can do this by iterating through each document in the collection, repeatedly applying an update with $elemMatch until the document has all of its relevant comments replaced, e.g.:
db.collection.find().forEach( function(doc) {
do {
db.collection.update({_id: doc._id,
comments:{$elemMatch:{user:"test",
avatar:{$ne:"new_avatar.jpg"}}}},
{$set:{"comments.$.avatar":"new_avatar.jpg"}});
} while (db.getPrevError().n != 0);
})
Note that if efficiency of this operation is a requirement for your application, you should normalize your schema such that the location of the user's avatar is stored in a single document, rather than in every comment.
One solution could be creating a function to be used with a forEach and evaling it (so it runs quickly). Assuming your collection is "article", you could run the following:
var runUpdate = function(){
db.article.find({"comments.user":"test").forEach( function(article) {
for(var i in article.comments){
article.comments[i].avatar = 'new_avatar.jpg';
}
db.article.save(article);
});
};
db.eval(runUpdate);
If you know the indexes you want to update you can do this with no problems like this:
var update = { $set: {} };
for (var i = 0; i < indexesToUpdate.length; ++i) {
update.$set[`comments.${indexesToUpdate[i]}. avatar`] = "new_avatar.jpg";
}
Comments.update({ "comments.user":"test" }, update, function(error) {
// ...
});
be aware that must of the IDE's will not accept the syntax but you can ignore it.
It seems like you can do this:
db.yourCollection.update({"comments.user":"test"},{$set:{"comments.0.avatar": "new_avatar.jpg", "comments.1.avatar": "new_avatar.jpg", etc...})
So if you have a small known number of array elements, this might be a little easier to do. If you want something like "comments.*.avatar" - not sure how to do that. It is probably not that good that you have so much data duplication tho..

SAPUI5 - complex model binding

I have this json model:
model/data.json
{
"orders" : [
{
"header" : { "id" : "00001", "description" : "This is the first order" },
"items" : [
{ "name" : "Red Book","id" : "XXYYZZ" },
{ "name" : "Yellow Book", "id" : "AACCXX" },
{ "name" : "Black Book", "id" : "UUEEAA" },
]
},
{
// another order with header + items
},
.....
]
}
and I'm assigning it onInit to the view, like this:
var model = new sap.ui.model.json.JSONModel("model/data.json");
sap.ui.getCore().setModel(reqModel);
I'm trying to display a list of orders in the first view (showing the id), like this:
var list = new sap.m.List({
id: "mainList",
items: []
});
var items = new sap.m.ActionListItem({
text : "{id}",
press : [ //click handler, onclick load the order details page ]
});
list.bindItems("/orders", items);
.... // add list to the page etc etc
What I cannot do, is connect each order to its header->id.. I tried
text: "/header/{id}"
text: "{/header/id}"
in the items declaration, and
list.bindItems("/orders/header", items)
in the list binding, but none of them works.. The id value is not displayed, even though a "blank" list item is shown..
Any idea? What am I doing wrong?
Thank you
The solution was one of those I tried (but I don't know why it didn't work at that time)
text: "{/header/id}"
The ListItem acts as a Template for a list/array of objects. That's why you bind it against an array structure in your data:
list.bindItems("/orders", itemTemplate)
That makes bindings of the ListItem relative to /orders and therefore your item should look like this without leading '/' (absolute paths would look like this /orders/0/header/id asf.):
var itemTemplate = new sap.m.ActionListItem({
text : "{header/id}",
press : [ //click handler, onclick load the order details page ]
});
Not quite sure how you made it work the way you have shown... May be it's not as picky as I thought.
Btw: For whatever reason the ResourceModel builds an exception of that syntax. You can always omit the leading '/' when dealing with ResourceModels (probably because they do not allow nested structures).
BR
Chris
Cannot add comments yet, therefore an answer to you solved Problem, that could answer the initial problem. (And inform People using that example in any way)
In the current code listing you use the variable "reqModel" to set the model, but the variable with the model in it is named "model" in the line before. Maybe that was the first reason why both of your examles would not work?
Perhaps this error was cleared on rewriting some passages while testing.
greetings! -nx

Resources