Best way to structure a friend list in Firebase - ios

I am developing an iOS app using Swift 3 and Firebase.
In this app I am creating a friend list with this structure:
Friends
User_id
Friend_user_id_1
Friend_user_id_2
I want to get a list of friends for a specific user and this is possible but since I only store ids of friends I am missing Name and Email.
My idea is to do a second query for each user in the friend list to get their personal data. The option to this is to store the Name and Email along the friend_id but that feels cumbersome since the friend might change their Name or Email.
Friends
User_id
Friend_user_id_1
Name
Email
Is it ok making a second query to get user details on each loop or is that a performance killer?
Do I have to save the Name and Email in list too?
Update
This is how I solve it right now
func setupContent() {
let user = FIRAuth.auth()?.currentUser
if user != nil {
DataService.dataService.FRIEND_REF.child((user?.uid)!).observe(.value, with: { snapshot in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshots {
DataService.dataService.USER_REF.child(snap.key).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? Dictionary<String, AnyObject>
let friend = Friend(key: snap.key, dictionary: value!)
self.friends.insert(friend, at: 0)
self.tableView.reloadData()
}) { (error) in
print(error.localizedDescription)
}
}
}
})
}
}
Thankful for all help before I start coding.

Related

How to store Firebase data to access easily and fast in Swift

I am working on my user profile page on my app and I need to retrieve data from Firebase and display the data on my user profile page. I have managed to retrieve the data successfully from the database but the problem is that every time I go to the user profile page, it takes a little time to access the information in the database, so as soon as you go to the user profile page, the page will be empty. How can I avoid this?
Put another way, is there a way to access the data and store before going to the user profile page and then displaying the data that is stored? Here is my code:
// Setup the name label
func setupNameLabel() {
// Access the database and get the current user's name
Database.database().reference().child("Users").child(userID!).child("Name").observeSingleEvent(of: .value) { (snapshot) in
guard let name = snapshot.value as? String else { return }
self.nameLabel.text = name
}
view.addSubview(nameLabel)
}
// Setup the username label
func setupUsernameLabel() {
// Access the database and get the current user's username
Database.database().reference().child("Users").child(userID!).child("Username").observeSingleEvent(of: .value) { (snapshot) in
guard let username = snapshot.value as? String else { return }
self.usernameLabel.text = username
}
view.addSubview(usernameLabel)
}
// Setup the email label
func setupEmailLabel() {
// Access the database and get the current user's email
Database.database().reference().child("Users").child(userID!).child("Email").observeSingleEvent(of: .value) { (snapshot) in
guard let email = snapshot.value as? String else { return }
self.emailLabel.text = email
}
view.addSubview(emailLabel)
}
Try To make a Model and then using Single Method and Get Data with Completion
func getData(forUserID: String, completion: #escaping (Model) -> Swift.Void, error: #escaping (Bool) -> Void) {
}
Check models of Salada cocoapod, please.
https://github.com/1amageek/Salada
Especially the models like Relation, Disposer, Set, Array, File are very beautiful.

Not sure if I should be using a real time database

Currently having trouble displaying all highscores from my firebase realtime database. At the moment I'm only displaying "live" scores.
I am currently making a score board, where the top 50 scores and their usernames are displayed in a table. This works fine. However I am unsure as to whether I should be using a Real-Time database.
You see, when I do a fresh build on my physical device and create a new user, the user is saved into the Firebase database and when I achieve a new score and push to display highscore users, my new user is rightfully there with their score.
If I log out (but am still within the app) and register with a new user, and then play again (with the new user), and then I click to see the highscores leaderboard again I can now see both of the users I have created, with their scores. GREAT!
I can check the Authentication part of firebase, and sure enough, my users are in there with their email and passwords etc. And I can check their highscores on the Real-Time database. All is well.
However, when I delete the app from my device or even push it into the background, and then do a fresh build, or re-open the app. The authentication details (password, username, email) are still there, but when I open the Real-Time database, there is no data there.
Consequently, when I click to see the highscores leaderboard, there is nothing to show. Is this because I should not be using a realtime database, is it only displaying “live/recent data” and doesn't actually store the data I want permanently. Should I be using SQL or something else?
This is the code to add my highscore to my database......
func addingScoreToFB() {
let currentUser = Auth.auth().currentUser
let highScore = UserDefaults.standard.integer(forKey: highScoreIdentifier)
let recentScore = UserDefaults.standard.integer(forKey: recentScoreIdentifier)
if let userID = currentUser?.uid {
let userDB = Database.database().reference().child("Users").child(userID)
if recentScore > highScore {
let highSDictionary = ["highScore":self.recentScores.last!]
userDB.updateChildValues(highSDictionary as Any as! [AnyHashable : Any], withCompletionBlock: { (err, ref) in // Adding Score
if let err = err {
print(err)
}
})
}
}
}
This is my code to display the scores.....
func retrieveUserData() {
let postsRef = self.ref.child("Users")
let query = postsRef.queryOrdered(byChild: "highScore").queryLimited(toLast: 50)
query.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let dict = snap.value as! [String: Any]
let name = dict["username"] as! String
let score = dict["highScore"] as! Int
let aScore = ScoreClass(withName: name, andScore: score)
self.scoresArray.insert(aScore, at: 0)
}
self.topScoresTableView.reloadData()
})
}
In summary I would like to be able to see the top 50 scores of every user in the database of all time, but I am currently only seeing the recent ones on a new build.

Swift Showing last sent message in chat using firebase

I am trying to show my last sent message on my home screen of the chat messenger, from storyboard it looks like :
and the code I used are:
func getAllMsg() {
self.users = []
let fromId = Auth.auth().currentUser!.uid
let ref = Database.database().reference().child("privateMessages").child(fromId)
ref.observeSingleEvent(of: .value) { (snapshot) in
for snap in snapshot.children.allObjects as! [DataSnapshot] {
// retrieving receiver's ID
let chatUserID = snap.key
let ref2 = Database.database().reference().child("users").child(chatUserID)
// to retrieve message ID
ref2.observeSingleEvent(of: .value, with: { (snapshot2) in
let newUser = User(dictionary: snapshot2.value as! [String: AnyObject])
// to get the last message
ref.child(chatUserID).queryLimited(toLast: 1).observeSingleEvent(of: .value, with: { (snapshot3) in
let value = snapshot3.children
while let rest = value.nextObject() as? DataSnapshot {
newUser.lastMessage = (rest.value as! [String: AnyObject])["textMessages"] as? String
self.users.append(newUser)
DispatchQueue.main.async {
self.tableView.reloadData()
}
break
}
})
})
}
}
}
I have done some changes to my database and the above codes works, but after i changed the way i do my database, it doesnt work anymore
previously, my firebase looks like
now my firebase looks like:
i made a chatRoomId by using send and receiver ID. however I am now not able to show my last sent message which was supposed to show on my homescreen. Any ways I can go about this? Or the way I fetched my database is wrong?
Your query is wrong according to your db structure. Also don't perform lastMessage query inside the block of other query because this is totally independent query and not related with any. Below piece of code will work for you.
let ref = kFirDefaultDatabase.reference().child("yourChatRoomId").queryOrdered(byChild: "fromId").queryEqual(toValue: "0YfqnPIOYFYKb8cYZMHnSYti62i2").queryLimited(toLast: 1)
ref.observeSingleEvent(of: DataEventType.value) { (snapshot) in
if snapshot.exists() {
print(snapshot.value)
}
}
This will fetch the last message sent by fromId for the requested chatRoomId. For more detail have a look at Firebase Queries doc.
And if you want to do this in table for all users like in WhatsApp or other chatting application then you will need to make an extra table LastMessages and save last message information here corresponding to each chatRoomId or if possible save this detail somewhere you can fetch with the tableData so that you don't need to query for each chatRoom in a loop.
You can do some better stuff to make it faster. Use CoreData or Sqlite and save/update lastMessage information into local db whenever you send or received any message, where chatRoomId will be a primary key and first get the information from local db and show in the table immediately and mean while you can fetch the data from server and update your local db and refresh the table.
EDIT: For comment to get the last message regardless I send to recipient or recipient send to me.
Remove orderBy query and just use limitToLast. See below code:
let ref = kFirDefaultDatabase.reference().child("yourChatRoomId").queryLimited(toLast: 1)
ref.observeSingleEvent(of: DataEventType.value) { (snapshot) in
if snapshot.exists() {
print(snapshot.value)
}
}
You need to set up rooms differently.
If you you have 3 people in a room, what will the RoomID be ? If someone leaves the room how will you know what the room history is ?

Firebase query using a list of ids (iOS)

I have an NSArray containing multiple ids. Is there a way in Firebase where I can get all the object with the ids in the array?
I am building a restaurant rating app which uses GeoFire to retrieve nearby restaurants. My problem is that GeoFire only returns a list of ids of restaurant that are nearby. Is there any way i can query for all the object with the ids?
No, you can't do a batch query like that in Firebase.
You will need to loop over your restaurant IDs and query each one using observeSingleEvent. For instance:
let restaurantIDs: NSArray = ...
let db = FIRDatabase.database().reference()
for id in restaurantIDs as! [String] {
db.child("Restaurants").child(id).observeSingleEvent(of: .value) {
(snapshot) in
let restaurant = snapshot.value as! [String: Any]
// Process restaurant...
}
}
If you are worried about performance, Firebase might be able to group all these observeSingleEvent calls and send them as a batch to the server, which may answer your original question after all ;-)
I know that this answer is considered accepted but I have had really good success using promise kit with the method frank posted with his javascript link Speed up fetching posts for my social network app by using query instead of observing a single event repeatedly and just wanted to share the swift version
So I have a list of users ids that are attached to a post like this:
also these methods are in my post class where I have access to the post id from firebase
// this gets the list of ids for the users to fetch ["userid1", "userid2"....]
func getParticipantsIds() -> Promise<[String]> {
return Promise { response in
let participants = ref?.child(self.key!).child("people")
participants?.observeSingleEvent(of: .value, with: { (snapshot) in
guard let snapshotIds = snapshot.value as? [String] else {
response.reject(FirebaseError.noData)
return
}
response.fulfill(snapshotIds)
})
}
}
// this is the individual query to fetch the userid
private func getUserById(id:String) -> Promise<UserData> {
return Promise { response in
let userById = dbRef?.child("users").child(id)
userById?.observeSingleEvent(of: .value, with: { (snapshot) in
guard let value = snapshot.value else {
response.reject(FirebaseError.noData)
return
}
do {
let userData = try FirebaseDecoder().decode(UserData.self, from: value)
response.fulfill(userData)
} catch let error {
response.reject(error)
}
})
}
}
// this is the where the magic happens
func getPostUsers(compeltion: #escaping (_ users:[UserData], _ error:Error?) -> ()){
getParticipantsIds().thenMap { (id) in
return self.getUserById(id: id)
}.done { (users) in
compeltion(users, nil)
}.catch({ error in
compeltion([], error)
})
}

Unable to persist 'posts' reference for 'users' in Firebase

I'm creating a social app and a common aspect of it is keeping tabs of the posts that users make. I'm using Firebase as the backend for this and here's my schema
SocialApp
posts
-KGsBG5TPYBtzRivZnbf
users
facebook:10154108240254134
cachedProfile
displayName: "Karthik Kannan"
imageURL: "https://scontent.xx.fbcdn.net/v/t1.0-1/p100x100..."
posts
-KGsBG5TPYBtzRivZnbf: true
provider: "facebook"
When i go ahead and add posts from one device(or simulator) this works as planned but when i delete the app from a device and reinstall it, the posts relationship in the users dictionary disappears. I would like it to persist across devices and keep the relationship intact so if a user decides to use another iDevice to login his posts don't get orphaned.
Here's the code I wrote for adding a post to Firebase and how I currently keep track of the user.
func postToFirebase(imgUrl: String) {
var gift: Dictionary<String, AnyObject> = [
"giftName": giftName.text!,
"giftDescription":giftDescription.text!,
"giftAvailableAt": giftAvailableAt.text!,
"giftPrice": Int(giftPrice.text!)!,
"username": NSUserDefaults.standardUserDefaults().valueForKey("uid")!,
"giftImage":imgUrl,
]
let firebasePost = DataService.ds.REF_POSTS.childByAutoId()
firebasePost.setValue(gift)
firebasePost.observeSingleEventOfType(.Value, withBlock: { snapshot in
if let postID = snapshot.key {
DataService.ds.REF_USER_CURRENT.childByAppendingPath("posts").childByAppendingPath(postID).setValue(true)
}
})
}
This is how I set current users:
var REF_USER_CURRENT:Firebase {
if let uid = NSUserDefaults.standardUserDefaults().valueForKey("uid") as? String {
let user = Firebase(url:"\(BASE_URL)").childByAppendingPath("users").childByAppendingPath(did){
return user!
} else {
return Firebase()
}
}
func createFirebaseUser(uid:String, user:Dictionary<String, AnyObject>) {
REF_USERS.childByAppendingPath(uid).setValue(user)
}
And this is the code in my login controller.
NSUserDefaults.standardUserDefaults().setValue(authData.uid, forKey: "uid")
let user = ["provider":authData.provider!,"displayName":authData.providerData["displayName"]!,"cachedProfile":authData.providerData["cachedUserProfile"]!, "imageURL":authData.providerData["profileImageURL"]!]
DataService.ds.createFirebaseUser(authData.uid, user: user)
self.performSegueWithIdentifier("loggedIn", sender: nil)
This is all the code I've written. I suspect it's something to do with NSUserDefaults. Any help would be greatly appreciated by this beginner.
Check your code. I found a typo here:
var REF_USER_CURRENT:Firebase {
if let uid = NSUserDefaults.standardUserDefaults().valueForKey("uid") as? String {
let user = Firebase(url:"\(BASE_URL)").childByAppendingPath("users").childByAppendingPath(did){
return user!
} else {
return Firebase()
}
}
Instead of "...childByAppendingPath(did)"
You might want to change "did" to "uid"
Other than that, I have not tested out the rest of your code.
Seems like You have watched the Mark Price course, for making this app.
So have I :)

Resources