I am trying to have my program add every instance of a child to an array but whenever I delete a child then add another it duplicates that added child twice and if I do it again it will duplicate 3 times and so on depending on how many times I delete and add. The adding and deleting is mainly handled in the function below and I can't figure out why it is duplicating them.
func fetchContacts(completion: #escaping () -> ()){
contacts = [Contact]()
let userRef = ref.child("users").child(user).child("contacts")
userRef.observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject]{
print(snapshot)
let contact = Contact()
contact.setValuesForKeys(dictionary)
self.contacts.append(contact)
}
self.contactsTable.reloadData()
completion()
}, withCancel: nil)
userRef.observe(.childRemoved, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject]{
let contact = Contact()
contact.setValuesForKeys(dictionary)
if let i = self.contacts.index(where: { $0.name == contact.name }) {
self.contacts.remove(at: i)
}
}
self.contactsTable.reloadData()
completion()
}, withCancel: nil)
}
Here is where the deleting is handled and how the functions are called in the viewDidLoad:
override func viewDidLoad() {
contactsTable.delegate = self
contactsTable.dataSource = self
contactsTable.rowHeight = 65
super.viewDidLoad()
fetchContacts(){
self.contactsTable.reloadData()
}
}
func handleDelete(phone: String, completion: #escaping () -> ()){
let userRef = ref.child("users").child(user).child("contacts")
userRef.child(phone).removeValue { (error, ref) in
if error != nil {
print("Error: \(error)")
}
completion()
}
}
This may be to do with you not calling reloadData() on the main thread.
Instead of just:
self.contactsTable.reloadData()
try:
DispatchQueue.main.async {
self.contactsTable.reloadData()
}
Related
so far I have always worked with the data i am retrieving from firebase without having to do something with them besides showing.
Now I would actually need to store the data in another array and/ or also in general I really wonder how to actually work with the data.
My approach right now looks like this but its actually not working.
Does anyone know how to do it?
....
class ProjectCharacterViewController: UIViewController {
// MARK: - Properties
var soloJobs: [String] = []
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
getJobs(for: User.current) { (memberJob) in
self.uniqueJobs = memberJob
}
}
...
func getJobs(for user: User, completion: #escaping ([MemberJobsStruct]) -> Void) {
var jobs: [String] = []
let ref = Database.database().reference().child("team").child(user.uid)
ref.observe(DataEventType.value, with: { snapshot in
for case let child as DataSnapshot in snapshot.children {
guard let value = child.value as? [String: Any] else {
return completion ([])
}
let memberJob = value["memberJob"] as! String
jobs.append(memberJob)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
})
soloJobs = jobs
}
Since the observe function is an asynchronous call, your soloJobs = jobs is getting called before it. That is why it is returning an empty array, because, at that time, your jobs array is also empty. Consider calling it inside your for loop as so:
func getJobs(for user: User, completion: #escaping ([MemberJobsStruct]) -> Void) {
var jobs: [String] = []
let ref = Database.database().reference().child("team").child(user.uid)
ref.observe(DataEventType.value, with: { snapshot in
for case let child as DataSnapshot in snapshot.children {
guard let value = child.value as? [String: Any] else {
return completion ([])
}
let memberJob = value["memberJob"] as! String
jobs.append(memberJob)
self.soloJobs = jobs
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
})
}
Im trying to call data from firebase. The problem is, the data is deeply nested and I don't think I can change that.
So I'm attempting to call values from firebase, which I can then use to reference new values.
The problem arises when my for loop is not finished before the next stage is called, meaning my dictionary count for the next stage is 0, so my next function is not called?
Is there a way to do this sufficiently?
Please help?
Heres my code:
func fetchBuyer(search: String, user: String, completion: #escaping ([Post]) -> (), withCancel cancel: ((Error) -> ())?) {
let ref = Database.database().reference().child("posts").child(user).child(search).child("purchases")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionaries = snapshot.value as? [String: Any] else {
completion([])
return
}
let keys: [String] = dictionaries.map({ $0.key })
var newdictionaries = [String: String]()
for i in keys {
let newref = Database.database().reference().child("posts").child(user).child(search).child("purchases").child(i).child("purchaser")
newref.observeSingleEvent(of: .value, with: { (snapshot) in
newdictionaries[i] = snapshot.value as? String
print("THESE ARE MY PURCHASES ID-->", newdictionaries.values)///prints out ["-M0pTHtZXYUVQT7DCLj-", "-M0pU79uQCCnBunAEkJN"]
})
}
var buyerPosts = [Post]()
print("newdictionaries.count--->", newdictionaries.count)//this print is 0
newdictionaries.forEach({ (postId, value) in
Database.database().fetchPost(withUID: user, postId: postId, completion: { (post) in
buyerPosts.append(post)
if buyerPosts.count == newdictionaries.count{
completion(buyerPosts)
}
})
})
}) { (err) in
print("Failed to fetch posts for buyers:", err)
cancel?(err)
}
}
Attempted answer:
let g = DispatchGroup() //// 1
for i in keys{
g.enter() //// 2
let newref = Database.database().reference().child("posts").child(user).child(search).child("purchases").child(i).child("purchaser")
print("now")
newref.observeSingleEvent(of: .value, with: { (snapshot)
newdictionaries[i] = snapshot.value as? String
print("print new dictionaries-->", newdictionaries)
// complete here
Database.database().fetchPost(withUID: user, postId: newdictionaries[i]!, completion: { (post) in
buyerPosts.append(post)
g.leave() //////// 3
})
})
}
g.notify(queue: DispatchQueue.main) {
print("finished!!!")
completion(buyerPosts)
}
You need a dispatch group and nest the calls
let g = DispatchGroup() //// 1
for i in keys{
g.enter() //// 2
let newref = Database.database().reference().child("posts").child(user).child(search).child("purchases").child(i).child("purchaser")
newref.observeSingleEvent(of: .value, with: { (snapshot)
newdictionaries[i] = snapshot.value as? String
// complete here
Database.database().fetchPost(withUID: user, postId: postId, completion: { (post) in
buyerPosts.append(post)
g.leave() //////// 3
})
})
}
/// 4
g.notfiy(queue.main) {
completion(buyerPosts)
}
I have created a messaging system for my app and am paginating the messages within the chat log but I'm having an issue that if a new message is sent the user will have to leave the screen and re open the controller to view the new messages they have sent/received. I have tried to reload the collection view and observe the messages again with no luck. Any help is appreciated.
Observing the messages. With Pagination. (working great! On initial load.)
var messages = [Message]()
fileprivate func observeMessages() {
guard let uid = Auth.auth().currentUser?.uid else { return }
guard let userId = user?.uid else { return }
if currentKey == nil {
let userMessageRef = Database.database().reference().child("user-message").child(uid).child(userId).queryLimited(toLast: 10).observeSingleEvent(of: .value) { (snapshot) in
guard let first = snapshot.children.allObjects.first as? DataSnapshot else { return }
guard var allObjects = snapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.forEach({ (snapshot) in
let messageId = snapshot.key
let ref = Database.database().reference().child("messages").child(messageId)
ref.observe(.value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
let message = Message(dictionary: dict)
self.messages.append(message)
self.messages.sort(by: { (message1, message2) -> Bool in
return message1.timeStamp.compare(message2.timeStamp) == .orderedDescending
})
self.collectionView?.reloadData()
})
})
self.currentKey = first.key
}
} else {
let userMessageRef = Database.database().reference().child("user-message").child(uid).child(userId).queryOrderedByKey().queryEnding(atValue: self.currentKey).queryLimited(toLast: 4).observeSingleEvent(of: .value) { (snapshot) in
guard let first = snapshot.children.allObjects.first as? DataSnapshot else { return }
guard var allObjects = snapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.forEach({ (snapshot) in
if snapshot.key != self.currentKey {
let messageId = snapshot.key
let ref = Database.database().reference().child("messages").child(messageId)
ref.observe(.value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
let message = Message(dictionary: dict)
self.messages.append(message)
self.messages.sort(by: { (message1, message2) -> Bool in
return message1.timeStamp.compare(message2.timeStamp) == .orderedDescending
})
self.collectionView?.reloadData()
})
}
})
self.currentKey = first.key
}
}
}
From Firebase database documentation
In some cases you may want a callback to be called once and then immediately removed, such as when initializing a UI element that you don't expect to change. You can use the observeSingleEventOfType method to simplify this scenario: the event callback added triggers once and then does not trigger again.
I suggest you to change to observeEventType:withBlock whichs allow you to observe all changes events.
Hope this helps.
The way I set mine up was to call the function in viewDidLoad and then again in viewDidAppear. I'm still learning as well, but you may want to try that, it would probably look something like this:
override func viewDidLoad() {
super.viewDidLoad()
observeMessages(for: userID) { (messages) in
self.messages = messages
self.collectionView.reloadData()
}
}
And again in viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
observeMessages(for: userID) { (messages) in
self.messages = messages
self.collectionView.reloadData()
}
}
I have don't know what the problem please help me. When I get a particular message from firebase database then the value is getting but my app got a crash on one line.So please tell me what I do wrong in my code.below is my function.We also provide the screenshot of the error.
func getLatestMessageFromFirebase(token:String,completionmessage: #escaping (_ message:String) -> Swift.Void)
{
print("getModelFromFirebase")
var message:String=""
ref.child("chatmessage/devicetoken/").child(token).queryLimited(toLast: 1).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
if value?["message"] as? String != ""
{
DispatchQueue.main.async
{
message = (value?["message"] as? String)! //My app stop on this line
completionmessage(message)
}
}
})
{ (error) in
print(error.localizedDescription)
}
}
func callAPI()
{
let response = (jsonResult.object(forKey: "chatListArr") as? NSArray)!
if response.count > 0
{
for i in 0..<response.count
{
let dict = response[i] as! NSDictionary
let chatlist = ChatList(dict: dict)
self.arr_list.append(chatlist)
}
for i in 0..<self.arr_list.count
{
let chatlist = self.arr_list[i]
self.getLatestMessageFromFirebase(token: chatlist.token, completionmessage: { (message) in
self.arr_list[i].msg = message
})
}
self.table_view.reloadData()
}
}
Please help me.
Thanks in Advance.
First of all you should clean your code up a bit, you do a couple of things which would be considered anti patterns
func getLatestMessageFromFirebase(token:String,completionmessage: #escaping (_ message:String) -> Swift.Void)
{
ref.child("chatmessage/devicetoken/").child(token).queryLimited(toLast: 1).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
for snap in snapshot.children.allObjects as [DataSnapshot] {
let value = snap.value as? [String: Any] ?? [:] // A good way to unwrap optionals in a single line
if let message = value["message"] as? String {
DispatchQueue.main.async {
completionmessage(message)
}
}
}
})
{ (error) in
print(error.localizedDescription)
}
}
With the above code your app shouldnt crash. And if there is a message AND it is a string (which might have been your problem before) then your callback will fire.
Is it possible to fetch all child nodes from parent in firebase db?
Trying to say this:
I want to get all posts at the same time.
It fetches videos but only the current users videos. I want to fetch ALL users videos at the same time.
Here's some code to get more understanding of what I'm doing:
fileprivate func fetchAllPost() {
fetchPosts()
fetchAllPostsFromUserIds()
}
fileprivate func fetchAllPostsFromUserIds() {
guard let uid = FIRAuth.auth()?.currentUser?.uid else { return }
FIRDatabase.database().reference().child("posts").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
guard let userIdsDictionary = snapshot.value as? [String: Any] else { return }
userIdsDictionary.forEach({ (key, value) in
FIRDatabase.fetchUserWithUid(uid: key, completion: { (user) in
self.fetchPostsWithUser(user: user)
})
})
}) { (err) in
print("failed to fetch following users ids:", err)
}
}
var posts = [Post]()
fileprivate func fetchPosts() {
guard let currentUserID = FIRAuth.auth()?.currentUser?.uid else { return }
FIRDatabase.fetchUserWithUid(uid: currentUserID) { (user) in
self.fetchPostsWithUser(user: user)
}
}
fileprivate func fetchPostsWithUser(user: User) {
let ref = FIRDatabase.database().reference().child("posts/\(user.uid)/")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
self.collectionView?.refreshControl?.endRefreshing()
guard let dictionaries = snapshot.value as? [String: Any] else { return }
dictionaries.forEach({ (key,value) in
guard let dictionary = value as? [String: Any] else { return }
var post = Post(user: user, dictionary: dictionary)
post.id = key
guard let uid = FIRAuth.auth()?.currentUser?.uid else { return }
FIRDatabase.database().reference().child("likes").child(key).child(uid).observe(.value, with: { (snapshot) in
if let value = snapshot.value as? Int, value == 1 {
post.hasLiked = true
} else {
post.hasLiked = false
}
self.posts.append(post)
self.posts.sort(by: { (p1, p2) -> Bool in
return p1.creationDate.compare(p2.creationDate) == .orderedDescending
})
self.collectionView?.reloadData()
}, withCancel: { (err) in
print("Failed to fetch info for post")
})
print(self.posts)
})
}) { (error) in
print("Failed to fetch posts", error)
}
}
I don't know Swift, but you could fetch FIRDatabase.database().reference().child("posts") and then iterate over children.