Firebase load related data iOS - ios

What's the best way to load "related" data in swift?
Common setup, if I have a list of users all stored under uid node and contains a list of follows which stores uids, something like:
"users" : {
"abc123" : {
"email" : "test#test.com",
"follows" : {
"xyz789" : true
}
},
"xyz789" : { ... }
}
What's the most efficient way of loading in the data for all the users one user follows? Is it best to loop through each of the uid's with observeSingleEvent(of: .value)?
This is the solution I've come up with, but feels somewhat cumbersome:
func loadRelated(user: User, completion: #escaping (Bool, [UserObject]) -> ()) {
let ref = Database.database().reference(withPath: "users/" + user.uid + "/follows")
ref.observeSingleEvent(of: .value) { snapshot in
var uids = [String]()
for child in snapshot.children {
let userData = child as! DataSnapshot
uids.append(userData.key)
}
let userRef = Database.database().reference(withPath: "users")
var users = [UserObject]()
var count = 0
uids.forEach { uid in
userRef.child(uid).observeSingleEvent(of: .value) { snapshot in
let user: UserObject(from: snapshot)
users.append(user)
count += 1
if count == uids.count {
completion(true, users)
}
}
}
}
}
I don't really want to go down the denormalization path and store each users data under the top level user.

If you are decided on using Realtime Database, it is best practice to create another root node in your case called user-follows. You can create a follow at the path user-follows/$uid/$fid by setting the value to true, then on your app you would have to observeSingleEvent for each snapshot key ($fid) at user-follows/$uid.
To avoid having to observe each follow separately, instead of setting the value to true, you can just store the data you need about a user in user-follows/$uid. However, a user may change their username for example and so you would need to keep the data inside each user-follows up to date. You can utilise Firebase Cloud Functions to maintain the user-follows when a user changes their information.
Otherwise, I would suggest looking at Firebase Firestore, where some nesting is allowed.

If you know that your node at /users will always contain few users, you could try to get all the users at once with a observeSingleEvent(of:) at path /users. Then filter the users with the ones who are in ../follows.
This may pull more data but it might be faster (not sure) and will need less code to handle.
In fact your initial implementation is quite performant already. Just make sure to handle correctly failing of observeSingleEvent(of:) or the condition count == uids.count will never be fulfilled.
By the way storing each user under ../follows will just duplicate your data and will be hard to maintain updated. So yes avoid it.

Related

Swift Firebase - getting multiple uid's then adding those uid's to a child

I've been thinking about this but I thought I'd post a question to get some more thinking power behind this or to see if this is even possible. I am grabbing multiple uid's and then want to take these uid's and append them to a child in my database and then add further data to them. Since they are uid's I can't access them separately which would be a easy firebase "update values" call, so how could I take this list of uid's and then add them to a child so they are their own separate children and then add values to them? I am just thinking about how I would set this firebase call to say "add each one of these uid's as its own child".
How I am getting the uid's
func getEmployees() {
let employees = Database.database().reference().child("Businesses").child(self.otherUser?["uid"] as! String).child("registered_employees").observe(.childAdded, with: { (snapshot) in
if snapshot.exists() {
let employess = snapshot.childSnapshot(forPath: "uid")
print(employess)
} else {
print("didnt call right values")
}
})
}
sample of data I would add to uid child
let userMessageRef = Database.database().reference().child("user-messages").child(fromID).child(toID)
let messageId = childRef.key
userMessageRef.updateChildValues([messageId!:1])
The code right above ^^^^ I would want the uid's to be in "toID" and then adding the "messageId" to those uid's
I don't know how I could even do each uid separately in the call because of the inability to extract each one and then set the data.
I think I understand so let me try an answer with an example. How we obtain the uid's we want to write is not outlined in the question so let try this:
Suppose we have a users node that stores our users and if they like pizza
users
uid_0 //the Firebase generated uid
name: "Richie"
likes_pizza: true
uid_1
name: "Marion"
likes_pizza: false
uid_2
name: "Fonzi"
likes_pizza: true
uid_3
name: "Howard"
likes_pizza: false
what we want to do it to get the users that like pizza, craft a new node and store each of the uid's as a parent and then a child of their name.
let usersRef = self.ref.child("users")
let pizzaQueryRef = usersRef.queryOrdered(byChild: "likes_pizza").queryEqual(toValue: true)
pizzaQueryRef.observeSingleEvent(of: .value, with: { snapshot in
guard let allUsers = snapshot.children.allObjects as? [DataSnapshot] else {return}
for user in allUsers {
let key = user.key
let name = user.childSnapshot(forPath: "name").value as! String
let pizzaRef = self.ref.child("pizza_lovers")
let aPizzaLoverRefUid = pizzaRef.child(key).child("their_name")
aPizzaLoverRefUid.setValue(name)
}
})
so this code queries for all users that like pizza (which enables us to access their uid's), and then (per the question) append them to a child in the database and then add further data to them
and then want to take these uid's and append them to a child in my
database and then add further data to them
the result is
pizza_lovers
uid_0
their_name: "Richie"
uid_2
their_name: "Fonzi"
Let me know if I misunderstood the question and I will update.

querying firebase efficiently

I want to search my user base from each users name value. From what I've seen online people often return all users then filter them in a table view but that doesn't seem practical nor viable. My thought was to query data and return an exponentially smaller array of values but I am having trouble using the query methods provided.
How do I query a specific aspect of my database?
How do I structure my code so that it's viable; not loading in EVERY user, something like 10 max at a time.
Any suggestions, resources, and links are greatly appreciated.
EDIT:
I did some researching and it looks like Firebase comes with some built in querying methods... So far this is what I'm attempting to test it out with the code below to print out users starting with I, but I can't get it to print any users in the console
ref.queryOrderedByKey().queryStarting(atValue: "I").queryEnding(atValue: "I\u{f8ff}")
.observe(.childAdded, with: { snapshot in
print(snapshot.key)
})
There are a number of solutions and often times loading ALL of the user data is too much data.
Here's a typical users node
users
uid_0
name: "Jean Luc"
uid_1
name: "Will"
uid_2
name: "Geordi"
One option is to iterate through each user node, one at a time, to retrieve the user name. This avoids an enormous data set entirely. We'll use the .childAdded event to load each and store in an array
let usersRef = self.ref.child("users")
var userNamesArray = [String]()
usersRef.observe(.childAdded, with: { snapshot in
let userDict = snapshot.value as! [String: Any]
let name = userDict["name"] as! String
userNamesArray.append(name)
})
A second option is to store the user name in an entirely different node, which significantly reduces the 'clutter' as the rest of the data remains in the main users node
user_names
uid_0: "Jean Luc"
uid_1: "Will"
uid_2: "Geordi"
As you can see with this structure, even with thousands of names it's just text with a very small footprint.
Another option is to load X number of users at a time using .startingAt and .endingAt and iterate over the returned users to get each name. In this case we want all users starting with A and ending with M... Sorry Worf.
let usersRef = self.ref.child("users")
var userNamesArray = [String]()
let nameQuery = usersRef.queryOrdered(byChild: "name")
.queryStarting(atValue: "A")
.queryEnding(atValue: "M\u{f8ff}")
nameQuery.observe(.value, with: { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let userDict = snap.value as! [String: Any]
let name = userDict["name"] as! String
userNamesArray.append(name)
}
})
The last example started with users names starting with A and ended with user names ending with M + a very high unicode character, which makes it inclusive for all names starting with M
The \uf8ff character used in the query above is a very high code point
in the Unicode range. Because it is after most regular characters in
Unicode, the query matches all values that start with queryString.

Getting a specific child value from nodes only in which user's UID is present

I am working on a group chat application, and I'm running into trouble trying to make a specific query to Firebase DB.
I need to get all of the profile pictures of users in a room. But only the rooms that the current user is a part of.
private func observeRooms() {
let databaseRef = FIRDatabase.database().reference()
let groupRef = databaseRef.child("groups")
guard let uid = FIRAuth.auth()?.currentUser?.uid else {return}
let queryRef = groupRef.queryOrdered(byChild: "participants/\(uid)").queryEqual(toValue: nil)
queryRef.observe(.childAdded, with: { snapshot in
let roomDict = snapshot.value as! [String: AnyObject]
print("roomDict: \(roomDict)")
let id = snapshot.key
let avatar = roomDict["profilePicture"] as! String
})
}
Obviously setting queryEqual(toValue: nil) returns all the rooms that the current user is NOT a part of - the opposite of what I need. I know the inverse will be to check queryEqual(toValue: true), but the : true is not showing up in the database.
This is how I add a node to "groups" in Firebase:
// Start group node for new room
let groupRef: FIRDatabaseReference = FIRDatabase.database().reference().child("groups")
let roomID = newRoomRef.key
let groupRoomRef = groupRef.child(roomID)
let groupDict = ["roomName" : roomName, "participants": [uid : true]] as [String : Any]
groupRoomRef.setValue(groupDict)
And this is how the data structure ends up:
"groups" : {
"-KkGzZ7frbnXmImt0JpV" : {
"participants" : {
"tzfHgGKWLEPzPU9GvkO4XE1QKy53" : {
"gender" : "male",
"handle" : "Testing",
"name" : "Test User",
"profilePicture" : "https://graph.facebook.com/*removed*/picture?type=large&return_ssl_resources=1",
"status" : "F8B016"
}
},
"roomName" : "Test Room"
},
How can I properly the profilePicture values for all users in a room, only for the groups that the current user's UID is a participant of?
You're on the right track. When using queryEqual your query needs to match the value actually present in the database. Unfortunately that gets complicated when the values you're querying against are complex objects. In fact I'm not sure the function will work even if you did match the entire object value. The issue here is that you're trying to look up objects by the presence of a child key, instead of actually sorting by or matching their values. That doesn't quite fit the use case of any of the orderBy methods.
There are a few ways to work around this, ordered by effort.
First, you could take advantage of the sort order to get only those who have some object with the name of the UID. Since Firebase sorts nulls first, then primitives, and finally objects, you should be able to order by child and start at true (or any primitive) to get all object instances with that key (assuming, of course, you're not storing UID: true anywhere).
groupRef.queryOrdered(byChild: "participants/\(uid)").queryStartingAtValue(true)
Second, duplicate the UID as a key inside the object with the value true so that you can sort on it directly. Notice the extra step in the query.
"groups" : {
"-KkGzZ7frbnXmImt0JpV" : {
"participants" : {
"tzfHgGKWLEPzPU9GvkO4XE1QKy53" : {
"tzfHgGKWLEPzPU9GvkO4XE1QKy53" : true,
...
groupRef.queryOrdered(byChild: "participants/\(uid)/\(uid)").queryEqual(toValue: true)
Finally, and this is what I recommend, reconsider your data structure. You're nesting a lot of data inside this collection of collections that is going to make your queries and updates unwieldy. Firebase tends to work better when you denormalize your data, preferring flat/shallow structures with some duplication of simple data. In your case, instead of storing the user's profile data inside of their group membership, you might have a collection of groups and another collection of users, with each group containing only a list of UIDs to its members and vice versa. The example in the documentation is a very similar case to yours involving membership in chat conversations. At that point your UIDs (and group IDs, if you're querying on users instead) will have a value of true so you can again use a simple value query.
"groups" : {
"-KkGzZ7frbnXmImt0JpV" : {
"participants" : {
"tzfHgGKWLEPzPU9GvkO4XE1QKy53" : true
...
"users" : {
"tzfHgGKWLEPzPU9GvkO4XE1QKy53" : {
"groups" : {
"-KkGzZ7frbnXmImt0JpV" : true
...
groupRef.queryOrdered(byChild: "participants/\(uid)").queryEqual(toValue: true)

Pulling Data from Firebase into Array

I was recently told to structure my Firebase differently. Before I was putting everything related to a particular user under his or her tree. I was told however to flatten it and create his or her nodes separately and then to just link that node into that users tree when you need to.
So my tree looks like this
root
card
*card autoID*
nickname: "foo"
type: "bar"
user
*user uid*
card
*card autoID*: true
I am going to add more to the card as the user progresses through the app, and if I understand how I am supposed to structure the data I will be adding it to the the card node since that card is linked to the user.
My question is how do I pull data from Firebase then into say an array or a dictionary? If it was all in one tree I would do something like this
let ref = FIRDatabase.database().reference()
let user = FIRAuth.auth()?.currentUser
let userCard = ref.child((user?.uid)!).child("card")
But since that card under the user is only a reference how do I then go to the real place where the card is...the part that has the nickname and type?
Edit
So with some help from other SO posts, the documentation, and a friend I have the code 90% working.
What I am able to do is
1) find all of the card autoID under the user node that is associated to the user and store those strings into an array # 1
2) I am able to query all of the card autoID under the node card and then find the ones that match what is in array # 1 and store them in array # 2 (the rest are ignored)
3) **Here is where I am stuck. If I am inside of the .observe then I can do what I want with the array like printing its contents. HOWEVER, if I call print outside of the .observe I get nothing...
here is my code
func pullCurrentUserCardInfo() {
let userCardsRef = ref.child("users").child((user?.uid)!).child("cards")
userCardsRef.observeSingleEvent(of: .value, with: {(snapshot) in
if let snapDict = snapshot.value as? [String: AnyObject] {
for each in snapDict {
self.usersCardRefArray.append(each.key)
self.count = Int(snapshot.childrenCount)
}
}
})
self.ref.child("cards").observe(.value, with: { (snapshot) in
if snapshot.hasChildren() {
for item in snapshot.value as! [String: AnyObject] {
for test in self.usersCardRefArray {
if test == item.key {
self.allCurrentUsersCards.append(item.key)
}
}
}
} else {
print("no children")
}
})
}
if I were to say the following inside of the function but outside of the .observe ....}) then it doesn't do anything.....
for item in allCurrentUsersCards {
print(item)
}
Am I missing something small somewhere or is this something to do with firebase?
I think there's an unneeded level of complexity here. You do not need to store (in this use case at least) a separate card for each user. There's a 1-1 relationship between user and card so just storing the card data for each user within the user node would be the best answer.
However, to answer the question directly, here's how to do it. We going to slightly alter the Firebase structure:
root
cards
*user uid* <- CHANGE
nickname: "foo"
type: "bar"
users
user uid: true <- CHANGE
Since user uid's are always unique and created for you, leverage them when working with users. So in this case just store the user uid's in the user node and that same uid in the cards node.
Create a User Class and an array to store them in. This would typically be done right inside a viewController for example
class ViewController: UIViewController {
class UserClass {
var uid = ""
var nickname = ""
var type = ""
}
var usersArray = [UserClass]()
Then, craft a Firebase observer to populate the usersArray, getting each card for each user
//iterate over all of the users, get the user and its card data
let usersRef = ref.child("users")
usersRef.observeSingleEvent(of: .value, with: { snapshot in
for snap in snapshot.children { //iterate over all users
let userSnap = snapshot as! FIRDataSnapshot
let userKey = userSnap.key //the uid of each user
//now that we have the uid, get it's card data
let thisUserCardRef = cardsRef.child("uid")
thisUserCardRef.observeSingleEvent(of: .value, with: { userSnap in
let userCardSnap = userSnap as! FIRDataSnapshot
let userCardDict = userCardSnap.value as! [String:AnyObject]
let nickname = userCardDict["nickname"]
let type = userCardDict["type"]
let aUser = UserClass()
aUser.userKey = userKey
aUser.nickname = nickname
aUser.type = type
self.usersArray.append(aUser)
//In general, this is where the tableView is refreshed
// because the user data and card data is valid at this point
//usersTableView.reload data /
})
}
})
The key here is to remember that Firebase is asynchronous and that code is way faster than the internet. So this high level example will fail most of the time
func getData() {
loadDataFromFirebase()
print("data loaded, do something with it") //<- executes before the prior line completes
}
func loadDataFromFirebase() {
someRef.observeEvent...
print("data is now valid inside observe closure")
}
This will usually result in
data loaded, do something with it
data is now valid inside observe closure
Which is opposite of what is wanted. Code executes faster than the internet so the asynchronous observe closure will occur after the data loaded... is printed. Only reference and work with firebase data inside a closure and use the closure to pace your app.
If you notice in the first example code provided - we only work with the data once it's been returned from Firebase.
Also note that we completely eliminated queries! Queries are 'heavy' by comparison to observe events and since we are leveraging the uid of each user, it's path will be known, hence the change from a node created with childByAutoId to using the uid.

Firebase + swift retrieving user data

I just tried to retrieve data from firebase for my project. For example, display the facebook user name in UILabel. I store the facebook user data like this
then retrieve the data using :
let ref = Firebase(url:"https://<app-name>.firebaseio.com/users/facebook:10207213459687665/name")
ref.observeEventType(.Value, withBlock: { snapshot in
self.facebookUserName.text = snapshot.value as! String
})
It works perfectly but it is pretty stupid by retrieving user name in a specific path because that could be different facebook user login.
I'm thinking like check the user is logged in and display their name or checking the currentUser or any smarter way to do this?
I am not sure how to do that.
There are 100 different ways to do this; here's a couple
users
user_id_0
facebook_id: facebook:10207213459687665
name: Nicholas
user_id_1
facebook_id: facebook:12346578912345689
name: Frank
in the above, you would query for the facebook_id you want, which will return the node and all of the child nodes (name, address, etc). The user_id_x is a Firebase auto-generated node name (guaranteed to be distinct)
ref.queryOrderedByChild("facebook_id").queryEqualToValue("facebook:12346578912345689")
.observeSingleEventOfType(.ChildAdded, withBlock: { snapshot in
print(snapshot.value) //prints the facebook_id and Frank
})
Another option is to use your same data structure and observe that node to load the data. Keep in mind that the facebook id is the KEY of the node, not the value - .value is key:value pairs.
let ref = Firebase(url:"https://<app-name>.firebaseio.com/users")
let thisUser = ref.childByAppendingPath("facebook:12346578912345689")
ref.observeSingleEventOfType(.ChildAdded, withBlock: { snapshot in
//snapshot will contain all of the child nodes
let userName = snapshot.value.objectForKey("name")
self.facebookUserName.text = userName
})
If you just care about the users name, you could simplify your structure by using the facebook id as the key and the value would be the user name:
users
fb_10207213459687665: Nicholas
fb_12346578912345689: Frank
and retrieve with the above observe code except you would again use the .value property as in your initial question.
In this case the .value property is a string as it's the only value (there are no child nodes as in the structure you posted, which could cause issues as it could be a series a key:value pairs which would crash)
Queries add some overhead so the observe is more efficient.
I have found an answer in http://www.appcoda.com/firebase/
When we are trying to fetch the data from current user add this code below to store the uid(when creating user account)
NSUserDefaults.standardUserDefaults().setValue(result ["uid"], forKey: "uid")
assign the variable:
var CURRENT_USER_REF: Firebase {
let userID = NSUserDefaults.standardUserDefaults().valueForKey("uid") as! String
let currentUser = Firebase(url: "\(BASE_REF)").childByAppendingPath("users").childByAppendingPath(userID)
return currentUser!
}
implement code for function:
<reference>.observeEventType(FEventType.Value, withBlock: { snapshot in
let currentUser = snapshot.value.objectForKey("username") as! String
<--your code-->
print("Username: \(currentUser)")
self.currentUsername = currentUser
}, withCancelBlock: { error in
print(error.description)
})
}
Then we could obsolete the ref in a direct path.
I had the same problem. To fix it, I created a variable that automatically references the user that's logged in:
class ViewController: UIViewController {
let UsersRef = Firebase(url:"https://<app-name>.firebaseio.com/users")
Then, under the #IBOutlet that you're trying to get the user data to show up in, write:
let LoggedInUser = UsersRef.childByAppendingPath("\(UsersRef.authData.uid)")
When you run the code, "UsersRef.authData.uid" is automatically replaced by the specific ID of the user currently logged in. This should create a direct path to the database info of the logged in user without having to manually enter the user's ID in the reference.
To retrieve data of a facebook user we can use the currentUser property of FirebaseAuth which returns a FIRUser which contains all the information about you user. After reading all the user data you can store it anywhere but for the sake of this example I have used UserDefaults.
import Firebase
func getUserInformation()
{
let user = Auth.auth().currentUser
if let user = user {
UserDefaults.standard.setValue(user.displayName!, forKey: "Username")
let uid = user.uid
UserDefaults.standard.set(uid, forKey: "user_ID")
let url = "http://graph.facebook.com/\(uid)/picture?type=square"
UserDefaults.standard.set(url, forKey: "ImageData")
}
}

Resources