CollectionView duplicating data after a new post - ios

I cannot seem to figure out why my data keeps duplicating after a new post has been made. When the app starts everything looks as it should and the data is presented properly from firebase.
After I make a new post and return to my feed the new data appears but the old data has been duplicated.
Here is my code,
override func viewDidAppear(_ animated: Bool) {
ref = FIRDatabase.database().reference()
ref.child("brackets").observe(FIRDataEventType.value, with: { (snapshot) in
if let bracketsSnapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for brackets in bracketsSnapshot {
if let bracketsDict = brackets.value as? Dictionary <String, Any> {
let key = brackets.key
let post = BracketsPublicFeed(postKey: key, postData: bracketsDict)
self.posts.append(post)
}
}
}
self.collectionView.reloadData()
})
}
Here is the code used when posted,
func PostData(itemToCell1: String, itemToCell2: String, itemToCell3: String, itemToCell4: String, userName: String?/*, userID: String?*/) {
let ref : FIRDatabaseReference!
ref = FIRDatabase.database().reference()
let votes = 0
let userName = ""
let key = ref.child("brackets").childByAutoId().key
let post = [
"userID": userID as AnyObject,
"item1": itemToCell1 as AnyObject,
"item2": itemToCell2 as AnyObject,
"item3": itemToCell3 as AnyObject,
"item4": itemToCell4 as AnyObject,
"userName": userName as AnyObject,
"votes": votes as AnyObject ]
let childUpdates = ["/brackets/\(key)": post,
"/user-posts/\(userID)/\(key)/": post]
ref.updateChildValues(childUpdates)
}
I have tried various things such as,
ref.removeAllObservers()
self.posts.removeAll()
in the viewDidDisappear
I will gladly post more code if I am not painting the whole picture.
UPDATE,
Thanks for your responses guys. So far none of them have worked except for the suggestion to use ref.child("brackets").removeAllObservers()
The thing is though it that it works exactly as it should when I write this code,
self.posts.removeAll()
ref.child("brackets").removeAllObservers()
So I get rid of everything and reload it every time the view appears.

From Firebase docs:
- (void) removeAllObservers
Removes all observers at the current reference, but does not remove any observers at child references.
removeAllObservers must be called again for each child reference where a listener was established to remove the observers.
So, ref.removeAllObservers() in viewDidDisappear won't remove observer for ref.child("brackets").
Try this in viewDidDisappear:
ref.child("brackets").removeAllObservers()

did you try observerSingleEvent? Seems like you're keep adding new observer every time view appears. There is, actually, neat way to use FirebaseCollectionViewDataSource without populating data manually, like this:
dataSource = FirebaseCollectionViewDataSource.init(query: ref.child("brackets"),
modelClass: Post.self,
nibNamed: "PostViewCell",
cellReuseIdentifier: "lesson",
view: self.collectionView)

You're listening for a .Value event, which triggers with the entire list each time that the data changes (such as when an item is added). So the first time it triggers with e.g. items 1, 2 and 3. Then next time it triggers with items 1, 2, 3 and 4.
You can deal with this in two ways:
clear the collection view every time
respond to .ChildXxx events instead
The first is simplest, but will lead to a flickering UI. So I'll show the second option: responding to .ChildAdded.
A .ChildAdded event is fired for every existing child when you first attach the observer. So that's items 1, 2 and 3. When you then add a new item, it will fire with only item 4. That allows you to simply add the new item to the collection view:
ref.child("brackets").observe(FIRDataEventType.ChildAdded, with: { (snapshot) in
if let bracketsDict = snapshot.value as? Dictionary <String, Any> {
let key = brackets.key
let post = BracketsPublicFeed(postKey: key, postData: bracketsDict)
self.posts.append(post)
self.collectionView.reloadData()
})
Note: I didn't compile this code, so you may have to fix some problem with it. If you do, leave comments and I'll update.

Related

I need to update a TableView every so often but doing so duplicates the cells

I have a TableView that I am updating from time to time with a Timer so that the data of the TableView changes if necessary... What happens is that the data is updated but it is duplicated and it does not eliminate the data that it had previously, then it is generating a giant TableView.
How could I make them update but delete the data I had previously and leave only the new cells when the tableview is updated?
This is the code that I use in the timer to update the TV:
#objc func updatetableview(){
databaseRef.child("A_Usuarios").queryOrdered(byChild: "TipoUsuario").queryEqual(toValue: "Empresa").observe(.childAdded, with: { (snapshot) in
let key = snapshot.key
self.snap = (snapshot.value as? NSDictionary)!
self.snap.setValue(key, forKey: "Uid")
self.city = self.snap["Ciudad"] as? String ?? ""
self.activo = self.snap["Activo"] as? String ?? ""
if self.city == self.cdad && self.activo != "No" {
if(key == self.loggedInUser?.uid){
print("Same as logged in user, so don't show!")
}
else
{
self.usersArray.append(self.snap)
//insert the rows
self.tableview.insertRows(at: [IndexPath(row:self.usersArray.count-1,section:0)], with: UITableView.RowAnimation.automatic)
}
}
}) { (error) in
print(error.localizedDescription)
}
I hope you can help me, thank you very much!
You need to clear the array before doing another observe
#objc func updatetableview(){
usersArray.removeAll()
....
}
BTW .observe(.childAdded is supposed to do the job , so you may need n't to do this
The observer you have used returns all the values in the given path. This closure is called when a new child is added. But it gets all the available in that path, not only the newly added data.
This is why it is generating a giant TableView.
So you need to get the newly added data only using queryLimited(toLast: UInt)
databaseRef.child("A_Usuarios").queryOrdered(byChild: "TipoUsuario").queryEqual(toValue: "Empresa").queryLimited(toLast: 1).observe(.childAdded, with: { (snapshot) in
In firebase, if you are observing same node 5 times then it will give you 5 events. So you are adding same record 5 times. So you need to check that you are already observing that node before observing. Following code creating a problem. Every time you call updateTableView. It is adding new observer and that why you get same records multiple times.
databaseRef.child("A_Usuarios").queryOrdered(byChild: "TipoUsuario").queryEqual(toValue: "Empresa").observe(.childAdded, with: { (snapshot) in

How to know when all tasks are completed after HTTPRequest (Swift)

I am currently facing an issue that has been bothering me for days. I have tried several solutions but don't seem to be able to fix the issue. Let me illustrate what is going on.
I have a feed with posts in a UITableView. As soon as the user selects one of the cells, he is shown a detailed view of this post in a new UIViewController. In there, I set up all views and call a function loadPostDetails() in the viewDidLoad() method. As soon as the view shows up, a custom loading indicator is presented which is set to disappear (hide) when the details are loaded and then the UITableView of this UIViewController (let's call it PostController) is shown.
In my loadPostDetails()function, I make a HTTPRequest which acquires the JSON data I need. This returns three kinds of info: the post details themselves, the likes and the comments. I need to handle each of these three elements before I reload the UITableView and show it. Currently, I do it like this:
HTTP.retrievePostDetails(postID: postID, authRequired: true) { (error, postDetails) in
if(error != nil) {
self.navigationController?.popViewController(animated: true)
} else {
if let postDetails = postDetails, let postInfo = postDetails["postInfoRows"] as? [[String: Any]], let postLikesCount = postDetails["postLikesCount"] as? Int, let postLikesRows = postDetails["postLikesRows"] as? [[String: Any]], let postCommentsRows = postDetails["postCommentsRows"] as? [[String: Any]] {
DispatchQueue.main.async {
let tableFooterView = self.postTableView.tableFooterView as! tableFooterView
if let postTitle = postInfo[0]["postTitle"] as? String, let postText = postInfo[0]["postText"] as? String {
self.postTitleLabel.text = postTitle
self.postTextLabel.text = postText
}
for (index, postCommentRow) in postCommentsRows.enumerated() {
tableFooterView.postComments.append(Comment(userID: postCommentRow["userID"] as! Int, userProfilePicURL: postCommentRow["userProfilePicURL"] as! String, userDisplayName: postCommentRow["userDisplayName"] as! String, commentTimeStamp: postCommentRow["commentTimeStamp"] as! TimeInterval, commentText: postCommentRow["commentText"] as! String))
}
var likeDisplayNames = [String]()
for postLikeRow in postLikesRows {
likeDisplayNames.insert(postLikeRow["userDisplayName"] as! String, at: 0)
}
if(postLikesCount > 2) {
tableFooterView.likesLabel.text = "\(likeDisplayNames[0]), \(likeDisplayNames[1]) and \(postLikesCount - 2) others"
} else {
tableFooterView.likesLabel.text = "\(postLikesCount) likes"
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
self.screenDotsLoader.isHidden = true
self.screenDotsLoader.stopAnimating()
self.postTableView.isHidden = false
self.postTableView.reloadData()
})
}
}
}
Note: I add more text to UILabels, like the date and the profile picture of the user, but I have removed a couple of lines to make it more readible and because the extra code is irrelevant for this problem.
Now, as you might already see, I call the reload stuff 1 second later, so in 95% of the cases it works just fine (but still, it is not perfect, as it is a "hack"). In the other 5%, the layout can't figure out the right constraints, resulting in a very bad layout.
I have in the last days tried to play with DispatchGroups(), but I couldn't figure out how to do it. I am in fact trying to know when all tasks have been performed, so when all UILabels have been updated, all UIImageViews have been updated etc. Only then, I want to reload the UITableView.
I was hoping someone could point me in the right direction so I can enhance my user experience a bit more. Thank you!
DispatchGroups is used when you do a bunch of asynchronous tasks together , and need to be notified upon finish of all , but currently you don't do this as when you receive the response , all are inside the main thread which is synchronous ( serial ) which means all the stuff before reloading the table will happen before it's reload , so you're free to use dispatch after if this will make sense to your UX

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.

confused with observing firebase database using value event type

i am a newbie in iOS development, and i am learning a tutorial about read and write data to firebase. I want to retrieve data from Firebase and populate the tableView with it.
I am confused when retrieving the data from real time database using .value data event type when observing the reference. here is the simplified code
class Story
{
var text = ""
var numberOfLikes = 0
var numberOfAngry = 0
let ref: FIRDatabaseReference!
init(snapshot: FIRDataSnapshot)
{
ref = snapshot.ref
if let value = snapshot.value as? [String : Any] {
text = value["text"] as! String
numberOfLikes = value["numberOfLikes"] as! Int
numberOfAngry = value["numberOfAngry"] as! Int
} else {
numberOfAngry = 0
numberOfLikes = 0
}
}
}
class StoriesTableViewController: UITableViewController
{
// MARK: - Properties
var stories = [Story]()
private let storiesRef = FIRDatabase.database().reference().child("stories")
#IBOutlet weak var composeBarButtonItem: UIBarButtonItem!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
storiesRef.observe(.value, with: { snapshot in
self.stories.removeAll()
for child in snapshot.children {
let story = Story(snapshot: child as! FIRDataSnapshot)
self.stories.append(story)
}
self.tableView.reloadData()
})
}
my questions are...
i have looked other tutorial which almost the same, just write simple data and populate the tableview with it, but the other tutorial just use .childadded as the event type. i don't understand why in this tutorial .value event type is used ? because it looks more complicated.
why we have to loop the snapshot.children ? is snapshot.children is just the same as child when we create database reference?
actually i am not really comfortable with term snapshot(FIRDataSnapshot) and reference (FIRDatabasereference). is there any article or video explaining about this term?
i am sorry if i am asking too many questions and it seems silly, just a rookie who wants to really grasp of this code. Thanks in advance
When you observe the .value event, your completion handler gets called once; with a snapshot of all child nodes. This allows you to handle all child nodes at once, which can be handy to do things like updating counters, or reducing the number of updates to a table view.
When you observe a list of items from the Firebase Database with a .value event, there will potentially be multiple child nodes. So the snapshot contains a list of those results. Even if there is only a single child node, the snapshot will contain a list of one child node. So your completion handler needs to loop over those results, which you do by iterating over snapshot.children.
A FIRDataSnapshot/DataSnapshot is a Firebase object that contains a snapshot of the data you requested at a specific time.

How to call data within a custom class function from Firebase

I have a post class that I use to fill a collection view with post data from Firebase. I was having trouble getting some user data so I tried putting the observer in the post class. This seems to work fine, however there is a slight delay in getting the data from Firebase so it seems to finish the init() function before the firebase call is complete. This is the post class :
class Post {
var _comment1Text: String?
var _comment1User: String?
var _comment1Name: String?
init(comment1Text: String, comment1User: String, comment1Name: String) {
self._comment1Text = comment1Text
self._comment1User = comment1User
self._comment1Name = comment1Name
if self._comment1User != "" {
DataService.ds.REF_USERS.child(self._comment1User!).observeSingleEventOfType(.Value, withBlock: { userDictionary in
let userDict = userDictionary.value as! NSDictionary
self._comment1Name = userDict.objectForKey("username") as? String
})
}
print(self._comment1Text)
print(self._comment1Name)
}
}
If I print within the firebase call, it works. However, if I print after it, for some reason, comment1name is not yet filled. Is there a way to get self._comment1Name to contain the data from Firebase in time to fill the collectionView?
Thanks in advance.
DataService.ds.REF_USERS.child(self._comment1User!).observeSingleEventOfType(.Value
is an Asynchronous call, So access your print functions inside the completionBlock and you have to update your collectionView inside the completionBlock.
DataService.ds.REF_USERS.child(self._comment1User!).observeSingleEventOfType(.Value, withBlock: { userDictionary in
let userDict = userDictionary.value as! NSDictionary
self._comment1Name = userDict.objectForKey("username") as? String
print(self._comment1Text)
print(self._comment1Name)
// Update your collectionView
})
Asynchronous call's are loaded in a different network thread, so it takes some time to retrieve the DB from the server.
If you are looking for communicating between a custom class and you viewController look at my this answer :- https://stackoverflow.com/a/40160637/6297658

Resources