How to remove firebase observer? - ios

I have tableView and refreshControl. I want that tableView's data reload only when I pull-to-refresh. I've made the function that reload data, but it works always.
Data reload after pull-to-refresh, but also observe update tableView.
How to remove it?
I want to make observe, reload data and remove observe (remove connection).
func reloadTable() {
let ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
postData.removeAll()
var databaseHandle: DatabaseHandle?
databaseHandle = ref.child("doctors").child(userID!).observe(.childAdded) {
(snapshot) in
if let actualPost = snapshot.value as? String {
self.postData.append(actualPost)
self.table.reloadData()
}
}
// I tried this:
ref.removeAllObservers()
// And this as well:
ref.removeObserver(withHandle: databaseHandle)
}

You need
let current = ref.child("doctors").child(userID!)
current.observe ///
current.removeAllObservers()
if you need to removeObservers you need to go deep as you add childs , as removeAllObservers for parents doesn't remove them for childs

Related

Displaying an array in a table view before the user see it

I am working on pulling in data from firebase and displaying it in a table view. I populate an array with the data received and use the array to fill the tableview, however the data is not being displayed.
I realized the issue is that the tableview is loading before the array gets populated. I've attempted putting a reloadData in viewdidload but this makes the data pop in after it is displayed and does not look clean.
How can I get the tableview to load the data before the view appears so that the transition is smooth?
This code is inside my viewdidload method :
let categoryRef = Database.database().reference().child("category/\(category)")
let user = Auth.auth().currentUser
if let user = user {
let uid = user.uid
categoryRef.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.hasChildren(){
for child in (snapshot.value as? NSDictionary)! {
if let object = child.value as? [String:AnyObject]{
let name = object["name"] as! String
self.nameArray.append(name)
self.categoryDict["\(name)"] = child.key as! String
}
}
}
})
}
}
Try reloading the data just after you appended all the children:
let categoryRef = Database.database().reference().child("category/\(category)")
let user = Auth.auth().currentUser
if let user = user {
let uid = user.uid
categoryRef.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.hasChildren(){
for child in (snapshot.value as? NSDictionary)! {
if let object = child.value as? [String:AnyObject]{
let name = object["name"] as! String
self.nameArray.append(name)
self.categoryDict["\(name)"] = child.key as! String
}
}
self.tableView.reloadData()
}
})
}
}
Call func insertSections(IndexSet, with: UITableViewRowAnimation) instead but remember to wrap it inside a call to beginUpdates and endUpdates. Also make sure your updating your UI on the main queue.
because
categoryRef.observeSingleEvent
is asynchronous, you should reload the table view after the block finishes, the way #barbarity proposes.
If you want a smoother transition, start this call
let categoryRef = Database.database().reference().child("category/\(category)")
before presenting your viewController, set the array and then present it.
But the practice in most of the cases with asynchronous processing is, start loading the data on viewDidLoad and have a UI loading mechanism (spinner)
I guess you could make other views which will make you sense the feeling of 'data pop' showed after the data is ready.
On the extreme condition, you may try to hide the whole tableview or even show Activity Indicator on the screen, then show the tableview and hide indicator after the datas for tableview is ready (In Objective-C):
- (void)viewDidLoad {
//do init work ...
self.tableview.hidden = YES;
[self showIndicator];
[self requestDataFinish:^{
self.tableview.hidden = NO;
[self hideIndicator];
[self.tableview reload];
}];
}

Not sure how to append array outside of async call

I'm trying to get certain child nodes named City from Firebase using observeSingleEvent but I am having issues trying to pull it into the main thread. I have used a combination of completion handlers and dispatch calls but I am not sure what I am doing wrong, in addition to not being that great in async stuff. In viewDidLoad I'm trying to append my keys from the setupSavedLocations function and return it back to savedLocations I feel like I am close. What am I missing?
Edit: Clarity on question
import UIKit
import Firebase
class SavedLocationsViewController: UIViewController {
var userID: String?
var savedLocations: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
setupSavedLocations() { (savedData) in
DispatchQueue.main.async(execute: {
self.savedLocations = savedData
print("inside", self.savedLocations)
})
}
print("outside",savedLocations)
}
func setupSavedLocations(completion: #escaping ([String]) -> ()) {
guard let user = userID else { return }
let databaseRef = Database.database().reference(fromURL: "https://************/City")
var dataTest : [String] = []
databaseRef.observeSingleEvent(of: .value, with: {(snapshot) in
let childString = "Users/" + user + "/City"
for child in snapshot.children {
let snap = child as! DataSnapshot
let key = snap.key
dataTest.append(key)
}
completion(dataTest)
})
}
sample output
outside []
inside ["New York City", "San Francisco"]
The call to setupSavedLocations is asynchronous and takes longer to run than it does for the cpu to finish viewDidLoad that is why your data is not being shown. You can also notice from your output that outside is called before inside demonstrating that. The proper way to handle this scenario is to show the user that they need to wait for an IO call to be made and then show them the relevant information when you have it like below.
class SavedLocationsViewController: UIViewController {
var myActivityIndicator: UIActivityIndicatorView?
override func viewDidLoad() {
super.viewDidLoad()
setupSavedLocations() { (savedData) in
DispatchQueue.main.async(execute: {
showSavedLocations(locations: savedData)
})
}
// We don't have any data here yet from the IO call
// so we show the user an indicator that the call is
// being made and they have to wait
let myActivityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
myActivityIndicator.center = view.center
myActivityIndicator.startAnimating()
self.view.addSubview(myActivityIndicator)
self.myActivityIndicator = myActivityIndicator
}
func showSavedLocations(locations: [String]) {
// This function has now been called and the data is passed in.
// Indicate to the user that the loading has finished by
// removing the activity indicator
myActivityIndicator?.stopAnimating()
myActivityIndicator?.removeFromSuperview()
// Now that we have the data you can do whatever you want with it here
print("Show updated locations: \(locations)")
}

Cannot auto update child values in tableview

Does anyone know how do you automatically update the child values to the tableView every time it changes? I have to terminate the app and when restarting it I'm able to to see the updated values. How can I update these without terminating the app each time?
var ref: DatabaseReference?
var databaseHandle: DatabaseHandle?
var postData = [String]()
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference()
databaseHandle = ref?.child("Posts").observe(.childAdded, with: { (snapshot) in
let post = (snapshot.value as AnyObject).description
if let actualPost = post {
postData.append(actualPost)
self.tableView.reloadData()
}
})
}
You should change
self.tableView.reloadData()
to
DispatchQueue.main.async {
self.tableView.reloadData()
}
that may work. Because all the UI-related works should happen in main queue

Array of struct not updating outside the closure

I have an array of struct called displayStruct
struct displayStruct{
let price : String!
let Description : String!
}
I am reading data from firebase and add it to my array of struct called myPost which is initialize below
var myPost:[displayStruct] = []
I made a function to add the data from the database to my array of struct like this
func addDataToPostArray(){
let databaseRef = Database.database().reference()
databaseRef.child("Post").queryOrderedByKey().observe(.childAdded, with: {
snapshot in
let snapshotValue = snapshot.value as? NSDictionary
let price = snapshotValue?["price"] as! String
let description = snapshotValue?["Description"] as! String
// print(description)
// print(price)
let postArr = displayStruct(price: price, Description: description)
self.myPost.append(postArr)
//if i print self.myPost.count i get the correct length
})
}
within this closure if I print myPost.count i get the correct length but outside this function if i print the length i get zero even thou i declare the array globally(I think)
I called this method inside viewDidLoad method
override func viewDidLoad() {
// setup after loading the view.
super.viewDidLoad()
addDataToPostArray()
print(myPeople.count) --> returns 0 for some reason
}
I want to use that length is my method below a fucntion of tableView
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myPost.count --> returns 0
}
Any help would be greatly appreciated!
You making a asynchronous network request inside closure and compiler doesn't wait for the response, so just Reload Table when get post data. replace the code with below it work works fine for you. All the best.
func addDataToPostArray(){
let databaseRef = Database.database().reference()
databaseRef.child("Post").queryOrderedByKey().observe(.childAdded, with: {
snapshot in
let snapshotValue = snapshot.value as? NSDictionary
let price = snapshotValue?["price"] as! String
let description = snapshotValue?["Description"] as! String
// print(description)
// print(price)
let postArr = displayStruct(price: price, Description: description)
self.myPost.append(postArr)
print(self.myPost.count)
print(self.myPost)
self.tableView.reloadData()
//if i print self.myPost.count i get the correct length
})
}
Firebase observe call to the database is asynchronous which means when you are requesting for the value it might not be available as it might be in process of fetching it.
That's why your both of the queries to count returns 0 in viewDidLoad and DataSource delegeate method.
databaseRef.child("Post").queryOrderedByKey().observe(.childAdded, with: { // inside closure }
Inside the closure, the code has been already executed and so you have the values.
What you need to do is you need to reload your Datasource in main thread inside the closure.
databaseRef.child("Post").queryOrderedByKey().observe(.childAdded, with: {
// After adding to array
DispatchQueue.main.asyc {
self.tableView.reloadData()
}
}

Grab Data, Sort Data, then Load into Table View (Swift 3 : Firebase)

I'm working on an application using Firebase. What I'm trying to accomplish is getting data from Firebase, sorting that data, and then finally loading that data into a tableView once that is done.
I'm not gonna share my whole code, but here's essentially how I want it to work:
var posts = [PostStruct]()
var following = [String]()
...
let databaseRef = FIRDatabase.database().reference()
for follower in following {
databaseRef.child("Posts").child(follower).observe(.value, with: {
DataSnapshot in
//Parse All The Data...
self.posts.insert(...)
}
}
self.posts.sort{$0.date.compare($1.date) == .orderedDescending}
print("Test")
self.tableView.reloadData()
That print("Test") gets called, but it gets called before the FIRDatabase is requested, so that tells me that there is absolutely no data in the tableView when it's sorting. So, I need to find a way to only sort once the Database is finished requesting.
I can put the sort and reload method in the for statement, and that works, but it loads everything up choppy, and it's not very efficient.
Not sure if this is the best way to handle this, but you could add a counter that is incremented and then execute your sort and reload code once that counter is equal to the count of the following array.
var counter = 0
let databaseRef = FIRDatabase.database().reference()
for follower in following {
databaseRef.child("Posts").child(follower).observe(.value, with: {
DataSnapshot in
//Parse All The Data...
counter += 1
self.posts.insert(...)
if counter == following.count {
self.sortPosts()
}
}
}
func sortPosts() {
self.posts.sort{$0.date.compare($1.date) == .orderedDescending}
print("Test")
self.tableView.reloadData()
}
if this is for your youtube tutorials I will try to answer
I think the solution of Donny is going to work, you can do it also with a callback function
func getData(handle:#escaping ((Bool) -> Void)){
let databaseRef = FIRDatabase.database().reference()
for follower in following {
databaseRef.child("Posts").child(follower).observe(.value, with: {
DataSnapshot in
//Parse All The Data...
counter += 1
self.posts.insert(...)
if counter == following.count {
handle(true)
}
}
}
}
and then in your method where you are calling getData.
getData(){ ready in
self.posts.sort{$0.date.compare($1.date) == .orderedDescending}
print("Test")
self.tableView.reloadData()
}

Resources