FirebaseQuery Observer not Removing - ios

I add an observer in viewWillAppear and remove it in viewWillDisappear. I switch tabs than manually add something to the posts ref and the observer still runs (I have a break point inside the block).
Where am I going wrong at when removing the observer?
var postRefHandle: DatabaseHandle!
var query = DatabaseQuery()
var postsRefObserver = Database.database().reference().child("posts")
// called in viewWillAppear
func listen() {
self.query = postsRefObserver
.queryOrderedByKey()
.queryLimited(toLast: 1)
self.postRefHandle = self.query.observe(.childAdded) { (snapshot) in
// do something
}
}
// called in viewWillDisappear
func remove() {
if let postRefHandle = postRefHandle {
self.postsRefObserver.removeObserver(withHandle: postRefHandle)
}
self.postsRefObserver.removeAllObservers()
}

You need to remove the observer on the exact query object that you registered it on.
So:
self.query.removeObserver(withHandle: postRefHandle)
or
self.query.removeAllObservers()

Related

viewDidLoad, is it called only once?

I am a beginner in iOS development, and I am following one tutorial using firebase database to make a simple chat app. Actually I am confused with the use of viewDidLoad method.
Here is the screenshot of the app: https://ibb.co/gqD4Tw
I don't understand why retrieveMessage() method is put on viewDidLoad when I want to send data (chat message) to firebase database, I used sendButtonPressed() method (which is an IBAction) and when I want to retrieve data from the database, I use retrieveMessage().
The retrieveMessage() method is called on viewDidLoad, as far as I know the viewDidLoad method is called only once after the view is loaded into memory. We usually use it for initial setup.
So, if viewDidLoad is called only once in initial setup, why the retrieveMessage() method can retrieve all the message that I have sent to my own database over and over again, after I send message data to the database ?
I don't understand why retrieveMessage() method is put on viewDidLoad below is the simplified code:
class ChatViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var messageArray = [Message]()
#IBOutlet var messageTextfield: UITextField!
#IBOutlet var messageTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
//Set as the delegate and datasource :
messageTableView.delegate = self
messageTableView.dataSource = self
//the delegate of the text field:
messageTextfield.delegate = self
retrieveMessage()
///////////////////////////////////////////
//MARK: - Send & Recieve from Firebase
#IBAction func sendPressed(_ sender: AnyObject) {
// Send the message to Firebase and save it in our database
let messageDB = FIRDatabase.database().reference().child("message")
let messageDictionary = ["MessageBody":messageTextfield.text!, "Sender": FIRAuth.auth()?.currentUser?.email]
messageDB.childByAutoId().setValue(messageDictionary) {
(error,ref) in
if error != nil {
print(error!)
} else {
self.messageTextfield.isEnabled = true
self.sendButton.isEnabled = true
self.messageTextfield.text = ""
}
}
}
//Create the retrieveMessages method :
func retrieveMessage () {
let messageDB = FIRDatabase.database().reference().child("message")
messageDB.observe(.childAdded, with: { (snapshot) in
let snapshotValue = snapshot.value as! [String:String]
let text = snapshotValue["MessageBody"]!
let sender = snapshotValue["Sender"]!
let message = Message()
message.messsageBody = text
message.sender = sender
self.messageArray.append(message)
self.messageTableView.reloadData()
})
}
}
viewDidLoad method is called only once in ViewController lifecycle.
The reason retrieveMessage() is called in viewDidLoad because it's adding observer to start listening for received and sent message. Once you receive or send message then this block(observer) is called and
then adding that text in array self.messageArray.append(message) and updating tableview.
viewDidLoad gets called only once but the firebase functions starts a listener, working in background and syncronizeing data.
Its called in viewDidLoad because it tells -> When this view loads, start listening for messages.
ViewDidLoad() is only called upon initializing the ViewController.
If you want to have a function called every time the user looks at the VC again (e.g. after a segue back from another VC) you can just use ViewDidAppear().
It is also called when ViewDidLoad() is called.

Firebase, how observe works?

I honestly I have tried to figure out when to call ref.removeAllObservers or ref.removeObservers, but I'm confused. I feed I'm doing something wrong here.
var noMoreDuplicators = [String]()
func pull () {
if let myIdi = FIRAuth.auth()?.currentUser?.uid {
let ref = FIRDatabase.database().reference()
ref.child("users").queryOrderedByKey().observe(.value, with: { snapshot in
if let userers = snapshot.value as? [String : AnyObject] {
for (_, velt) in userers {
let newUser = usera()
if let thierId = velt["uid"] as? String {
if thierId != myIdi {
if let userName = velt["Username"] as? String, let name = velt["Full Name"] as? String, let userIdent = velt["uid"] as? String {
newUser.name = name
newUser.username = userName
newUser.uid = userIdent
if self.noMoreDuplicators.contains(userIdent) {
print("user already added")
} else {
self.users.append(newUser)
self.noMoreDuplicators.append(userIdent)
}
}
}
}
}
self.tableViewSearchUser.reloadData()
}
})
ref.removeAllObservers()
}
}
Am I only supposed to call removeAllObservers when observing a single event, or...? And when should I call it, if call it at all?
From official documentation for observe(_:with:) :
This method is used to listen for data changes at a particular location. This is
the primary way to read data from the Firebase Database. Your block
will be triggered for the initial data and again whenever the data
changes.
Now since this method will be triggered everytime the data changes, so it depends on your usecase , if you want to observe the changes in the database as well, if not then again from the official documentation:
Use removeObserver(withHandle:) to stop receiving updates.
Now if you only want to observe the database once use observeSingleEvent(of:with:) , again from official documentation:
This is equivalent to observe:with:, except the block is
immediately canceled after the initial data is returned
Means that you wont need to call removeObserver(withHandle:) for this as it will be immediately canceled after the initial data is returned.
Now lastly , if you want to remove all observers , you can use this removeAllObserver but note that:
This method removes all observers at the current reference, but does
not remove any observers at child references. removeAllObservers must
be called again for each child reference where a listener was
established to remove the observers
Actually, you don't need to call removeAllObservers when you're observing a single event, because this observer get only called once and then immediately removed.
If you're using observe(.value) or observe(.childAdded), and others though, you would definitely need to remove all your observers before leaving the view to preserve your battery life and memory usage.
You would do that inside the viewDidDisappear or viewWillDisappear method, like so:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Remove your observers here:
yourRef.removeAllObservers()
}
Note: you could also use removeObserver(withHandle:) method.

Firebase : How to removeObserver(withHandle:) after observeSingleEvent()?

I use Firebase Database with swift. I can easily remove an observer from a DatabaseReference when I observe with databaseReference.observe(...) :
databaseHandle = databaseReference.observe(
.value,
with: { (snapshot) in ... },
withCancel: { (error) in ... })
...
databaseReference.removeObserver(withHandle: databaseHandle)
My problem is when I use databaseReference.observeSingleEvent(...). Because it doesn't return a FIRDatabaseHandle, I can't remove the observer when I want.
I know that databaseReference.observeSingleEvent(...) removes the observer as soon at it has been fired once. However, sometimes I need to remove the observer before it has been fired.
I also know that I could use databaseReference.removeAllObservers(), but this is not a convenient solution in my case.
Does one of you know how I can prematurely remove an Observer (created with observeSingleEvent(...)) from a databaseReference ?
Thank you in advance
Since databaseReference.observeSingleEvent(...) doesn't return a handle that you can remove the only option is to use databaseReference.observe(...).
Just remove the handle manually once you need to OR when the first event fires.
Update
Try using this extension:
public extension FIRDatabaseReference {
#discardableResult
public func observeOneEvent(of eventType: FIRDataEventType, with block: #escaping (FIRDataSnapshot) -> Swift.Void) -> FIRDatabaseHandle {
var handle: FIRDatabaseHandle!
handle = observe(eventType) { (snapshot: FIRDataSnapshot) in
self.removeObserver(withHandle: handle)
block(snapshot)
}
return handle
}
}

Swift iOS -Which viewController lifecycle event to use to send data to Firebase after a view changes

I have some information to send to Firebase. The thing is I want to send the data but I also have to pull the data from there first. The data I get is based on the users input.
I'm already making several nested async calls to Firebase. Not only do i have to wait for the calls to finish to make sure the data has been set but I don't want to have the user waiting around unnecessarily when they can leave the scene and the data can be pulled and changed in a background task.
I was thinking about using a NSNotification after the performSegueWithIdentifier is triggered. The observer for the notification would be inside viewWillDisappear.
Is this safe to do and if not what's the best way to go about it?
Code:
var ref: FIRDatabaseReference!
let uid = FIRAuth.auth()?.currentUser?.uid
let activityIndicator = UIActivityIndicatorView()
override func viewDidLoad() {
super.viewDidLoad()
self.ref = FIRDatabase.database().reference().child(self.uid!)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(fetchSomeValueFromFBThenUpdateAndResendAnotherValue), name: "FbFetchAndSend", object: nil)
}
#IBAction func buttonPressed(sender: UIButton) {
activityIndicator.startAnimating()
levelTwoRef //send levelTwo data to FB run 1st callback
scoreRef //send score data to FB run 2nd callback
powerRef //send power data to FB run 3rd callback
lifeRef //send life data to FB run Last callback for dispatch_async...
dispatch_async(dispatch_get_main_queue()){
activityIndicator.stopAnimating()
performSegueWithIdentifier....
//Notifier fires after performSegue???
NSNotificationCenter.defaultCenter().postNotificationName("FbFetchAndSend", object: nil)
}
}
func fetchSomeValueFromFBThenUpdateAndResendAnotherValue(){
let paymentRef = ref.child("paymentNode")
paymentRef?.observeSingleEventOfType(.Value, withBlock: {
(snapshot) in
if snapshot.exists(){
if let dict = snapshot.value as? [String:AnyObject]{
let paymentAmount = dict["paymentAmount"] as? String
let updatePayment = [String:AnyObject]()
updatePayment.updateValue(paymentAmount, forKey: "paymentMade")
let updateRef = self.ref.child("updatedNode")
updateRef?.updateChildValues(updatePayments)
}
You are adding the observer in viewWillDisappear, So it won't get fired because it won't be present when your segue is performed.
Add the observer in viewDidLoad and it will work.
But if you just want to call fetchSomeValueFromFBThenUpdateAndResendAnotherValue() when the view is disappearing then there is no need for observer.
Simply call the method on viewWillDisappear like this -
override func viewWillDisappear(animated: Bool)
{
super.viewWillDisappear(animated)
fetchSomeValueFromFBThenUpdateAndResendAnotherValue()
}

Firebase removing observers

I have a problem removing a Firebase observer in my code. Here's a breakdown of the structure:
var ref = Firebase(url:"https://MY-APP.firebaseio.com/")
var handle = UInt?
override func viewDidLoad() {
handle = ref.observeEventType(.ChildChanged, withBlock: {
snapshot in
//Do something with the data
}
}
override func viewWillDisappear(animated: Bool) {
if handle != nil {
println("Removed the handle")
ref.removeObserverWithHandle(handle!)
}
}
Now when I leave the viewcontroller, I see that "Removed the handle" is printed, but when I return to the viewcontroller, my observer is called twice for each event. When I leave and return again, it's called three times. Etc. Why is the observer not being removed?
I do also call ref.setValue("some value") later in the code, could this have anything to do with it?
Thought I was having this bug but in reality I was trying to remove observers on the wrong reference.
ORIGINAL CODE:
let ref: FIRDatabaseReference = FIRDatabase.database().reference()
var childAddedHandles: [String:FIRDatabaseHandle] = [:]
func observeFeedbackForUser(userId: String) {
if childAddedHandles[userId] == nil { // Check if observer already exists
// NOTE: - Error is caused because I add .child(userId) to my reference and
// do not when I call to remove the observer.
childAddedHandles[userId] = ref.child(userId).observeEventType(.ChildAdded) {
[weak self] (snapshot: FIRDataSnapshot) in
if let post = snapshot.value as? [String:AnyObject],
let likes = post["likes"] as? Int where likes > 0 {
self?.receivedFeedback(snapshot.key, forUserId: userId)
}
}
}
}
func stopObservingUser(userId: String) {
// THIS DOES NOT WORK
guard let cah = childAddedHandles.removeValueForKey(userId) else {
print("Not observing user")
return
}
// Error! I did not add .child(userId) to my reference
ref.removeObserverWithHandle(cah)
}
FIXED CODE:
func stopObservingUser(userId: String) {
// THIS WORKS
guard let cah = childAddedHandles.removeValueForKey(userId) else {
print("Not observing user")
return
}
// Add .child(userId) here
ref.child(userId).removeObserverWithHandle(cah)
}
Given it's April 2015 and the bug is still around I'd propose a workaround for the issue:
keep a reference of the handles (let's say in a dictionary and before initiating a new observer for the same event type check if the observer is already there.
Having the handles around has very low footprint (based on some official comments :) ) so it will not hurt that much.
Observers must be removed on the same reference path they were put upon. And for the same number of times they were issued, or use ref.removeAllObservers() for each path.
Here's a trick I use, to keep it tidy:
var fbObserverRefs = [FIRDatabaseReference]() // keep track of where observers defined.
...then, put observers in viewDidLoad():
fbObserverRefs.append(ref.child("user/\(uid)"))
fbObserverRefs.last!.observe(.value, with: { snap in
// do the work...
})
...then, in viewWillDisappear(), take care of removing any issued observers:
// Only true when popped from the Nav Controller stack, ignoring pushes of
// controllers on top.
if isBeingDismissed || isMovingFromParentViewController {
fbObserverRefs.forEach({ $0.removeAllObservers() })
}

Resources