Firebase database observer not getting all children - ios

I am observing my Firebase database for .childAdded. When I add a child (Dictionary to the database, it should create a snapshot of all the values to parse into a 'Task' within my app.
func storeTask(_ task: Task) {
let taskInfo: [String] = [task.title, task.desc, set, due, "\(task.complete)"]
setTaskDatabaseValue(values: taskInfo, dbChild: dbReference.child(task.title))
}
func setTaskDatabaseValue(values val: [String], dbChild db: DatabaseReference) {
db.child("title").setValue(val[0])
db.child("desc").setValue(val[1])
db.child("set").setValue(val[2])
db.child("due").setValue(val[3])
db.child("complete").setValue(val[4])
}
And this is my observer method:
func getTasks() {
self.dbReference.observe(.childAdded) { (snapshot) in
let snapVal = snapshot.value as! Dictionary<String,String>
let title = snapVal["title"]!
let desc = snapVal["desc"]!
let set = snapVal["set"]!
let due = snapVal["due"]!
let complete = snapVal["complete"]!
let task = Task(title: title, desc: desc, set: set, due: due, complete: (complete == "true"))
TaskController.sharedInstance.addTaskToLocalList(task)
self.tableView.reloadData()
}
}
Where getTasks() is called, in the viewDidLoad() of the view controller :
DatabaseController.sharedInstance.addTableView(tableView)
DatabaseController.sharedInstance.getTasks()
The observer method always seems to only retrieve the task title and nothing else, yet when I check the Firebase console all the information is there. Why is this?
Thanks!

Related

getting names displaying from firebase

I am trying to display the contents of a firebase database. I know that I am reading them correctly as I am able to print them as they are read in. The problem is when I call the method to display them on screen, they are "out of range".
I know this means the the methods are being called simultaneously therefore the array is empty. I have tried the "Sleep()" method and doesn't work.
//arrays of names and descriptions
var Names:[String] = []
var Desctiptions: [String] = []
inital method
override func viewDidLoad() {
super.viewDidLoad()
getRestauraunt()
//create slides
scrollView.delegate = self
slides = createSlides()
setupSlideScrollView(slides: slides)
}
func getRestauraunt(){
let db = Firestore.firestore()
db.collection("Test").getDocuments { (snapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in snapshot!.documents {
let name = document.get("Name") as! String
let description = document.get("Description") as! String
//print("Names: ",name," Description: ",description)
self.Names.append(name)
self.Desctiptions.append(description)
}
}
}
}
create slides method
func createSlides() -> [Slide] {
//firebase link
let slide1:Slide = Bundle.main.loadNibNamed("Slide", owner: self, options: nil)?.first as! Slide
slide1.labelTitle.text = Names[0]
}
I would like if someone could show me how to get the 'createSlides()' method to wait until the 'getRestauraunts()' method has finished. Thank you
Just call it from the end of the getrestaurant()'s getDocuments closure
func getRestauraunt(){
//as before...
} else {
for document in snapshot!.documents {
let name = document.get("Name") as! String
let description = document.get("Description") as! String
self.Names.append(name)
self.Desctiptions.append(description)
}
self.createSlides()
}
}
}
As an aside, it might also be worth creating a simple Document struct with name and description properties, and just having the one array: [Document]

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)")
}

Completion handler Firebase observer in Swift

I am making a completion handler for a function which will return a list of objects. When it return value for first time, it works well. But when any change happen into firebase database and again observe gets called, array size gets doubled up. Why it's getting doubled up?
func getStadiums(complition: #escaping ([Stadium]) -> Void){
var stadiums: [Stadium] = []
let stadiumRef = Database.database().reference().child("Stadium")
stadiumRef.observe(.value, with: { (snapshot) in
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)
})
}
And calling like this
getStadiums(){ stadiums
print(stadiums.count) // count gets doubled up after every observe call
}
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)
})
}
This line stadiumRef.observe(.value, with: { (snapshot) in ... actually adding an observer that will be called everytime your stadium data is changed.
Because you called it twice by using getStadiums(){ stadiums ..., the total observer added will be 2.
That makes the line stadiums.append(stadium) called twice in the second call.
My suggestion would be to use stadiumRef.observe() once without calling it from getStadiums().
Create a Model as below
class OrderListModel: NSObject {
var Order:String?
var Date:String?
}
Use the below code in the view controller and you should be able to see content in your tableview
func getOrdersData() {
self.orderListArr.removeAll()
let ref = Database.database().reference().child(“users”).child(user).child("Orders")
ref.observe(.childAdded, with: { (snapshot) in
print(snapshot)
guard let dictionary = snapshot.value as? [String : AnyObject] else {
return
}
let orderObj = OrderModel()
orderObj.Order = dictionary[“Order”] as? String
orderObj.Date = dictionary[“Date”] as? String
self.orderListArr.append(orderObj)
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.reloadData()
}, withCancel: nil)
}
func ListenForChildrenAdded() {
let registerToListenTo = "YourPathHere"
ref.child(registerToListenTo).observeSingleEvent(of: .value) { (snapshot) in
let initialChildren = snapshot.childrenCount
var incrementer = 0
ref.child(registerToListenTo).observe(.childAdded, with: { (snapshot) in
incrementer += 1
print("snapshot: \(snapshot.key) #\(incrementer)")
if incrementer == initialChildren {
print("-> All children found")
} else if incrementer > initialChildren {
print("-> Child Was Added - Run Some Code Here")
}
})
}}

Updating values in Firebase

func updateFirebase(){
myFun = thisIsMyFunTextView.text
IAm = iAmTextView.text
var profileKey = String()
profileRef.queryOrdered(byChild: "uid").queryEqual(toValue: userID).observe(.value, with:{
snapshot in
for item in snapshot.children {
guard let data = item as? FIRDataSnapshot else { continue }
guard let dict = data.value as? [String: Any] else { continue }
guard let profileKey = dict["profileKey"] else { continue }
self.profileRef.child(profileKey as! String).child("bodyOfIAM").setValue(IAm)
self.profileRef.child(profileKey as! String).child("bodyOfThisIsMyFun").setValue(myFun)
}
})
}
#IBAction func backButtonClicked(_ sender: Any) {
updateFirebase()
DispatchQueue.main.asyncAfter(deadline: .now() + 4, execute: {
self.dismiss(animated: true)
})
}
myFun and IAm are successfully defined by the changes to the textviews by the user. I can't extract the childByAutoID value without triggering this for in loop that does not end once called, continuing even as a new view controller is presented. The "bodyOfThisIsMyFun" vacillates between the old value and the new value during this loop while the "bodyOfIAM" gets correctly redefined right away and stays that way like it should. How do I get the extracted new values to replace the old values here?
I needed to add this line of code at the end of the for...in statement:
self.profileRef.removeAllObservers()

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()
}
}

Resources