Swift filtering firebase data by child node - swift

I'm trying to filter my firebase data within the table view to group items that have the same category (child value) after each other. Currently, it's not returning the values based on the child value. I think it's being done in random order.
Here is my code:
How I'm attempting to filter the data:
self.items.append(item)
self.items.sort(by: { (item1, item2) -> Bool in
return item1.category.compare(item2.category) == .orderedSame
})
self.tableView.reloadData()
Model:
struct Item {
var id: String?
var user: User
var fromId: String?
let item: String
let category: String
let creationDate: Date
init(user: User, dictionary: [String: Any]) {
self.user = user
self.item = dictionary["item"] as? String ?? ""
self.fromId = dictionary["fromId"] as? String ?? ""
self.category = dictionary["category"] as? String ?? ""
let secondsFrom1970 = dictionary["creationDate"] as? Double ?? 0
self.creationDate = Date(timeIntervalSince1970: secondsFrom1970)
}
}
Fetch function:
fileprivate func fetchListItems() {
self.tableView.refreshControl?.endRefreshing()
guard let uid = Auth.auth().currentUser?.uid else { return }
guard let listId = list?.id else { return }
let ref = Database.database().reference().child("lists").child(listId).child("list-items")
ref.observe(.childAdded) { (snapshot) in
let itemId = snapshot.key
let itemRef = Database.database().reference().child("items").child(itemId)
itemRef.observeSingleEvent(of: .value, with: { (snapshot) in
let itemId = snapshot.key
guard let dictionary = snapshot.value as? [String: Any] else { return }
guard let uid = dictionary["fromId"] as? String else { return }
Database.fetchUserWithUID(uid: uid, completion: { (user) in
var item = Item(user: user, dictionary: dictionary)
item.id = snapshot.key
self.items.append(item)
self.items.sort(by: { (item1, item2) -> Bool in
return item1.category.compare(item2.category) == .orderedSame
})
self.tableView.reloadData()
ref.keepSynced(true)
})
})
}
}
Database:

Related

Get data from firestore and assign it to an array of dictionaries

I am trying to get data from firestore collection and assign it to an array of dictionaries. for this part of the code below... i get the error "Cast from 'QuerySnapshot?' to unrelated type '[[String : Any]]' always fails" and the console prints "is not working".
guard let snap = snapshot as? [[String:Any]] else {
print("is not working")
completion(.failure(DatabaseError.failedToFetch))
return
}
Here is the full code.
// fetches and returns all conversations for the user with passed in uid
public func getAllConversations(for uid: String, completion: #escaping(Result<[Conversation], Error>) -> Void) {
print("fetching all convos")
//NEW
let db = Firestore.firestore()
let CurrentUser = Auth.auth().currentUser?.uid
let ListRef = db.collection("users").document(CurrentUser!).collection("conversations")
// fetch the current users convo list
ListRef.getDocuments { snapshot, error in
if let err = error {
debugPrint("Error fetching documents: \(err)")
} else {
guard let snap = snapshot as? [[String:Any]] else {
print("is not working")
completion(.failure(DatabaseError.failedToFetch))
return
}
print("is working")
let conversations: [Conversation] = snap.compactMap({ dictionary in
guard let id = dictionary["id"] as? String,
let name = dictionary["name"] as? String,
let otherUserUID = dictionary["other_user-uid"] as? String,
let latestMessage = dictionary["latest-message"] as? [String:Any],
let date = latestMessage["date"] as? String,
let message = latestMessage["message"] as? String,
let isRead = latestMessage["is-read"] as? Bool else {
return nil
}
//save other user ID to a global var
self.test = otherUserUID
//assign data into an array of dictionaries
let latestConvoObject = LatestMessage(date: date, text: message, isRead: isRead)
return Conversation(id: id, name: name, otherUserUid: otherUserUID, latestMessage: latestConvoObject)
})
completion(.success(conversations))
}
}
}
There are a numbers of way to read that data, and the process can be simplified by conforming objects to the codable protocol but let me provide a straight forward example. I don't know what your Conversation object looks like so here's mine
class ConversationClass {
var from = ""
var to = ""
var msg = ""
var timestamp = 0
convenience init(withDoc: DocumentSnapshot) {
self.init()
self.from = withDoc.get("from") as? String ?? "no from"
self.to = withDoc.get("to") as? String ?? "no to"
self.msg = withDoc.get("msg") as? String ?? "no msg"
self.timestamp = withDoc.get("timestamp") as? Int ?? 0
}
}
and then here's the the code that reads in all the conversation documents from a Collection, stores each in a ConversationClass object, puts those in an array and returns it through an escaping completion handler
func getConversations(completion: #escaping( [ConversationClass] ) -> Void) {
let conversationCollection = self.db.collection("conversations")
conversationCollection.getDocuments(completion: { snapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
guard let docs = snapshot?.documents else { return }
var convoArray = [ConversationClass]()
for doc in docs {
let convo = ConversationClass(withDoc: doc)
convoArray.append(convo)
}
completion(convoArray)
})
}

Fetched data from Firestore returns duplicates

I am trying to fetch data from firebase firestore. The problem i have is that my fetch is returning the results x4 times. For example when i do print(name) it print the users name x4 times.
I think there may be a loop that is not working correctly?
// fetches and returns all conversations for the user with passed in uid
public func getAllConversations(for uid: String, completion: #escaping(Result<[Conversation], Error>) -> Void) {
let CurrentUser = Auth.auth().currentUser?.uid
let db = Firestore.firestore()
let ConversationRef = db.collection("users").document(CurrentUser!).collection("conversations").document(
"jVymlfbpuAYQQ9Brf8SbUZ7KCGg1")
// get the otherUserUId TO DO
ConversationRef.getDocument { snapshot, error in
if error != nil {
print("Error connecting to database")
} else {
if let document = snapshot {
if document.exists {
let data = document.data()
print(data)
let conversations: [Conversation] = data!.compactMap ({ dictionary in
guard let conversationId = data!["id"] as? String,
let name = data!["name"] as? String,
let otherUserUid = data!["other_user-uid"] as? String,
let latestMessage = data!["latest-message"] as? [String:Any],
let date = latestMessage["date"] as? String,
let message = latestMessage["message"] as? String,
let isRead = latestMessage["is-read"] as? Bool
else {
return nil
}
print(name)
let latestMessageObject = LatestMessage(date: date, text: message, isRead: isRead)
return Conversation(id: conversationId, name: name, otherUserUid: otherUserUid, latestMessage: latestMessageObject)
})
completion(.success(conversations))
}
else {
completion(.failure(DatabaseError.failedToFetch))
return
}
}
}
}
}
Please note that ConversationRef.getDocument{..} will only Return One Specific Document, which you’re Referring here :
let ConversationRef = db.collection("users").document(CurrentUser!).collection("conversations").document("jVymlfbpuAYQQ9Brf8SbUZ7KCGg1”)
So the let data = document.data()
will be single [String:Any] object(in this case Single ‘Conversation’),
not the Array of Dictionaries(eg: [Conversations]).
Try doing it this way:
// fetches and returns all conversations for the user with passed in uid
public func getAllConversations(for uid: String, completion: #escaping(Result<Conversation, Error>) -> Void) {
let CurrentUser = Auth.auth().currentUser?.uid
let db = Firestore.firestore()
let ConversationRef = db.collection("users").document(CurrentUser!).collection("conversations").document(
"jVymlfbpuAYQQ9Brf8SbUZ7KCGg1")
// get the otherUserUId TO DO
ConversationRef.getDocument { snapshot, error in
if error != nil {
print("Error connecting to database")
} else {
if let document = snapshot {
if document.exists {
if let data = document.data() {
if let conversationId = data["id"] as? String,
let name = data["name"] as? String,
let otherUserUid = data["other_user-uid"] as? String,
let latestMessage = data["latest-message"] as? [String:Any],
let date = latestMessage["date"] as? String,
let message = latestMessage["message"] as? String,
let isRead = latestMessage["is-read"] as? Bool {
print(name)
let latestMessageObject = LatestMessage(date: date, text: message, isRead: isRead)
let conversations = Conversation(id: conversationId, name: name, otherUserUid: otherUserUid, latestMessage: latestMessageObject)
completion(.success(conversations))
}
}
}
else {
completion(.failure(DatabaseError.failedToFetch))
return
}
}
}
}
}
// fetches and returns all conversations for the user with passed in uid
public func getAllConversations(for uid: String, completion: #escaping(Result<[Conversation], Error>) -> Void) {
let CurrentUser = Auth.auth().currentUser?.uid
let db = Firestore.firestore()
let ConversationRef = db.collection("users").document(CurrentUser!).collection("conversations")
ConversationRef.addSnapshotListener { snapshot, error in
if error != nil {
print("Error connecting to database")
} else {
guard let snap = snapshot else {
completion(.failure(DatabaseError.failedToFetch))
return
}
for document in snap.documents {
let data = document.data()
print(data)
guard let conversationId = data["id"] as? String,
let name = data["name"] as? String,
let otherUserUid = data["other_user-uid"] as? String,
let latestMessage = data["latest-message"] as? [String:Any],
let date = latestMessage["date"] as? String,
let message = latestMessage["message"] as? String,
let isRead = latestMessage["is-read"] as? Bool else {
return
}
print(name)
let latestMessageObject = LatestMessage(date: date, text: message, isRead: isRead)
let conversations = [Conversation(id: conversationId, name: name, otherUserUid: otherUserUid, latestMessage: latestMessageObject)]
completion(.success(conversations))
}
}
}
}

Problem fetching data from firebase by using struct file

struct UserClass {
var babyName: String!
var babyHeight: String!
var babyWeight: String!
var babyURL: String!
var uid: String!
var reference:DatabaseReference!
var key: String!
init?(snapshot: DataSnapshot?) {
guard let value = snapshot?.value as? [String:AnyObject],
let uid = value["uid"] as? String,
let babyName = value["BabyName"] as? String,
let babyURL = value["BabyURL"] as? String,
let babyHeight = value["BabyHeight"] as? String,
let babyWeight = value["BabyWeight"] as? String else {
return nil
}
self.key = snapshot?.key
self.reference = snapshot?.ref
self.uid = uid
self.babyURL = babyURL
self.babyName = babyName
self.babyHeight = babyHeight
self.babyWeight = babyWeight
}
func getuserData() -> String {
return ("BabyName = \(babyName)")
}
}
func fetchCurrentUserInfo() {
var currentUserRef = Database.database().reference().child("Users").child("\(userID)")
handler = currentUserRef.queryOrderedByKey().observe(DataEventType.value, with: { (snapshot) in
print("User data = \(snapshot.value)")
let user = UserClass(snapshot: snapshot)
print(user?.babyName)
self.babyName.text = user?.babyName
})
}
I am getting user data but not user.babyName. How can I fix this?
May be this will help you, as the db structure is not mentioned in question. but you have to iterate children one by one and then use for loop to fetch the exact data from firebase.
reference = FIRDatabase.database().reference()
reference.child("Users").queryOrderedByKey().observe(DataEventType.value, with: { (snapshot) in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshots
{
let userId = child.childSnapshot(forPath: "userID").value! as! String
print(userId)
}
}
})

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.

Posts Being Uploaded Randomly in Collection View - Swift & Firebase

I have been refactoring my code and now I'm having trouble with the posts.
Whenever I add a new post to the collection view, it is being added in a random cell and out of order, instead of in the first post.
I know the reason is the fetchuser function and from what I'm being told due to the asynchronous loading, but don't know what to do in order to correct this.
Could someone help me figure out what to do so that my posts are added in the first cell?
#objc func observePostsAdoption() {
let postsRef = Database.database().reference().child("posts")
postsRef.queryOrdered(byChild: "postType").queryEqual(toValue: "adopt").observe(.value) { (snapshot) in
var tempPost = [Posts]()
for child in snapshot.children {
if let childSnapshot = child as? DataSnapshot {
let dict = childSnapshot.value as? [String: Any]
let newAdoptiondPost = Posts.transformPost(dict: dict!)
//This will look up all users at once
self.fetchUser(userid: newAdoptiondPost.userid!, completed: {
tempPost.insert(newAdoptiondPost, at: 0)
DispatchQueue.main.async {
self.postsadoption = tempPost
self.adoptionCollectionView.reloadData()
self.refresherAdoption.endRefreshing()
}
})
}
}
}
}
func fetchUser(userid: String, completed: #escaping ()-> Void ) {
Database.database().reference().child("users").child(userid).observeSingleEvent(of: .value) { (snapshot) in
if let dict = snapshot.value as? [String: Any] {
let user = UserProfile.transformUser(dict: dict)
self.users.insert(user, at: 0)
completed()
}
}
}
Here's my Post Struct
class Posts {
//UserView
var uid: String?
var author: UserProfile?
var timestamp: Date?
var userid: String?
func getDateFormattedString() -> String {
let formatter = DateFormatter()
formatter.dateFormat = "MMM d, HH:mm"
return formatter.string(from: self.timestamp!)
}
//Image
var photoUrl: URL?
//PostInformation View
var city: String?
var municipality: String?
var name: String?
var breed : String?
var phone : String?
var address : String?
var petType: String?
var genderType: String?
var comments: String?
}
extension Posts {
static func transformPost(dict: [String: Any]) -> Posts {
let post = Posts()
//Post Picture
let photoUrl = dict["photoUrl"] as? String
post.photoUrl = URL(string: photoUrl!)
//INFO POSTS
post.userid = dict["userid"] as? String
post.city = dict["city"] as? String
post.municipality = dict["municipality"] as? String
post.name = dict["name"] as? String
post.breed = dict["breed"] as? String
post.phone = dict["phone"] as? String
post.address = dict["address"] as? String
post.comments = dict["comments"] as? String
post.petType = dict["petType"] as? String
post.genderType = dict["gender"] as? String
let timestamp = dict["timestamp"] as? Double
post.timestamp = Date(timeIntervalSince1970: timestamp!/1000)
return post
}
}
If you already have the posts ordered by post type you can just do sorting depending on the timestamp. For example
#objc func observePostsAdoption() {
let postsRef = Database.database().reference().child("posts")
postsRef.queryOrdered(byChild: "postType").queryEqual(toValue: "adopt").observe(.value) { (snapshot) in
var tempPost = [Posts]()
for child in snapshot.children {
if let childSnapshot = child as? DataSnapshot {
let dict = childSnapshot.value as? [String: Any]
let newAdoptiondPost = Posts.transformPost(dict: dict!)
//This will look up all users at once
self.fetchUser(userid: newAdoptiondPost.userid!, completed: {
tempPost.insert(newAdoptiondPost, at: 0)
DispatchQueue.main.async {
self.postsadoption = tempPost
self.postsadoption.sort { (p1, p2) -> Bool in
return p1.timeStamp?.compare(p2.timeStamp!) == .orderdDescending
}
self.adoptionCollectionView.reloadData()
self.refresherAdoption.endRefreshing()
}
})
}
}
}
}
With that the posts adoption array will be sorted depending on the timestamp that you have.