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
ObserveSingleType
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
Set
isPersistenceEnabled
tofalse
. This will ensure that all the.observe
calls do not use data cached locally on the deviceUpdate 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:
- empty this array inside
viewWillAppear
and then callself.collectionView.reloadData()
, or - 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
What Is the Practical Use of Nested Functions in Swift
How to Create a Window with Transparent Background with Swift on Osx
Expected Hexadecimal Code in Braces After Unicode Escape
Swift Closures Causing Strong Retain Cycle with Self
Process Array in Parallel Using Gcd
Does Swift Guarantee the Storage Order of Fields in Classes and Structs
Difference Between String Interpolation and String Initializer in Swift
How to Change Navigation Bar & Back Button Colour iOS 15
How to Cache Images Using Urlsession in Swift
Scenekit Scnscenerendererdelegate - Renderer Function Not Called
Swift 4.0 Mapview Running Slow
Swift Enum Loses Initialized Values When Set as a Property
How to Rotate Only One View Controller to Landscape Orientation in iOS Swift 3
How to Set the Alpha of an Uiimage in Swift Programmatically
Checking If a Double Value Is an Integer - Swift
Getting Dyld_Fatal_Error After Updating to Xcode 6 Beta 4 Using Swift