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"
}
}
}
Related
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
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
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.
I've installed SwiftMongoDB using CocoaPods. Added 2 documents in the collection. When I try to retrieve them using .find() method It only returns one document.
func all() -> [MongoDocument]{
let UsersCollection = MongoCollection(name: "users")
mongodb?.mongodb.registerCollection(UsersCollection)
for (index,value) in UsersCollection.find().successValue!.enumerate(){
debugPrint(value)
}
// UsersCollection.find().successValue!.count
// returns 1.
return UsersCollection.find().successValue!
}
My collection looks like:
{ "_id" : ObjectId("56bb29ca42b9b41900000000"), "address" : "US", "given" : "User", "birthDate" : "1985-08-01", "family" : "UserFam", "identifier" : "E3826", "date" : "10.2.2016 at 14:15:6" }{ "_id" : ObjectId("56bb29ca42b9b41900000000"), "address" : "US", "given" : "User2", "birthDate" : "1985-08-01", "family" : "UserFam2", "identifier" : "E3826", "date" : "10.2.2016 at 14:15:6" }
Is there another way of getting all the documents? Am I doing something wrong?
I have never used SwiftMongoDB but I have used swift for iOS development and mongoDB with Java. First of all, here is your first object's and your second object's ids together:
1st: 56bb29ca42b9b41900000000
2nd: 56bb29ca42b9b41900000000
As you can see they are the same. So I strongly believe your issue arises from that. Have you defined that property as primary key?
This is a bug. Maybe the package is in it's early versions ....
When using the IOS API, I'm making a call to connect via the ApigeeDataClient connectEntities method. I pass in the type "users", then the user's uuid, then connectionType "likes", with the connectee type of "songs" and the song's uuid.
Example:
ApigeeClientResponse *response = [_dataClient connectEntities:#"users" connectorID:_apigeeUser.uuid connectionType:#"likes" connecteeType:#"songs" connecteeID:song.uuid];
When I make the connection, it says successful, but when I look at the data on the server, it seems to save the connection incorrectly. For example, for the song, I see:
connecting :likes :/songs/b523a6aa-bb39-11e4-a2bb-35673af856e9/connecting/likes
It looks like the song's uuid isn't in the connecting path.
The same is true for the connection related to the user. It's the user's uuid that seems to be connected to the same user. The uuid is that of the song's uuid, not the user's. When I make the call to getEntityConnections, like so:
ApigeeClientResponse *response = [_dataClient getEntityConnections:#"songs" connectorID:_apigeeUser.uuid connectionType:#"likes" query:nil];
It returns an error, saying "expected song, but got user's uuid.
Entity c831e1c4-2e6e-11e4-94ce-299efa8c6fd5 is not the expected type, expected song, found user"
In looking in Apigee itself, in the data section, I see the following snippet:
"connections": {
"likes": "/users/c831e1c4-2e6e-11e4-94ce-299efa8c6fd5/likes"
}
The song's uuid is missing. Even when I try to update the JSON directly on the server, basically adding the song's uuid to the end, it says it's saved, but it removes the song's uuid.
Even just using the curl method to make a connection doesn't work. For example:
curl -X POST http://api.usergrid.com/peterdj/sandbox/users/bc2fc82a-bfa3-11e4-a994-b19963f1779d/likes/c37f1eaa-bfa3-11e4-9141-97b3510c98e6
When I make that call, I get this
{"action":"post",
"application":"0baaf590-2c1b-11e4-9bb5-11cb139f1620",
"params":{
},
"path":"/users/bc2fc82a-bfa3-11e4-a994-b19963f1779d/likes",
"uri":"https://api.usergrid.com/peterdj/sandbox/users/bc2fc82a-bfa3-11e4-a994-b19963f1779d/likes",
"entities":[
{
"uuid":"c37f1eaa-bfa3-11e4-9141-97b3510c98e6",
"type":"song",
"name":"WingSpan",
"created":1425167080842,
"modified":1425167080842,
"bpm":"124",
"code":"WingSpan",
"genre":"Progressive House",
"metadata":{
"connecting":{
"likes":"/users/bc2fc82a-bfa3-11e4-a994-b19963f1779d/likes/c37f1eaa-bfa3-11e4-9141-97b3510c98e6/connecting/likes"
},
"path":"/users/bc2fc82a-bfa3-11e4-a994-b19963f1779d/likes/c37f1eaa-bfa3-11e4-9141-97b3510c98e6"
},
"title":"Wing Span"
}
],
"timestamp":1425246006718,
"duration":78,
"organization":"peterdj",
"applicationName":"sandbox"
}
Notice that the resulting connecting path seems correct when it's returned, but when do another GET curl, as so:
curl http://api.usergrid.com/peterdj/sandbox/users/bc2fc82a-bfa3-11e4-a994-b19963f1779d
The song's uuid isn't there:
{
"action" : "get",
"application" : "0baaf590-2c1b-11e4-9bb5-11cb139f1620",
"params" : { },
"path" : "/users",
"uri" : "https://api.usergrid.com/peterdj/sandbox/users",
"entities" : [ {
"uuid" : "bc2fc82a-bfa3-11e4-a994-b19963f1779d",
"type" : "user",
"name" : "peter",
"created" : 1425167068578,
"modified" : 1425167495412,
"username" : "peterdj",
"email" : "asdf#adf.com",
"activated" : true,
"picture" :"",
"metadata" : {
"path" : "/users/bc2fc82a-bfa3-11e4-a994-b19963f1779d",
"sets" : {
"rolenames" : "/users/bc2fc82a-bfa3-11e4-a994-b19963f1779d/roles",
"permissions" : "/users/bc2fc82a-bfa3-11e4-a994-b19963f1779d/permissions"
},
"connections" : {
"likes" : "/users/bc2fc82a-bfa3-11e4-a994-b19963f1779d/likes"
},
"collections" : {
"activities" : "/users/bc2fc82a-bfa3-11e4-a994-b19963f1779d/activities",
"devices" : "/users/bc2fc82a-bfa3-11e4-a994-b19963f1779d/devices",
"feed" : "/users/bc2fc82a-bfa3-11e4-a994-b19963f1779d/feed",
"groups" : "/users/bc2fc82a-bfa3-11e4-a994-b19963f1779d/groups",
"roles" : "/users/bc2fc82a-bfa3-11e4-a994-b19963f1779d/roles",
"following" : "/users/bc2fc82a-bfa3-11e4-a994-b19963f1779d/following",
"followers" : "/users/bc2fc82a-bfa3-11e4-a994-b19963f1779d/followers"
}
}
} ],
"timestamp" : 1425311662762,
"duration" : 12,
"organization" : "peterdj",
"applicationName" : "sandbox"
}
Is this a bug with the entity connections with Apigee/Usergrid or am I doing something wrong?
Thanks
Well, turns out, thanks to the comments by #remus, I've figured it out.
In this call:
ApigeeClientResponse *response = [_dataClient getEntityConnections:#"songs" connectorID:_apigeeUser.uuid connectionType:#"likes" query:nil];
The connection needs to be "users", not "songs". Works now. Thanks #remus