Firebase Observe Called After Following Command

Firebase Observe called after following command

Firebase loads data from its database asynchronously. This means that the code executes in a different order from what you may expect. The easiest way to see this is with some log statements:

let ref = Database.database().reference().child("Users").child(currentMessage.senderId).child("name")
print("Before attaching observer")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
print("In completion handler")
})
print("After attaching observer")

Unlike what you may expect, this code prints:

Before attaching observer

After attaching observer

In completion handler

This happens because loading data from Firebase (or any other web service) may take some time. If the code would wait, it would be keeping your user from interacting with your application. So instead, Firebase loads the data in the background, and lets your code continue. Then when the data is available, it calls your completion handler.

The easiest way to get used to this paradigm is to reframe your problems. Instead of saying "first load the data, then add it to the list", frame your problem as "start loading the data. when the data is loaded, add it to the list".

In code this means that you move any code that needs the data into the completion handler:

let ref = Database.database().reference().child("Users").child(currentMessage.senderId).child("name")    
ref.observeSingleEvent(of: .value, with: { (snapshot) in
self.username = snapshot.value as! String
let nameModel = NameModel(name: self.username, uid: *some UID*)
decoratedItems.append(DecoratedChatItem(chatItem: nameModel, decorationAttributes: nil))
})

For some more questions on this topic, see:

  • Swift: Wait for Firebase to load before return a function
  • Can Swift return value from an async Void-returning block?
  • Array items are deleted after exiting 'while' loop?
  • Asynchronous functions and Firebase with Swift 3

Firebase observe is called twice?

SOLUTION UPDATED

Finally, found the answer with 2 possible ways

  1. ObserveSingleType

  2. Or Remove the FIRDatabaseHandle observer

//check total message size of a single channel

//you can choose one of these 2 ways
let mainChannelRef: FIRDatabaseReference = FIRDatabase.database().reference().child("channels")

//1st way
if let channelid = channel?.id {
mainChannelRef.child(channelid).child("messages").observeSingleEvent(of: .value, with: { (snapshot) in
UserDefaults.standard.set(snapshot.childrenCount, forKey: "latestreadchatsize@\(channelid)")
UserDefaults.standard.synchronize()
})
}


//2nd way
if let channelid = channel?.id {
let newRefHandle: FIRDatabaseHandle = mainChannelRef.child(channelid).child("messages").observe(FIRDataEventType.value, with: { ( snapshot: FIRDataSnapshot) -> Void in
UserDefaults.standard.set(snapshot.childrenCount, forKey: "latestreadchatsize@\(channelid)")
UserDefaults.standard.synchronize()
})
mainChannelRef.child(channelid).child("messages").removeObserver(withHandle: newRefHandle)
}

Both of them make sure the observer is clicked only once

Firebase 'observe' fires multiple times with different data

Reason why

After some digging I've discovered that double-firing is an intended behaviour for Firebase SDK. First - cached snapshot for a reference, Second - retrieved from Realtime Database.

How to handle

  1. Set isPersistenceEnabled to false. This will ensure that all the .observe calls do not use data cached locally on the device

  2. Update data with the second call (if there will be one)

How can I detect firebase observe childAdded is stop calling when reach queryLimit?

queryLimited(toLast: 5)

means (in much simpler words) please get the last 5 values (order is decided by the previous part of your query)

1. Now, since you are sorting the data by times , the values with the last 5 times will be retrieved, therefore your observer will be triggered 5 times

2. Note that if you have less than 5 records say 2 records, then it will be triggered only twice because maximum limit is 5, not minimum limit

3. Another point is that say if a new child is added and when you sort the items again according to the time and the new child is one of the last 5 items then this observer will be triggered again.

so to get the query limit you can make some changes in your code like this:

func receiveIfExist(completion: @escaping (data: YourType, limitCount: Int) -> Void) {
let requestWithQuery = Database.database.reference().child("root").queryOrdered(byChild: "time")

requestWithQuery.queryLimited(toLast: 5).observe(.childAdded, with: { (snapshot) in
guard let value = snapshot.value as? [String: Any] else { return }

self.getSingleTalkInfo(key: snapshot.key, value: value, completion: { (data) in
self.index = self.index + 1
completion(data, self.index)
})
})
}

Then using the above function as follows:

var index = 0
override func viewDidLoad() {
super.viewDidLoad()

self.myIndicator.startAnimating() // start running indicator
self.receiveIfExist { (data, limitCount) in

self.handleTalk(with: data) // do something with request data
if limitCount == 5 {
self.myIndicator.stopAnimating()
}


}
}

UPDATED:

Since very good point raised by Kevin, that above solution will fail if we have say only two records and index will never be equal to 5 and myIndicator will not stop animating,

One solution that comes to my mind is this:

First we get the children count using observeSingleEvent:

func getChildrenCount(completion: @escaping (_ childrenCount: Int) -> Void){
Database.database.reference().child("root").observeSingleEvent(of:.value with: { (snapshot) in

completion(snapshot.children.count)

}
}

then we apply the query to get last 5 items:

func receiveIfExist(completion: @escaping (data: YourType, limitCount: Int) -> Void) {
let requestWithQuery = Database.database.reference().child("root").queryOrdered(byChild: "time")

requestWithQuery.queryLimited(toLast: queryLimit).observe(.childAdded, with: { (snapshot) in
guard let value = snapshot.value as? [String: Any] else { return }

self.getSingleTalkInfo(key: snapshot.key, value: value, completion: { (data) in
self.index = self.index + 1
completion(data, self.index)
})
})
}

then use this count in your code as follows:

var index = 0
var childrenCount = 0
var queryLimit = 5
override func viewDidLoad() {
super.viewDidLoad()

self.myIndicator.startAnimating() // start running indicator
self.getChildrenCount {(snapChildrenCount) in
self.childrenCount = snapChildrenCount

self.receiveIfExist { (data, limitCount) in

self.handleTalk(with: data) // do something with request data
if (self.childrenCount < self.queryLimit && limitCount == self.childrenCount) || limitCount == self.queryLimit {
DispatchQueue.main.async {
self.myIndicator.stopAnimating()
}

}


}

}

}

Swift Firebase observe with .childAdded retrieving duplicate data

I haven't used Firebase, but when you pass a block to the observe function, there's nothing to suggest the observation stops when the view disappears.

So I wonder if calling observe multiple times is the problem. Have you tried calling observe just once, in ViewDidLoad?

Alternatively, have a property to record whether the observation has been started:

var observing: Bool = false

func observeAddedChildren() {
if observing {
return false
}

self.children_query.observe(.childAdded, with: { snapshot in
print(snapshot.value)
})
observing = true
}

Edit: here's a couple more ways, because you're presumably adding the snapshot items to an array or other collection, then your UICollectionViewDataSource is reading stuff from that array.

So either:

  1. empty this array inside viewWillAppear and then call self.collectionView.reloadData(), or
  2. if your snapshot object has a unique identifier property, then put this into an array, and then the observe block can ignore items it's already seen before

Like this:

var snapshotIDs : [String] = Array<String>()

func observeAddedChildren() {
self.children_query.observe(.childAdded, with: { snapshot in
if !snapshotIDs.contains(snapshot.uniqueID) {
print(snapshot.value)
snapshotIDs.append(snapshot.uniqueID)
}
})
}

When Firebase query removes Observer with handle, other observers are not called

It was my mistake in the code, I changed FriendsObserver and now everything works.

class FriendsObserver: FirebaseObserver {

static let shared = FriendsObserver()
private override init() {
super.init()
}

// MARK: - Queues

private let queue = DispatchQueue(label: "com.myapp.FriendsObserver.queue")

// MARK: - Data

private var userFriendshipStartObservers = [String : DatabaseHandle]()
private var userFriendshipEndObservers = [String : DatabaseHandle]()


open func observeSpecificUserFriendshipStart(_ observer: FirebaseObserverDelegate, isObserve: Bool, userID: String, startHandler: ((_ friend: FriendModel) -> Void)?, fail: ((_ error: Error) -> Void)?) {
let observerID = observer.observerID

let realmManager = RealmManager()
let timestamp = Date().currentTimestamp
guard let currentUserID = realmManager.getCurrentUser()?.id else { return }
let query = Database.database().reference().child(FriendsPaths.MainGateways.friends.description).child(currentUserID).child(FriendsPaths.SubGateways.userFriends.description).queryOrdered(byChild: "friendID").queryEqual(toValue: userID)

if !isObserve {
guard let handle = userFriendshipStartObservers[observerID] else { return }
query.removeObserver(withHandle: handle)
userFriendshipStartObservers[observerID] = nil

// system
removeObserverModel(observerID, handle: handle)
return
}

DispatchQueue.global(qos: .background).async {
var isContinue = true

self.queue.sync {
if self.userFriendshipStartObservers[observerID] != nil {
isContinue = false
}
}
guard isContinue else { return }


var handle: DatabaseHandle = 0
handle = query.observe(.childAdded, with: { (snapshot) in
guard snapshot.exists() else { return }
guard let dict = snapshot.value as? [String : Any] else { return }
guard let friendModel = Mapper<FriendModel>().map(JSON: dict) else { return }

guard timestamp < friendModel.friendshipTimeStamp else { return }

if friendModel.friendID == userID {
startHandler?(friendModel)
}
}, withCancel: { (error) in
fail?(error)
})

self.queue.sync {
self.userFriendshipStartObservers[observerID] = handle
self.addObserver(observerID, handle: handle, query: query, ref: nil)
}
}
}

/// Only one observer on one object
open func observeUserFriendshipEnd(_ observer: FirebaseObserverDelegate, isObserve: Bool, friendID: String, endHandler: ((_ removedFriendModel: FriendModel) -> Void)?, fail: ((_ error: Error) -> Void)?) {
let observerID = observer.observerID
let realmManager = RealmManager()
guard let currentUserID = realmManager.getCurrentUser()?.id else { return }
let query = Database.database().reference().child(FriendsPaths.MainGateways.friends.description).child(currentUserID).child(FriendsPaths.SubGateways.userFriends.description).queryOrdered(byChild: "friendID").queryEqual(toValue: friendID)

if !isObserve {
guard let handle = userFriendshipEndObservers[observerID] else { return }
query.removeObserver(withHandle: handle)
userFriendshipEndObservers[observerID] = nil

// system
removeObserverModel(observerID, handle: handle)
return
}

DispatchQueue.global(qos: .background).async {
var isContinue = true

self.queue.sync {
if self.userFriendshipEndObservers[observerID] != nil {
isContinue = false
}
}
guard isContinue else { return }

var handle: DatabaseHandle = 0

handle = query.observe(.childRemoved, with: { (snap) in
guard snap.exists() else { return }
guard let dict = snap.value as? [String : Any] else { return }
guard let removedFriendModel = Mapper<FriendModel>().map(JSON: dict) else { return }
let friendRealmManager = FriendRealmManager()
friendRealmManager.removeFriend(removedFriendModel.friendID)
if removedFriendModel.friendID == friendID {
endHandler?(removedFriendModel)
}
}, withCancel: { (error) in
fail?(error)
})

self.queue.sync {
self.userFriendshipEndObservers[observerID] = handle
self.addObserver(observerID, handle: handle, query: query, ref: nil)
}
}
}

}

Completion handler Firebase observer in Swift

The code you're using declares stadiums outside of the observer. This means any time a change is made to the value of the database reference, you're appending the data onto stadiums without clearing what was there before. Make sure to remove the data from stadiums before appending the snapshots again:

func getStadiums(complition: @escaping ([Stadium]) -> Void){
var stadiums: [Stadium] = []
let stadiumRef = Database.database().reference().child("Stadium")
stadiumRef.observe(.value, with: { (snapshot) in
stadiums.removeAll() // start with an empty array
for snap in snapshot.children {
guard let stadiumSnap = snap as? DataSnapshot else {
print("Something wrong with Firebase DataSnapshot")
complition(stadiums)
return
}
let stadium = Stadium(snap: stadiumSnap)
stadiums.append(stadium)
}
complition(stadiums)
})
}


Related Topics



Leave a reply



Submit