Firebase items returning multiple times in collection view on database change - swift

When the function to create or delete a list is called inside of my app the remaining lists are duplicated and displayed multiple times within the collectionView until the app is reloaded. I only called the fetchLists function twice, in the viewDidLoad and in the pull to refresh function. On pull to refresh the lists return to normal.
Fetch list:
fileprivate func fetchLists() {
self.collectionView?.refreshControl?.endRefreshing()
guard let currentUid = Auth.auth().currentUser?.uid else { return }
let ref = Database.database().reference().child("list-feed").child(currentUid)
ref.observe(.value) { (listFeedSnapshot) in
guard var allObjects = listFeedSnapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.forEach({ (allObjectsSnapshot) in
let listId = allObjectsSnapshot.key
let listRef = Database.database().reference().child("lists").child(listId)
listRef.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
guard let uid = dict["uid"] as? String else { return }
Database.fetchUserWithUID(uid: uid, completion: { (user) in
guard let dictionary = snapshot.value as? [String: Any] else { return }
var list = List(user: user, dictionary: dictionary)
let listId = snapshot.key
list.id = snapshot.key
self.list = list
self.lists.append(list)
self.lists.sort(by: { (list1, list2) -> Bool in
return list1.creationDate.compare(list2.creationDate) == .orderedDescending
})
self.collectionView?.reloadData()
ref.keepSynced(true)
listRef.keepSynced(true)
})
})
})
}
}
Create list:
let values = ["uid": uid, "title": listNameText, "creationDate": Date().timeIntervalSince1970] as [String : Any]
ref.updateChildValues(values, withCompletionBlock: { (error, ref) in
if let error = error {
self.navigationItem.rightBarButtonItem?.isEnabled = true
print("failed to save user info into db:", error.localizedDescription)
return
}
let memberValues = [uid : 1]
ref.child("list-members").updateChildValues(memberValues)
self.handleUpdateFeeds(with: ref.key!)
self.handleListFeeds(with: ref.key!)
print("successfully created list in db")
Update feeds:
func handleUpdateFeeds(with listId: String) {
guard let uid = Auth.auth().currentUser?.uid else { return }
let values = [listId: 1]
Database.database().reference().child("list-feed").child(uid).updateChildValues(values)
}
func handleListFeeds(with listId: String) {
guard let uid = Auth.auth().currentUser?.uid else { return }
let values = [listId: 1]
Database.database().reference().child("user-lists").child(uid).updateChildValues(values)
}
Firebase database:
{
"list-feed" : {
"otxFDz0FNbVPpLN27DYBQVP4e403" : {
"-LjeAoHJTrYK7xjwcpJ9" : 1,
"-LjeApq-Mb_d_lAz-ylL" : 1
}
},
"lists" : {
"-LjeAoHJTrYK7xjwcpJ9" : {
"creationDate" : 1.5630020966384912E9,
"title" : "Test 1",
"uid" : "otxFDz0FNbVPpLN27DYBQVP4e403"
},
"-LjeApq-Mb_d_lAz-ylL" : {
"creationDate" : 1.563002101329072E9,
"list-members" : {
"otxFDz0FNbVPpLN27DYBQVP4e403" : 1
},
"title" : "Test 2",
"uid" : "otxFDz0FNbVPpLN27DYBQVP4e403"
}
}
}

Since you're calling ref.observe(, you're attaching a permanent observer to the data. This means that if you call fetchLists a second, you're attaching a second observer and you'll get the same data twice.
If you only want the data to be read once per call to fetchLists, you should use observeSingleEventOfType:
fileprivate func fetchLists() {
self.collectionView?.refreshControl?.endRefreshing()
guard let currentUid = Auth.auth().currentUser?.uid else { return }
let ref = Database.database().reference().child("list-feed").child(currentUid)
ref.observeSingleEvent(of: .value) { (listFeedSnapshot) in
Also see the documentation on reading data once.

Related

How do I add firestore document Id to my model and subsequently to the array?

I have a struct "Order" that contains a field called orderId:
protocol OrderSerializable {
init?(dictionary:[String:Any])
}
struct Order {
var orderId: String
var status: Int
var currentTotal: Double
var Dictionary:[String : Any] {
return [
"orderId": orderId,
"status": status,
"currentTotal": currentTotal
]
}
}
extension Order : OrderSerializable {
init?(dictionary: [String : Any]) {
guard let orderId = dictionary["orderId"] as? String,
let status = dictionary["status"] as? Int,
let currentTotal = dictionary["currentTotal"] as? Double
else { return nil }
self.init(orderId: orderId, status: status, currentTotal: currentTotal)
}
}
I need to add the firestore document Id to the orderId field in the model array i.e. "ordersArray". How would I go about doing that?
This is my query code so far and I have indicated the line that I need:
orderRef.getDocuments() {
querySnapshot, error in
if let error = error {
print("\(error.localizedDescription)")
} else {
guard let documents = querySnapshot?.documents else { return }
for document in documents {
let orderDictionary = document.data() as [String : Any]
let order = Order(dictionary: orderDictionary)
// Here I want to append the firestore documentId to order.orderId before appending it to the array
self.ordersArray.append(order!)
}
DispatchQueue.main.async {
self.ordersTableView?.reloadData()
}
}
}
Thanks in advance.
Different error
Modify your extension to accept the documentId as an additional parameter, and pass this to the created Order object.
protocol OrderSerializable {
init?(dictionary:[String:Any], id: String)
}
extension Order : OrderSerializable {
init?(dictionary: [String : Any], id: String) {
guard let status = dictionary["status"] as? Int,
let currentTotal = dictionary["currentTotal"] as? Double
else { return nil }
self.init(orderId: id, status: status, currentTotal: currentTotal)
}
}
Then, when you create each order, pass the documentId as the id parameter.
orderRef.getDocuments() {
querySnapshot, error in
if let error = error {
print("\(error.localizedDescription)")
} else {
guard let documents = querySnapshot?.documents else { return }
for document in documents {
let orderDictionary = document.data() as [String : Any]
let order = Order(dictionary: orderDictionary, id: document.documentId)
self.ordersArray.append(order!)
}
DispatchQueue.main.async {
self.ordersTableView?.reloadData()
}
}
}
Alternatively, you could have stored the orderId directly in the document itself, so that it would be passed in along with the dictionary, avoiding the need for using documentId.

Cant fetch SavedPost

My app has an option to save posts for users to watch later. The code is:
func savedPost(for cell: FirstView) {
guard let indexPath = collectionView.indexPath(for: cell) else { return }
var post = self.posts[indexPath.item]
guard let currentuserId = Auth.auth().currentUser?.uid else { return }
let targetUid = post.user.uid
guard let postId = post.id else { return }
let ref = Database.database().reference().child("save_post").child(currentuserId).child(postId)
if post.hasSaved {
ref.removeValue { (err, _) in
if let _ = err {
showErr(info: NSLocalizedString("failtoUnsave", comment: ""), subInfo: tryLater)
return
}
post.hasSaved = false
self.posts[indexPath.item] = post
self.collectionView.reloadItems(at: [indexPath])
}
} else {
let values = ["userId": targetUid]
ref.updateChildValues(values) { (err, ref) in
if let _ = err {
showErr(info: NSLocalizedString("failtoSave", comment: ""), subInfo: tryLater)
}
post.hasSaved = true
self.posts[indexPath.item] = post
self.collectionView.reloadItems(at: [indexPath])
}
}
}
With this code my firebase database in "save_post" has -> currentUseruId -> postid -> postUserId.
On ProfileController users can view saved Posts from "savedPost" Tab. The code is:
var savedPosts = [Post]()
fileprivate func fetchSaved() {
var userIds = [String]()
var postIds = [String]()
guard let uid = self.user?.uid else { return }
let getIDsRef = Database.database().reference().child("save_post").child(uid)
let query = getIDsRef.queryOrderedByKey()
query.observeSingleEvent(of: .value) { (snapshot) in
let dictionary = snapshot.value as? [String: Any]
dictionary?.forEach({ (key,value) in
guard let dic = value as? [String: String] else { return }
postIds.append(key)
userIds.append(dic["userId"] ?? "")
})
var i = 0
while i < userIds.count {
self.fetchPostsWithUserIDPostID(userID: userIds[i], postID: postIds[i])
i += 1
}
}
}
fileprivate func fetchPostsWithUserIDPostID(userID: String, postID: String) {
let getPostRef = Database.database().reference().child("video_list")
getPostRef.child(userID).child(postID).observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String: Any] else { return }
let ref = Database.database().reference().child("users").child(userID)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
let user = User(uid: userID, dictionary: dict)
var post = Post(user: user, dictionary: dictionary)
post.id = postID
guard let currentUserUID = Auth.auth().currentUser?.uid else { return }
Database.database().reference().child("likes").child(postID).child(currentUserUID).observeSingleEvent(of: .value, with: { (snapshot) in
if let value = snapshot.value as? Int, value == 1 {
post.hasLiked = true
} else {
post.hasLiked = false
}
post.hasSaved = true
self.savedPosts.append(post)
self.savedPosts.sort(by: { (p1, p2) -> Bool in
return p1.creationDate.compare(p2.creationDate) == .orderedDescending
})
self.collectionView.reloadData()
})
})
})
}
However, when I click "savedPost" tab, there is no post shown. I don't know where my mistake is. I have all the necessary code under all override func collectionView(....). I believe the error should come from the code listed above. I am sincerely looking for help to resolve this issue. Thanks.
There could be a number of things going on here. It would be good to throw some print statements in there to make sure that 1) the data you're getting back from the database looks like what you expect, and 2) that you're properly parsing it into Post objects. Do you have your cells defined properly for your CollectionView? Also, I don't see where you are defining the data source for the CollectionView.

Fetch multi level node from Firebase

I am trying to fetch the "friends" from the node to be able to show them in UICollectionView afterwards. I now realized that I have to use a struct and place the Friends array inside. I am struggling now to understand how to fetch them into that array (you can see it at the bottom of the post). Data is stored in a firebase node. How can I grab the data and what would be the procedure to place it in UICollectionView afterwards? This is my function so far to retrieve.
UPDATE: (I think I am fetching correctly now but I don't get any results. Is there something that I should do in collection view? or what am I doing wrong?)
UPDATE: Here is my code for post fetching:
func fetchPosts3() {
ref.child("Users_Posts").child("\(unique)").queryOrderedByKey().observeSingleEvent(of: .value, with: { snapshot in
print(snapshot)
if snapshot.value as? [String : AnyObject] != nil {
let allPosts = snapshot.value as! [String : AnyObject]
self.posts.removeAll()
for (_, value) in allPosts {
if let postID = value["postID"] as? String,
let userIDDD = value["userID"] as? String
{
//ACCESS FRIENDS
ref.child("Users_Posts").child("\(unique)").child(postID).child("friends").queryOrderedByKey().observeSingleEvent(of: .value, with: { (snap) in
print("FRIENDS: \(snap.childrenCount)")
//var routine = self.postsWithFriends[0].friends
for friendSnap in snap.children {
if let friendSnapshot = friendSnap as? DataSnapshot {
let friendDict = friendSnapshot.value as? [String: Any]
let friendName = friendDict?["name"] as? String
let friendPostID = friendDict?["postID"] as? String
let postsToShow = PostWithFriends(id: userIDDD, friends: [Friend(friendName: friendName!, friendPostID: friendPostID!)])
self.postsWithFriends.append(postsToShow)
print("COUNTING: \(self.postsWithFriends.count)")
// then do whatever you need with your friendOnPost
}
}
})
}
}
//GET LOCATION
self.collectionView?.reloadData()
self.posts.sort(by: {$0.intervalPosts! > $1.intervalPosts!})
}
})
ref.removeAllObservers()
}
That's how the data looks at the database:
{
"-LN2rl2414KAISO_qcK_" : {
"cellID" : "2",
"city" : "Reading",
"date" : "2018-09-23 00:41:26 +0000",
"friends" : {
"UJDB35HDTIdssCtZfEsMbDDmBYw2" : {
"name" : "Natalia",
"postID" : "-LN2rl2414KAISO_qcK_",
"userID" : "UJDB35HDTIdssCtZfEsMbDDmBYw2"
},
"Vyobk7hJu5OGzOe7E1fcYTbMvVI2" : {
"name" : "Gina C",
"postID" : "-LN2rl2414KAISO_qcK_",
"userID" : "Vyobk7hJu5OGzOe7E1fcYTbMvVI2"
}
},
}
}
And this is my object that's stored into array
struct PostWithFriends {
var postID : String?
var friends: [Friend]
}
class Friend : NSObject {
var friendName: String?
var friendUserID: String?
var postID: String?
init(friendName: String, friendPostID: String) {
self.friendName = friendName
self.postID = friendPostID
}
}
Replace this
if let friend = snap.value as? [String : AnyObject] {
}
With this:
for friendSnap in snap.children {
if let friendSnapshot = friendSnap as? FIRDataSnapshot {
let friendOnPost = FriendOnPost()
let friendDict = friendSnapshot.value as? [String: Any]
friendOnPost.name = friendDict?["name"] as? String
friendOnPost.friendUserID = friendDict?["userID"] as? String
friendOnPost.postID = friendDict?["postID"] as? String
// then do whatever you need with your friendOnPost
}
}

Having trouble trying to obtain value from Firebase

I'm trying to get a value inside a Firebase database I set up but I'm having issues trying get it out. There is a section in my program where I go through each child under Cities and obtain the the name of each city. I thought it would be a good idea to try to obtain the status for each city in the same section but I can't get it out. Below is a sample of the JSON object. I feel like I'm close but missing something to put it all together.
"Users" : {
"NPNBig20BXNpX4Rz0UbMyiKAarY2" : {
"Cities" : {
"New York City" : {
"Status" : "Empty"
},
"Atlanta" : {
"Status" : "Empty"
},
"Test City" : {
"Status" : "Wow",
"Value" : "Test"
}
},
"Email" : "fakeemail#gmail.com"
}
}
Code:
guard let uid = userID else { return }
let databaseRef = Database.database().reference(fromURL: "https://testApp.firebaseio.com/").child("Users").child(uid).child("Cities")
var dataTest : [String] = []
//var cityDictionary: [String:String]()
databaseRef.observeSingleEvent(of: .value, with: {(snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot
let key = snap.key
guard let value = snap.value else { return }
//let testData = value["Status"] **Type Any has no subscript
print("Key: ", key, "\nValue: ", value)
dataTest.append(key)
}
completion(dataTest)
})
This is the printed output
Key: New York City
Value: {
Status = Empty;
}
Key: Sintra
Value: {
Status = Empty;
}
Key: Test City
Value: {
Status = Wow;
Value = Test;
}
Here is the way you can get Status from your value:
if let value = snap.value as? [String: AnyObject] {
let Status = value["Status"] as? String ?? ""
}
And your complete code will be:
guard let uid = userID else { return }
let databaseRef = Database.database().reference(fromURL: "https://testApp.firebaseio.com/").child("Users").child(uid).child("Cities")
var dataTest : [String] = []
//var cityDictionary: [String:String]()
databaseRef.observeSingleEvent(of: .value, with: {(snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot
let key = snap.key
guard let value = snap.value else { return }
//let testData = value["Status"] **Type Any has no subscript
if let value = snap.value as? [String: AnyObject] {
let Status = value["Status"] as? String ?? ""
}
print("Key: ", key, "\nValue: ", value)
dataTest.append(key)
}
completion(dataTest)
})
func handleGetCities(_ completion: #escaping ([String]) -> ()) {
guard let uid = Auth.auth().currentUser?.uid else { return }
// OR
guard let uid = userID else { return }
// THEN
var data: [String] = []
let reference = Database.database().reference()
reference.child("Users").child(uid).child("Cities").observe(.childAdded, with: { (snapshot) in
let city_name = snapshot.key
data.append(city_name)
completion(data)
}, withCancel: nil)
}

Swift 3: How to retry Firebase upload on failure

I'm initializing some data in Firebase that I will use to track a user's activity. I need to make sure this data is written to Firebase so I'm wondering what the best practice is for ensuring a critical upload was successful?
static func createUserActivityCounts(uid: String) {
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + .seconds(5)) {
let databaseReference = Database.database().reference()
let userCounts: [String: Any] = ["posts": 0,
"comments": 0,
"likes": 0]
databaseReference.child("userActivity").child(uid).child("counts").setValue(userCounts) { (error, ref) -> Void in
if error != nil {
print(error!.localizedDescription)
}
}
}
}
I think the best way to resolve this issue is to check if the value exists whenever you attempt to query a critical upload. If the value isn't there then you initialize it. Here is the function I wrote to handle this.
private func getUserActivityCounts() {
if let uid = Auth.auth().currentUser?.uid {
userRef.child("userActivity").child(uid).child("counts").observeSingleEvent(of: .value, with: { snap in
if !snap.exists() {
// create user counts
if let uid = Auth.auth().currentUser?.uid {
DatabaseFunctions.createUserActivityCounts(uid: uid)
}
}
if let counts = snap.value as? [String: Any] {
if let numberOfLikes = counts["likes"] as? Int, let commentCount = counts["comments"] as? Int, let postCount = counts["posts"] as? Int {
DispatchQueue.main.async {
self.headerRef.postCount.text = String(postCount)
self.headerRef.commentCount.text = String(commentCount)
self.headerRef.likeCount.text = String(numberOfLikes)
self.collectionView.reloadData()
}
self.numberOfUserPosts = postCount
self.commentCount = commentCount
self.likeCount = numberOfLikes
}
}
})
}
}