Firebase, how observe works? - ios

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.

Related

Update tableView row from AppDelegate Swift 4

[![enter image description here][1]][1]
Hello. I have a tableview like in the picture above and I'm receiving some silent push notifications. Depending on them I need to reload a specific cell from the tableView. Since I'm getting the notification in the AppDelegate and there at the moment I'm reloading the whole tableView...but personally I don't find this the best solution since I only need to update a specific row.
Any hints please how can I update just a specific cell from appDelegate?
if userInfo["notification_type"] as? String == "update_conversation" {
if let rootVC = (self.window?.rootViewController as? UINavigationController)?.visibleViewController {
if rootVC is VoiceViewController {
let chatRoom = rootVC as! VoiceViewController
chatRoom.getConversations()
// the get Conversations method makes a call to api to get some data then I reload the whole tableView
}
}
func getConversations() {
let reachabilityManager = NetworkReachabilityManager()
if (reachabilityManager?.isReachable)! {
ServerConnection.getAllConversation { (data) in
if let _ = data{
self.conversations = data
self.onlineRecent = self.conversations
GlobalMainQueue.async {
self.mainTableView.reloadData()
}
}
}
}
}
This is my getConversation method which is used in VoiceViewController to populate my tableview
Have the app delegate broadcast an app-specific notification center notification (on the main thread). Have the view controller that contains your table view listen for that notification and update the cell in question as needed. That way you don't contaminate your app delegate. The app delegate should only deal with system level app stuff, not business logic.
You could get your row’s cell using self.mainTableView.cellForRow(at:IndexPath(…), and update it directly.
Or, I’ve found you save a load of time and your view controllers end up more reliable using ALTableViewHelper [commercial - available on Framework Central here]. It’s free to try.
The helper does the most of the work - you describe how the data connects to the UITableView. I’ve put together an example (on GitHub here), which I hope is something like what you’re trying to do.
import ALTableViewHelper
class VoiceViewController {
// #objc for the helper to see the var’s, and dynamic so it sees any changes to them
#obj dynamic var conversations: Any?
#obj dynamic var onlineRequest: Any?
func viewDidLoad() {
super.viewDidLoad()
tableView.setHelperString(“””
section
headertext "Conversation Status"
body
Conversation
$.viewWithTag(1).text <~ conversations[conversations.count-1]["title"]
$.viewWithTag(2).text <~ "At \\(dateFormat.stringFromDate(conversations[conversations.count-1]["update"]))"
UpdateButton
$.viewWithTag(1).isAnimating <~ FakeConversationGetter.singleton.busy
“””, context:self)
}
func getConversations() {
let reachabilityManager = NetworkReachabilityManager()
if (reachabilityManager?.isReachable)! {
ServerConnection.getAllConversation { (data) in
if let _ = data {
// change the data on the main thread as this causes the UI changes
GlobalMainQueue.async {
self.conversations = data
self.onlineRequest = self.conversations
}
}
}
}
}

Firebase logged in user not loading fast enough before segue

I'm hitting this button
override func viewDidLoad() {
FIRAuth.auth()?.signIn(withEmail: "hakuna.matata.kitty#gmail.com", password: "password") {
(user, error) in
// TODO: SET UP A SPINNER ICON
print("Logged into hmk")
self.performSegue(withIdentifier: "login", sender: self)
}
}
As you can see, on callback it redirects to a UITableViewController. The populator code sits in the initializer of UITableViewController, the sole purpose in the closure is to populate self.chatsList
Sometimes, whenever I test out the app, the UITableViewController is blank.
ref.child("users").child(currentUser.uid).observeSingleEvent(of: .value, with: { (snapshot) in
guard snapshot.exists() else {
print("Error: Path not found")
return
}
let value = snapshot.value as? NSDictionary
// Convert a dict of dicts into an array of dicts
// You will lose chatID in the process, so be warned
// Unless you would like to to keep the data in a struct
for (chatIdKey, secondDict) in value?["chats"] as! [String: NSDictionary] {
// This only appends metadata for the last chat
// Does not load every chat message
self.chatsList.append(Chat(chatId: chatIdKey, targetChat: secondDict))
}
})
But when I hit the back key, wait long enough, and login again, the table is populated properly.
How can I make sure that it loads correctly every time? I expected the performSegue on callback would guarantee that we do have a currentUser...
I think your observer should be changed. I see this tableview will be some kind of chat so you might want to keep the reference alive while the user is in that ViewController so instead of using observeSingleEvent you could just observe and you manually remove the observer when leaving the view.
Another thing is that you are observing the value when you should probably observe the childAdded as if any event is added while being in the "chat" view the user will not receive it.
Change the line below and see if this works for you.
ref.child("users").child(currentUser.uid).observe(.childAdded, with: { (snapshot) in
Hope it helps!

Updating label if value in singleton changes

I am getting some user information from Firebase and storing it into singleton. After that every time the value changes I want that the label changes also but it doesn't until I terminate the app and come back in.
How should I update label if value changes in singleton?
I have tab views. In first tab I assign values and in second tab I try to put the values to label.
This is my singleton:
class CurrentUser: NSObject
{
var generalDetails: User = User()/// Consecutive declarations on a line must be separated by ';'
static let sharedInstance = CurrentUser()
fileprivate override init(){
super.init()
}
}
And like this I assign values:
self.databaseRef.child("users").child(user.uid).observeSingleEvent(of: .value) { (snapshot:FIRDataSnapshot) in
guard let firebaseValue = snapshot.value as? [String:AnyObject], let userName = firebaseValue["username"] as? String, let email = firebaseValue["email"] as? String, let reputation = firebaseValue["reputation"] as? Int, let profilePicURL = firebaseValue["profileImageUrl"] as? String
else
{
print("Error with FrB snapshot")//Here
return
}
//Set values
self.currentUser.generalDetails = User(userName: userName, email: email, reputation: reputation, profileImageURL: profilePicURL, uid: user.uid)
}
And if I want to put the value to the label I simply do this(This reputation is the only thing that can change often):
self.reputationLabel.text = String(self.currentUser.generalDetails.reputation)
You can do either of these:-
Communicate between the singleton and your class with delegate-protocol method , fire the delegate method in the class whenever your repo changes and update your label.
Open a different thread in your network link for the user's reputation in the viewController itself:-
override func viewDidLoad() {
super.viewDidLoad()
FIRDatabase.database().reference().child("users").child(FIRAuth.auth()!.currentUser!.uid).child("reputation").observe(.childChanged, with: {(Snapshot) in
print(Snapshot.value!)
//Update yor label
})
which will get called every time the value of reputation changes.
I like Dravidian's answer and I would like to offer an alternative: KVO
We use Key-Value Observing to monitor if our app is disconnected from the Firebase server. Here's the overview.
We have a singleton which stores a boolean variable isConnected, and that variable is set by observing a special node in Firebase
var isConnected = rootRef.child(".info/connected")
When connected/disconnected, the isConnected var changes state.
We have a little icon on our views that indicates to the user the connected state; when connected it's green, when disconnected it's red with a line through it.
That icon is a class and within each class we have code that observes the isConnected variable; when it's state changes all of the icons change automatically.
It takes very little code, is reusable, is clean and easily maintained.
Here's a code snippet from the Apple documentation
//define a class that you want to observe
class MyObjectToObserve: NSObject {
dynamic var myDate = NSDate()
func updateDate() {
myDate = NSDate()
}
}
//Create a global context variable.
private var myContext = 0
//create a class that observes the myDate var
// and will be notified when that var changes
class MyObserver: NSObject {
var objectToObserve = MyObjectToObserve()
objectToObserve.addObserver(self,
forKeyPath: "myDate",
options: .new,
context: &myContext)
There will be more to it but that's it at a 10k foot level.
The Apple documentation is here
Using Swift with Cocoa and Obj-c 3.01: Design Patterns
and scroll down the the Key-Value Observing Section. It's a good read and very powerful. It follows the same design pattern as Firebase (or vice-versa) - observe a node (variable) and tell me when it changes.

How to keep from having repeated items on a tableView due to a function triggered several times?

My issue is that I am querying elements in viewDidLoad and adding to the tableView. On another view, the user can add elements to Parse, that I am querying again on viewDidAppear to display only the newly added elements without requiring the view to re-load.
These elements are class of user with name, age etc...
On the viewDidAppear, I am querying the elements from Parse, going through a messy filtering to find out the ones that are not already displayed on the tableView, and then I trigger my function to add it.
It appears that even though I have removed duplicate items from my array, the function to set up my user (adding his name etc) gets called several times and consequently I still end up with duplicated items on my tableView.
The corresponding codes as below:
override func viewWillAppear(animated: Bool) {
var query = PFUser.query()
query!.whereKey("username", equalTo: PFUser.currentUser()!.username!)
query!.findObjectsInBackgroundWithBlock { (objects, NSError) -> Void in
if let objects = objects as? [PFObject] {
for member in objects {
if member["Network"] != nil {
var acceptedMembers: [String] = member["Network"] as! [String]
self.usernames = acceptedMembers
////
var query2 = PFUser.query()
query2?.findObjectsInBackgroundWithBlock({ (objects2, error2) -> Void in
if let objects2 = objects2 as? [PFUser] {
for otherUser in objects2 {
if contains(self.usernames, otherUser.username!) {
var arrayName1 = [String]()
arrayName1.append(otherUser.username!)
var arrayName2 = [String]()
for n in self.arrayMem {
arrayName2.append(n.name)
dispatch_async(dispatch_get_main_queue(), {
for extra in arrayName1 {
if contains(arrayName2, extra) {
} else {
var arrayName3 = [String]()
arrayName3.append(extra)
let unique3 = NSSet(array: arrayName3).allObjects
self.plusOne = unique3.first as! String
self.nameMember = self.plusOne as String
self.setName()
self.tableView.reloadData()
}
}
})
}
}}}}) }}}}
}
PS: I have tried cleaner solution to remove duplicate, converting my class to Hashable, then Equatable and using a simple function, however it turns up that this solution, messier as it is works more efficiently.
Anyway, the question here touches the function "self.setName()" that gets called repeatedly.
Do you have any idea how could this be fixed ?
Thank you infinitely,
A different approach would be to add a new object to your array after it's added in the other view controller and then add a new row for it into your table view. I would suggest communicating the newly formed object via an NSNotification.
View Controller with Table
Fetch your objects from Parse here, do it once in viewDidLoad
func fetchParseObjects () {}
Add an observer for this class to the NSNotificationCenter, also in viewDidLoad
NSNotificationCenter.defaultCenter().addObserver(self, selector: "objectUpdated:", name: "objectUpdated", object: nil)
Remove the observer in a deinit method
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self, name: "objectUpdated", object: nil)
}
func objectUpdated(notification: NSNotification) {
if let yourKindOfObject = notification.object as? YOUR_TYPE_HERE {
// Add, update or remove an item from the array that holds the original fetched data here based on the object
// Update your tableView accordingly (add, remove or update)
}
Where I have done something similarly I have created a custom object to hold some properties of what object needs to be updated and how it should be updated.
View Controller that Updates
Do what you normally do, but send a notification with a descriptive object to use to update your original data accordingly
NSNotificationCenter.defaultCenter().postNotificationName("objectUpdated", object: YOUR_OBJECT_WITH_UPDATE_DATA)

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