Weird error when getting posts from Firebase - swift - ios

I have a weird error that I do not know how to solve, let me explain:
(I should say there is more code before the following, if you want to see)
for i in 0..<self.followings.count {
let following = self.followings[i]
print("\(following.Id) and \(self.followings.count)")
FIRDatabase.database().reference().child("feed-items").queryOrderedByChild("profilePhoto").queryEqualToValue(following.Id).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)
}
}
This is my code for showing posts from users that currentUser is following, in my feed (UITableView). The problem is when someone is clicking the like button, the person which post was liked will double every time currentUser clicks like.
I can prevent that from happening if I mode this line of code: var newUpdates = [Sweet]() up before viewDidLoad. But if I do so then the feed will only show posts from one of the users that currentUser is following - even if I am following two users or more.
So how can I show all posts from users I am following and NOT duplicate when clicking like on one of their posts? Let me know if you need more code :-)

Related

Firebase Observer Event Type

I’m trying to modify code written a by a previous programmer. He wrote a getPostFromFirebase() function where it updates the tableview when 1) the app loads up due to it’s presence in viewDidLoad and 2) when there is a new post add by a user. The problem is that he used a .observe(.childAdded) event type which means when a post is deleted or modified, the tableView will not update(my end goal to do). When I change .childAdded to .value, the current data doesn’t get loaded upon launch. I’ve been banging my head against the wall to figure out a if let statement to add a .value event type so the view can refresh after any change(if that’s even possible). I’m familiar with Firebase RT DB hence how I was able to ID the observer issue but I’m no where near close to as good as I’d like to be so any help is appreciated.
func getPostFromFirebase() {
let mostRecent = dbRef.lastestPostsQuery(count: 10)
mostRecent.keepSynced(true)
mostRecent.observe(.childAdded) { (snapshot: DataSnapshot) in
/*parse method in the PostFetcher class
that returns the post data or an error by way of a tuple.*/
let (post, error) = PostFetcher.parsePostSnapshot(snapshot: snapshot)
if let post = post {
self.latestPosts.append(post)
if let postId = post.postId { print("PostId = \(postId)") }
}
if let error = error {
print("\(#function) - \(error)")
}
}
}
Edit:
Thanks to Franks help I was able to implement his suggestion and added a .removeAll() to remove the current state and have the view append a fresh snapshot. Whether a post is added or deleted, the view now updates as I'd like it to do.
func getPostFromFirebase() {
let mostRecent = dbRef.lastestPostsQuery(count: 10)
mostRecent.keepSynced(true)
mostRecent.observe(.value) { (snapshot: DataSnapshot) in
self.latestPosts.removeAll()
for child in snapshot.children.allObjects as! [DataSnapshot] {
let (post, error) = PostFetcher.parsePostSnapshot(snapshot: child)
if let post = post {
self.latestPosts.append(post)
self.tableView.reloadData()
if let postId = post.postId { print("PostId = \(postId)") }
}
if let error = error {
print("\(#function) - \(error)")
}
}
}
}
The .child* events fire on child nodes of the location/query that you observe, while .value fires on the location/query itself. This means that the value you get is one level higher up in the JSON, and you'll need to loop over the results:
mostRecent.observe(.value) { (snapshot: DataSnapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
let (post, error) = PostFetcher.parsePostSnapshot(snapshot: child)
if let post = post {
self.latestPosts.append(post)
if let postId = post.postId { print("PostId = \(postId)") }
}
if let error = error {
print("\(#function) - \(error)")
}
}
}
Alternatively, you can listen to the .childChanged and .childRemoved events (in addition to .childAdded that you already have) and handle them separately. This may be better in cases where you need to update the UI efficiently, since it allows you to handle each individual case (new node, changed, node, removed node) in the most efficient way.

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 ?

How can I stop UICollectionView from showing duplicate items after Firebase update

I have two UICollection views on a page that displays data about a Room. It includes photos of the room in one UICollection View and another UICollection View which contains a list of items in that room. There's a link to edit the Room. When a user clicks on the link, they then segue to another view that let's them update it including adding additional photos.
After adding a photo, and hitting submit, in the background the photo is uploaded to Firebase storage and in the Firebase database, the record is updated to include the name of the file that was just uploaded. Meanwhile, the user is segued back to the Room view.
There's a watched on the record of the room in Firebase and when it updates, then the view is refreshed with new data. This is where the problem occurs. It appears, based on a lot of debugging that I've been doing, that the Observe method fires twice and what ends up happening, is the UICollection view that holds the images of the room will show duplicates of the last photo added.
For example, if I add one photo to the room, that photo will appear in the collection 2x. I've attempted to clear the array before the array is updated with the images, and from my analysis, it appears that the array only contains two items, despite showing three in the view. I'm not sure what is happening that would cause this?
Here's a link to the entire file, because I think it might help.
Here's the loadData() method in case this is all that's important:
func loadData() {
self.ref = Database.database().reference()
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
guard let userID = Auth.auth().currentUser?.uid else { return }
let buildingRef = self.ref.child("buildings").child(userID)
buildingRef.keepSynced(true)
buildingRef.child(self.selected_building as String).observe(DataEventType.value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
if ((value) != nil) {
let building_id = value?["id"] as! String
let saved_image = value?["imageName"] as! String
let user_id = userID as! String
let destination = "/images/buildings/\(userID)/\(building_id)/"
let slideShowDictionary = value?["images"] as? NSDictionary
if ((slideShowDictionary) != nil) {
self.slideShowImages = [UIImage]()
self.slideShowCollection.reloadData()
var last_value = ""
slideShowDictionary?.forEach({ (_,value) in
print("are they different? \(last_value != (value as! String))")
if (last_value != value as! String) {
print("count: \(self.slideShowImages.count)")
print("last_value \(last_value)")
print("value \(value)")
last_value = value as! String
CloudStorage.instance.downloadImage(reference: destination, image_key: value as! String, completion: { (image) in
self.slideShowImages.append(image)
self.slideShowCollection.reloadData()
})
}
})
CloudData.instance.getBuildingById(userId: user_id, buildingId: building_id, completion: { (building) in
self.title = building.buildingName as String
self.roomsCollection.reloadData()
})
}
}
})
// User is signed in.
self.getRooms()
}
I am not completely familiar with the Firebase API but if you are having issues with the observation I would suspect the following:
#IBAction func unwindToRoomsVC(segue:UIStoryboardSegue) {
loadData()
}
Triggering loadData a second time looks like it would add a second observation block. As best I can tell the .observe method probably persists the block it is given and triggers it on all changes.

Code crashing when ran too frequently. Firebase / Swift

func grabPosts2(){
posts = []
currentUserRef?.child("invitedToPosts").queryOrdered(byChild: "timestamp").queryStarting(atValue: cutoff).observeSingleEvent(of: .value, with: { (snapshot: FIRDataSnapshot) in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshots {
let postKey = snap.key
DataService.ds.REF_INITIATIONS.child(postKey).observeSingleEvent(of: .value, with: { (snapshot: FIRDataSnapshot) in
if let dict = snapshot.value as? Dictionary<String,Any>{
let post = Post(postKey: snapshot.key, postData: dict)
self.posts.append(post)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
})
}
}
})
}
Database example:
User
invitedToPosts
09283094823904
timestamp: 30909000909
Initiations
09283094823904
post information
What the code above is doing is going into a user's invitedToPosts, if the timestamp is recent enough, grab the key and go find it in the Initiations, then grab out the post information, make a post object with it, append it to my posts array, and reload my tableview each time.
I run this code on startup and then I've got both a button and a refresh controller that runs this function.
If I slowly tap on the refresh button or do my drag to refresh slowly I never get a crash with this code. However, if I tap on the button super quickly, sometimes I can tap it 3 times and sometimes I can tap on it 20 times, but eventually the code crashes.
I'm getting an index out of range on this line in my cellForRowAtIndexPath
let post = posts[indexPath.row]
So I assume it's got something to do with clearing out the post array and trying to refresh the page while another request is running slowly and then it catches up and everything goes nuts. No idea though.
Any ideas how to safely run this code or how to successfully write this so that when I aim to refresh my tableview with up to date information I won't get this crash?
Thanks!

Wont show like until refresh of tableview - swift & Firebase

I am fetching all the posts from the users I am following like this:
func observePosts(userID: String) {
let ref = FIRDatabase.database().reference().child("Users").child(userID).child("Wall")
ref.observeEventType(.ChildAdded, withBlock: { (snapshot) in
let postId = snapshot.key
let postReference = FIRDatabase.database().reference().child("feed-items").child(postId)
postReference.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
let update = Sweet(snapshot: snapshot)
self.updates.append(update)
self.updates = self.updates.reverse()
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}, withCancelBlock: nil)
}, withCancelBlock: nil)
}
THe problem is, when I click like on one of the posts, the likelabel is not updates. I have a custom class for the cell and in that I have the likeButton action and it is updating the count of likes. It just does not show in the tableView until I refresh the tableview.
How can I get it update the count in real time so I do not need to pull to refresh before I can see that I liked the post?

Resources