Removing the Firebase Observer does not work - swift

I load some data to show them on a tableView. I try to remove the observer for this. Right now the observer shows duplicate content. It shows the a post 10-15 times.
This is how I observe:
func loadData(){
let placeIdFromSearch = ViewController.placeidUebertragen
ref = Database.database().reference().child("placeID/\(placeIdFromSearch)/\(ViewComments.subComment)/subcomment/")
ref.observe(.childAdded) { (snapshot) in
print("something changed")
guard let dic = snapshot.value as? [String: Any] else { return }
let newPost = importSubPosts(dictionary: dic, key: snapshot.key)
guard let userUid = newPost.userID else { return }
self.fetchUser(uid: userUid, completed: {
self.table.insert(newPost, at: 0)
self.tableView.reloadData()
})
}
}
func fetchUser(uid: String, completed: #escaping () -> Void) {
ref = Database.database().reference().child("user").child(uid)
ref.observe(.value) { (snapshot) in
guard let dic = snapshot.value as? [String: Any] else { return }
let newUser = UserModel(dictionary: dic)
self.users.insert(newUser, at: 0)
completed()
}
}
This should remove the observer:
ref = Database.database().reference().child("placeID/\(placeIdFromSearch)/\(ViewComments.subComment)/subcomment/")
ref.removeAllObservers()
But nothing happens.
PS: If it helps if I deactivate this part the error goes away. But then I don't have user profile picture. Is It because I use this func already on a different tableView?
ref.observe(.value) { (snapshot) in
guard let dic = snapshot.value as? [String: Any] else { return }
let newUser = UserModel(dictionary: dic)
self.users.insert(newUser, at: 0)

The Firebase documentation provides a number of examples of how to read and write data
In this case it appears there are a couple of issues;
One is how to implement listeners:
.childAdded - This event is triggered once for each existing child and
then again every time a new child is added to the specified path
ChildAdded is great for initially loading a list of data e.g. several child nodes and watching for additions to that list thereafter. If you want to load the data and then stop listening, you can remove the listener from inside the .childAdded closure (there are other options as well)
If you're interested in loading just a single node, 'subcomment' see the load single node section below
If, however subcomment should be plural 'subcomments' because there are several, you can user this pattern to load them without leaving an observer
let usersRef = my_firebase.child("users")
usersRef.observeSingleEvent(by: .value, with: { snapshot in
//all users are returned to the app in the snapshot
// .value means 'give me everything in the specified node'
// so then we can iterate over the snapshot to get each users data
}
Load single node -
The second issue which ties to the first issue within the 'fetchUser' function - it appears you want to read in a single user from Firebase, one time.
While you can use .observe to do that, it actually reads in ALL data and listens for all events ongoing which doesn't appear like you want to do.
A better solution is to read the user one time with observeSingleEvent aka getData that reads it in and doesn't leave a listener - therefore eliminating the need to remove observers when the read is done.
There's a perfect example in the documentation that you could almost copy and paste the code - it reads a single user, and doesn't leave an observer. It could be used for reading the subcomment as well without leaving a listener attached. See the code at Read Data Once
ref.child("users/\(uid)/username").getData(completion: { error, snapshot in
guard error == nil else {
print(error!.localizedDescription)
return;
}
let userName = snapshot.value as? String ?? "Unknown";
});

Related

Firebase: cannot retrieve children if they are less than the specified number in queryLimited

I have the following function that gets invoked when a user scrolls conversation tableview, it works well but if the remaining children are less than the specified number it retrieves none! What is more interesting is that those values appear in print(snapshot.value) but not in print(child). How can I get all nodes even if they are less than the specified number?
Thank you.
func fetchBatch(betweenUser userID: String, fromTimestamp: Double , completion: #escaping([Message]) -> ()){
guard let currentUID = Auth.auth().currentUser?.uid else { return }
var messages = [Message]()
REF_MESSAGES.child(currentUID).child(userID).queryOrdered(byChild: "timestamp").queryEnding(atValue: fromTimestamp).queryLimited(toLast: 20).observeSingleEvent(of: .value) { snapshot in
print(snapshot.value)
for child in snapshot.children {
print(child)
if let childSnapshot = child as? DataSnapshot {
guard let dictionary = childSnapshot.value as? [String: AnyObject] else {return}
let message = Message(dictionary: dictionary)
messages.append(message)
}
}
return completion(messages)
}
}
Just a guess but you may have a problem with your data structure.
That guard statement
guard let dictionary = childSnapshot.value as? [String: AnyObject] else {return}
will prevent the messages array from being fully populated when it fails.
My guess is your retrieving say, 10 child notes, then as the code iterates over them, at some point the guard statement fails due to the structure. For example a [Int: Any] instead of a [String: Any]
The end result is that not all of the child nodes are not added to the array - which in turn means there's less elements in the array than was was actually retrieved from Firebase.

Could not cast value of type 'FIRDatabaseReference' (0x108f4d170) to 'NSString' (0x10a4f24a8)

I have a database reference that I want to change to a String and then to a URL type.
let database = Database.database().reference().child("users/profile/\(uid!)/pathToImage")
let string = database as! String // **Code Crashes here
let url = URL (string: string)!
func setImage() {
ImageService.downloadImage(withURL: url) { image in
self.currentProfileImage.image = image
}
}
setImage()
This
let database = Database.database().reference().child("users/profile/\(did!)/pathToImage")
isn't a type of a direct url string , it's a refrence to your image , you need to single listen to it
database.observeSingleEvent(of: .value, with: { (snapshot) in
let str = snapshot.value as! String
print(str)
})
It sounds like you might be confused about how the Realtime Database SDK works. If you have a database reference, and you want the value of that location of the database, it's not going to work to simply convert that reference to a string. You have to query the database at that location, use a callback to receive the data, then get the string out of the snapshot that was delivered to the callback.
I suggest going over the documentation for how to perform database queries in order to understand how it works. In particular, pay attention to the section called read data once. Your code will look more like this:
let ref = ref.child("path/to/string")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? String
// use the value here
}) { (error) in
print(error.localizedDescription)
}

Firebase database doesn't stop updating

I was trying to update my Firebase database and I ran into this problem. Take a look at the following code snippet and the screenshot:
func saveRetrieveStoryID(completion: #escaping (Bool) -> Void) {
let userID = Auth.auth().currentUser?.uid
//Create a reference to the database
let DBRef = Database.database().reference()
let storyIDRef = DBRef.child("Story IDs").child(userID!)
storyIDRef.observe(.value) { (snapshot) in
for childOne in snapshot.children {
print(childOne)
if let childOneSnapshot = childOne as? DataSnapshot {
storyIDKeyList.append(Int(childOneSnapshot.key)!)
print(childOneSnapshot.key)
completion(true)
}
}
print(storyIDKeyList)
}
}
What the code does is that it retrieves the key (-1) from the database and stores it inside a list (storyIDKeyList). Now take a look at the following code snippet:
saveRetrieveStoryID { (saved) in
if saved {
// Store the story ID in the user's story ID dict
let storyIDRef = DBRef.child("Story IDs").child(userID!)
let newStoryIDKey = storyIDKeyList.last! + 1
storyIDs[String(newStoryIDKey)] = storyRef.key
storyIDRef.updateChildValues(storyIDs, withCompletionBlock: { (error, ref) in
if let error = error?.localizedDescription {
print("Failed to update databse with error: ", error)
}
})
}
}
This piece of code, takes the last item from the storyIDKeyList and adds 1 to it. Then this will be added to the storyIDs dictionary storyIDs[String(newStoryIDKey)] = storyRef.key and the database will be update with the new key and value. But the problem is that, the database keeps on updating and it doesn't stop until I stop running the code. Here is a picture of the resulting database:
Notice that all the values are the same. This following screenshot should be the expected outcome:
I just want to add one key/value to the database each time I run the code; I kind of know why this is happening but I'm finding it difficult to solve this problem.
After a lot of tries, I managed to find a solution to this problem.
Edit: I found a better solution, thanks to this answer: Android Firebase Database keeps updating value. Using observeSingleEvent() retrieves the data only once.
Here is the code (Better answer IMO):
func saveRetrieveStoryID(completion: #escaping (Bool) -> Void) {
let userID = Auth.auth().currentUser?.uid
let storyIDRef = DBRef.child("Story IDs").child(userID!)
storyIDRef.observeSingleEvent(of: .value) { (snapshot) in
for childOne in snapshot.children {
if let childOneSnapshot = childOne as? DataSnapshot {
storyIDKeyList.append(Int(childOneSnapshot.key)!)
}
}
completion(true)
}
}
Old answer (Works too):
func saveRetrieveStoryID(completion: #escaping (Bool) -> Void) {
let userID = Auth.auth().currentUser?.uid
let storyIDRef = DBRef.child("Story IDs").child(userID!)
storyIDRef.observe(.value) { (snapshot) in
for childOne in snapshot.children {
if let childOneSnapshot = childOne as? DataSnapshot {
storyIDKeyList.append(Int(childOneSnapshot.key)!)
}
}
storyIDRef.removeAllObservers()
completion(true)
}
}

Waiting until a certain task completely finish to execute another task

I'm trying to retrieve some encryption messages from Firebase Realtime Database, decrypt them, and display them in the CollectionView. The decrypting process is successful, but I have faced a problem about multithreading that: The order of the fetched-decrypted messages added to the Messages array is wrong, so they are not displayed in the CollectionView with the correct order, the order of displaying message in CollectionView varies during each run. I thought this problem happens because the time needed to finish decrypting process of each encrypted message is different, some encrypted messages need more time to decrypt, and some encrypted messages finish decrypting before others, so the order that they are added to the Messages array is no longer correct. The workflow that I expect that:
Making fetch request to the messages node on Firebase Database
With each fetched message:
3.1. Decrypt it
3.2. Append it to the Messages array
3.3. Reload the CollectionView to update UI
But I don't know how to use GCD to achieve that correctly, the showing messages is not in the correct order because the concurrency problem. But, I found a solution that if I try to place a sleep(1) command to my code, the code run correctly, but it's too slow because of sleeping command. I tried many ways, but it doesn't seem right, except for using sleep(1) command. Please help me to do this properly, thank you so much!. Here is my code:
func observeMessage(){
self.eThree = VirgilHelper.sharedVirgilHelper.eThreeToUse!
// Get current user's UID
guard let uid = FIRAuth.auth()?.currentUser?.uid , let toId = self.user?.id else {
return;
}
let userMessagesRef = FIRDatabase.database().reference().child("user-messages").child(uid).child(toId);
userMessagesRef.observe(.childAdded, with: { (snapshot) in
let messageId = snapshot.key;
let messagesRef = FIRDatabase.database().reference().child("messages").child(messageId);
// Observe the entire value of that node
messagesRef.observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String:AnyObject] {
//sleep(1) // The working sleep command, but it's too slow
let message = Message(dictionary: dictionary)
if let fromUID = message.fromId, let toUID = message.toId, let cipherText = message.text {
self.eThree!.lookupPublicKeys(of: [fromUID], completion: { (lookupResult, error) in
if error != nil {
print("Error when looking up the Public Key of UID \(fromUID), \(String(describing: error))")
}
if let lookupResult = lookupResult {
message.text = try! self.eThree!.decrypt(text: cipherText, from: lookupResult[fromUID]!)
print("text: \(message.text)")
// The concurency prolem happens at here
self.messages.append(message);
// Go back to main thread to update UI
DispatchQueue.main.async {
// The concurency prolem happens at here, UI doesn't display with correct order of fetched-decrypted messages
self.collectionView?.reloadData()
let indexPath = IndexPath(item: self.messages.count-1, section: 0)
self.collectionView?.scrollToItem(at: indexPath, at: .bottom, animated: true);
}
}
})
}
}
}, withCancel: nil)
}, withCancel: nil)
}
In Swift, to wait for another task to be done before continue on with other task, you can use DispatchQueue.group().
let group = DispatchGroup()
group.enter()
DispatchQueue.main.async {
print("1")
group.leave()
}
group.enter()
DispatchQueue.main.async {
print("2")
group.leave()
}
group.enter()
DispatchQueue.main.async {
print("3")
group.leave()
}
group.notify(queue: .main) {
print("Done")
}
So the way you use it:
Initialize group
Enter the group by: group.enter(), before you start task
Put: group.leave(), after each task
Pass closure to group.notify. It will be executed when group task is empty.
NOTE:
A number of .enter() needs to match with .leave()

iOS writing to Firebase leads to crash

I wonder if my code is thread safe, in tableView(_ tableView:, leadingSwipeActionsConfigurationForRowAt indexPath:) I create an action that accepts a friend request. The method is invoked from a blok of UIContextualAction(style: .normal, title: nil) { (action, view, handler) in }
The actual Firebase call is like this:
class func acceptInvite(uid: String, completion: #escaping (Bool)->Void) {
guard let user = currentUser else { completion(false); return }
usersRef.child(user.uid).child("invites").queryEqual(toValue: uid).ref.removeValue()
usersRef.child(user.uid).child("friends").childByAutoId().setValue(uid)
usersRef.child(uid).child("friends").childByAutoId().setValue(user.uid)
completion(true)
}
image from debug navigator
It would be great if someone had an explanation.
edit: I think the problem is in my async loop to get the userdata
class func get(type: String, completion: #escaping ([Friend])->Void) {
let usersRef = Database.database().reference().child("users")
guard let user = currentUser else { completion([]); return }
usersRef.child(user.uid).child(type).observe(.value){ (snapshot) in
guard let invitesKeyValues = snapshot.value as? [String: String] else { completion([]); return }
var optionalFriendsDictArray: [[String: Any]?] = []
let dispatchGroup = DispatchGroup()
for (_, inviteUID) in invitesKeyValues {
dispatchGroup.enter()
usersRef.child(inviteUID).observe(.value, with: { (snapshot) in
let friend = snapshot.value as? [String: Any]
optionalFriendsDictArray.append(friend)
dispatchGroup.leave()
})
}
dispatchGroup.notify(queue: DispatchQueue.global(), execute: {
let friends = optionalFriendsDictArray.flatMap({ (optional) -> Friend? in
Friend.init(userDictionary: optional)
})
completion(friends)
})
}
}
This problem really gets me thinking about Firebase usage. I could add more information about the user at the friends key of a user so you don't have to query all the user to populate a small list with a name and a photo.
But what about viewing your friends posts on your timeline, your definitely not going to copy every friends' post into the users object. ???
I solved this problem by fetching the data with an observe single event and using the childadded and childremoved observers for mutations.