Retrieving data with .Value and .ChildAdded - ios

I have a list of data to retrieve from Firebase, using Swift.
I tried to get the data using .Value and Firebase returns a dictionary with the IDs of each item and for each ID the info associated.
The endpoint I am calling is /ideas.
let ideasRef = firebase.childByAppendingPath(IdeaStructure.PATH_IDEAS)
ideasRef.observeEventOfType(.Value, withBlock: { snapshot in
print(snapshot.value)
}, withCancelBlock: { error in
print(error.description)
})
In order to optimize this, I changed with .ChildAdded. In this case I get only the single item without the ID associated.
Is it possible to get also the ID of each item using .ChildAdded?
If not, how can I save the ID generated by Firebase into each item? Currently I am saving each item in this way:
let ideasRef = firebase.childByAppendingPath(IdeaStructure.PATH_IDEAS).childByAutoId()
let idea = [
IdeaStructure.FIELD_MESSAGE: message,
IdeaStructure.FIELD_CREATOR_ID: userId,
IdeaStructure.FIELD_CREATION_DATE: NSDate().formattedISO8601
]
ideasRef.setValue(idea)

To get the key of the snapshot, access its key property:
let ideasRef = firebase.childByAppendingPath(IdeaStructure.PATH_IDEAS)
ideasRef.observeEventOfType(.Value, withBlock: { snapshot in
print(snapshot.key)
print(snapshot.value)
}, withCancelBlock: { error in
print(error.description)
})
This and many more topics are covered in Firebase's excellent programming guide for iOS.

Related

Query Firebase Database With Swift

I am trying to query an entry in my firebase realtime database with Swift to basically see if an entry exists with the owner = 33206. My current code is not returning what I need, how can I fix this query?
Here is my code:
var promoCodes: DatabaseReference {
return ref.child("PromoCodes")
}
func getIsAffiliate() {
promoCodes.child("owner").queryEqual(toValue: 33206).observeSingleEvent(of: .value) { snapshot in
print("SNAP HERE: \(snapshot)")
AffiliateService.isAffiliate = snapshot == nil
}
}
Output: SNAP HERE: Snap (owner) <null>
The snapshot passed to your code will never be nil. To check if there are matching nodes, you can check snapshot.exists() instead.

Firebase + iOS: Receiving stale data using observeSingleEvent without using isPersistence = true

I currently use observeSingleEvent to fetch data periodically in our game. It seems that the client is receiving stale data at times while using this method. From what I have read, I believe this should only happen if isPersistence = true, which is not the case. Is this still expected behavior? Shouldn't I receive fresh data each time I query? Thanks in advance.
EDIT: More detailed query:
for levelNumber in 1...numberOfLevels
{
ref.child(pathToLevelData + "/" + levelNumber).queryOrderedByValue().queryStarting(atValue:
highScore+1).observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children
{
let snap = child as! DataSnapshot
guard let value = snap.value as? Int else { return }
// Process value, but it is not always fresh data from Firebase
}
})
}

Firebase Child Added Is Called Every time a Child is Changed

I have a group messaging application that works fine until I want to change some of the basic group properties such as group title, image, etc. Before I show my code to display my conversations and update them I will show you some of my data structure.
When it comes to dealing with the displaying and editing of conversations I use two main nodes. An overall conversation node containing the conversation properties and a conversations node within my current user.
Here is what the conversation node in my current user looks like:
As you can see in the image above my user has a conversation node with a list of conversation ids. These conversation ids refer to a conversation node within my database. Here is a picture of the conversation node:
Just to review the problem. Basically when I update any of the conversation properties (title, image, members) it re calls my child added method which creates an error I will show later.
Here is my code to display the conversations:
func observeUserConversations() {
guard let uid = currentUserProperties.id else {
return
}
FIRDatabase.database().reference().child("users").child(uid).child("conversations").observe(.childAdded, with: { (snapshot) in
FIRDatabase.database().reference().child("conversations").child(snapshot.key).observe(.value, with: { (conversationSnapshot) in
if let conversation = Groups(snapshot: conversationSnapshot) {
conversation.groupId = conversationSnapshot.key
self.conversations.append(conversation)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
}, withCancel: nil)
}
Here is my code to update some of the conversation properties:
static func updateConversationProperties(conversationId: String, property: String, propertyValue: String) {
let updateConversationPropertyRef = FIRDatabase.database().reference().child("conversations").child(conversationId).child(property)
updateConversationPropertyRef.setValue(propertyValue)
ProgressHUD.showSuccess("Field Updated!")
}
Please note I have tried using update child values instead of set value and it still has the same bug.
To sum up whenever I update a conversation property the child added function is called and appends a duplicate version of the conversation to my conversation array.
I know this may be a bit confusing, so I have a video here showing the bug:
https://youtu.be/OhhnYzQRKi8
In the video above you will see that the same conversaiton is duplicated and added twice.
Any help would be appreciated!
UPDATE
So I changed my observers a bit to look like this:
FIRDatabase.database().reference().child("users").child(uid).child("conversations").observe(.childAdded, with: { (snapshot) in
FIRDatabase.database().reference().child("conversations").child(snapshot.key).observeSingleEvent(of: .value, with: { (conversationSnapshot) in
if let conversation = Groups(snapshot: conversationSnapshot) {
conversation.groupId = conversationSnapshot.key
self.conversations.append(conversation)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
}, withCancel: nil)
In the above code, everything works and no duplicates are made. However, now the conversations won't update in realtime. Instead they will display the old data and won't update to the newly changed data. Also if I add a conversation the new added conversaiton won't display.
Here is what I notice:
The way you had the code originally, the second listener was triggered any time a change is made to the value of /"conversations"/snapshot.key. And whenever this call was made, you were appending the conversationSnapshot to conversations array:
FIRDatabase.database().reference().child("users").child(uid).child("conversations").observe(.childAdded, with: { (snapshot) in
FIRDatabase.database().reference().child("conversations").child(snapshot.key).observe(of: .value, with: { (conversationSnapshot) in
if let conversation = Groups(snapshot: conversationSnapshot) {
conversation.groupId = conversationSnapshot.key
self.conversations.append(conversation) // here is where you are appending the data. This will be appended each time a change is made
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
}, withCancel: nil)
Now as you point out, if you change FIRDatabase.database().reference().child("conversations").child(snapshot.key).observe to .observeSingleEvent, the data won't append again, but you won't get updates. One option is whenever the listener is triggered, you search the array for the snapshot key, and then update the snapshot at that index if found. Not the most efficient method, to be sure.
In summation, it sounds like you do need to use observe, but as it stands, the data is duplicated because the code appends the snapshot to the end of the array whenever a change is made to the snapshot's value. You will have to use something other than self.conversations.append(conversation).
I'd be happy to brainstorm some other options if you wanted to message me directly.
FIRDatabase.database().reference().child("users").child(uid).child("conversations").observe(.childAdded, with: { (snapshot) in
FIRDatabase.database().reference().child("conversations").observe(.childAdded, with: { (conversationAdded) in
if conversationAdded.key == snapshot.key {
if let group = Groups(snapshot: conversationAdded) {
self.conversations.append(group)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}
})
}, withCancel: nil)
FIRDatabase.database().reference().child("users").child(uid).child("conversations").observe(.childAdded, with: { (snapshot) in
FIRDatabase.database().reference().child("conversations").child(snapshot.key).observe(.childChanged, with: { (conversationSnapshot) in
let conversationIdsArray = self.conversations.map({$0.groupId})
let changeAtGroupIdIndex = conversationIdsArray.index(of: snapshot.key)
let conversationToBeUpdated = self.conversations[changeAtGroupIdIndex!]
conversationToBeUpdated.setValue(conversationSnapshot.value, forKeyPath: conversationSnapshot.key)
self.tableView.reloadData()
}, withCancel: nil)
}, withCancel: nil)
In the above code, I create two different observers. The first one loads conversations when the app is loaded or a conversation is added. The second one updates the conversation array if the child has been changed. This solves both problems.
Filter array with data excluding the object just received. Identify that object in the existing array by a unique id like groupID or chatID in my case. Then the repeated object will be removed
self.conversations = self.conversations.filter { obj in (obj.chatId as? String) != (data.chatId as? String) }
self.conversations.append(data)

Query Firebase for selected values in iOS Swift

I am creating a chat app in Swift where I am using Firebase. In my Firebase database, I have an object called members. From that members, I only want data which has a particular key name.
My structure:
So from members, I only want data which has anuj as the key.
Code:
refMembers.queryOrderedByKey().queryEqual(toValue: true, childKey: "anuj").observe(.value, with: { (snap) in
Logger.sharedInstance.log(whatToPrint: snap.childrenCount as AnyObject)
*******This crashes my app*******
})
Code
refMembers.queryOrderedByValue().queryEqual(toValue: true).observe(.value, with: { (snap) in
Logger.sharedInstance.log(whatToPrint: snap.childrenCount as AnyObject)
******* here i get no data*******
})
Let's try:
refMembers.orderByChild("anuj").equalTo(true).on("value", function(snapshot) {
console.log(snapshot.key)
})
Read more Firebase Docs

Showing post from currentUsers wall - Firebase / Swift

I am trying to create a wall/timeline that shows posts from all the users that currentUser is following. All users that currentUser is following is showed under Users -> UserID -> Following. Whenever one of their followers is making a post it will be added under feed-items with an autoID - the key (the autoID) is added to currentUsers Users -> UserID -> Wall at the same time.
Here is an image of an example from my Firebase database:
Under Wall as you can see, one of this users followers has made a post (the whole post is saved under feed-items) and the autoID of that post has made it to the users Wall.
Now I am trying to figure out how to show all the posts in feed-items, based on the autoID's stored under currentUsers Wall.
I have tried the following code, but nothing shows and when it reaches this line print(self.updates.count) it is printing 0.
func startObersvingDB(userID: String) {
FIRDatabase.database().reference().child("Users").child(userID).child("Wall").observeEventType(.ChildAdded, withBlock: { snapshot in
if let posts = snapshot.value!["Post"] as? String {
self.postArray.append(posts)
for i in 0..<self.postArray.count {
let post = self.postArray[i]
print(post)
FIRDatabase.database().reference().child("feed-items").queryEqualToValue(post).observeEventType(.Value, withBlock: { (snapshot: FIRDataSnapshot) in
var newUpdates = [Sweet]()
for update in snapshot.children {
let updateObject = Sweet(snapshot: update as! FIRDataSnapshot)
newUpdates.append(updateObject)
}
self.updates = newUpdates.reverse()
print(self.updates.count)
self.tableView.reloadData()
}) { (error: NSError) in
print(error.description)
}
}
}
})
}
If I am guessing right, your structure for feed-items is something like this.
feed-items
-UniquePostID
-Post Data (key-value pair(s))
If this is the case then to retrieve data for a post use .child(post) instead of .queryEqualToValue(post). Also since this will return DataSnapshot for single post you can directly create your Sweet object and append it in existing updates array.
One more thing I don't think you need to iterate entire postArray each time a new post is added. You should retrieve data for new post only.
Hope this helps!!

Resources