Firebase getting specific parent not all children SWIFT - swift

This is my first question here on stackoverflow, but I can't figure this out for the life of me. I am using Xcode and Swift 4.
I want to read a certain child level from Firebase to make a tableView based on the results there. Below is my Firebase data:
I want to make a tableView that lists Bill and Josh which is under "subNames" I seem to only make a snapshot that includes all of Bill and Josh's children as well. I just want the list of items directly in the subNames child. Anybody know how to do this? I am sure I am missing something..
This is my code:
var ref: DatabaseReference!
var refHandle: UInt!
override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference()
}
func loadmoreData () {
refHandle = ref?.child("Users").child(FBuid).observe(.value, with : { (snapshot) in
let mynamesArray = snapshot.childSnapshot(forPath: "subNames")
print (mynamesArray)
})

To get an array with just the keys under subNames, you'll need something like this:
refHandle = ref?.child("Users").child(FBuid).child("subNames").observe(.value, with : { (snapshot) in
let mynamesArray = [String]()
for child in snapshot.children {
mynamesArray.append(child.key)
}
print (mynamesArray)
})

Related

How do I add data from a user in firebase rather than replace what is already there?

The app presents users with a random quote. I want users to be able to save the quotes and see which ones they saved. I can get a single quote to save, however, anytime the save button is clicked again, it overrides the previously saved quote with the new one. I've tried to find the answer elsewhere, but I cannot seem to get it to work. Below is my current code. I've also tried replacing setValue with updateChildValues, but I get the error Cannot convert value of type 'String' to expected argument type '[AnyHashable : Any]'.
import UIKit
import FirebaseDatabase
import FirebaseAuth
class QuotesViewController: UIViewController {
var ref: DatabaseReference?
override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference()
}
#IBAction func backToMain(_ sender: UIButton) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(identifier: "mainHome")
vc.modalPresentationStyle = .overFullScreen
present(vc, animated: true)
}
#IBOutlet weak var quotesLabel: UILabel!
#IBAction func saveButton(_ sender: UIButton) {
guard let user = Auth.auth().currentUser?.uid else { return }
ref!.child("users").child(Auth.auth().currentUser!.uid).child("Quotes").child("quote").setValue(quotesLabel.text!)
}
#IBOutlet weak var nextButtonOutlet: UIButton!
#IBAction func nextQuoteButton(_ sender: UIButton) {
let quotesData = QuotesData()
let randomQuote = quotesData.randomQuote()
quotesLabel.text = randomQuote
}
}
I've also tried:
ref!.child("users").child(Auth.auth().currentUser!.uid).child("Quotes").child("quote").updateChildValues(quotesLabel.text!)
That is expected, you basically are referencing the very same node child("quote") and trying to change its value. But if you want to have multiple quotes, what you need to do is to create multiple nodes under the parent child("Quotes") with different names.
One trivial way of doing so, you might append a different number to each new quote node, for example when you want to add a new quote, define the following path:
child("Quotes").child("quote1").setValue("...")
Path for another quote:
child("Quotes").child("quote2").setValue("...")
And so on.
Alternatively, you can use Firebase Database reference method childByAutoId() to generate unique names. You will use that method after defining the parent node:
ref!.child("users").child(Auth.auth().currentUser!.uid).child("Quotes").childByAutoId().setValue(quotesLabel.text!)
Note:
Try to avoid force unwrapping as much as you can because that makes your app more prone to crashes.
I think you have to add one more field inside your firebase database that must be a unique one
For first time adding data into firebase you can write something like this
let key = ref.child("Quotes").childByAutoId().key
let dict = ["quote": quotesLabel.text!,
"quoteId" : key ?? ""
] as [String: Any]
And whenever you are saving the same quote then you can update the value inside the particular quoteID like this
func updateDatainFirebase(quoteId:String,quote:String){
let dict = ["quote": quotesLabel.text!,
"quoteId" : quoteId ?? ""
] as [String: Any]
self.ref.child(quoteId).updateChildValues(dict)
}

How do you force the ViewController to export the data in 'b' instead of 'a' to the next VC?

This is my first attempt at XCode, used to work on MatLab so very new to this. I have a VC here (named guests2ViewController) which I'm trying to send data to the next VC (named resultsViewController). Unfortunately the VC is exporting the data from the lines (print "b") instead of the earlier lines (print "a"). Why is this, and how could I fix it?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as? resultsViewController
if segue.identifier == "toresults"{
//prioritise this part of code - call it part A
Database.database().reference(withPath: self.password).observeSingleEvent(of: .value, with: { snapshot in
//dates
var arr = [String]()
//times
var arr1 = [String]()
//number of attendees
var arr2 = [String]()
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? DataSnapshot {
let newobj=rest.children
while let rest1 = newobj.nextObject() as? DataSnapshot {
arr.append(rest.key)
arr1.append(rest1.key)
arr2.append(String(rest1.childrenCount))
let guests = arr2.max()
var index = 0
for n in arr2 {
if n == guests {
//print (index)
let datefromarray = String(arr[index])
let timefromarray = String(arr1[index])
let guests1 = String(Int(guests!)!)
vc?.databasedate = (datefromarray)
vc?.databasetime = (timefromarray)
vc?.databaseguests = (guests1)
break
}
index += 1
}
}
}
print(vc?.databasedate)
print(vc?.databasetime)
print(vc?.databaseguests)
print("a")
//this doesn't get sent to the next VC after taking the data from Firebase
})
}
print(vc?.databasedate)
print(vc?.databasetime)
print(vc?.databaseguests)
print("b")
//this gets sent to the next VC
}
}
Data is loaded from Firebase (and most modern web APIs) asynchronously. This is to prevent blocking the user from using your app, while the (possibly slow) network request is in progress.
If you check in a debugger, you'll see that print("b") happens before vc?.databasedate = (datefromarray), which explains why you're passing the wrong value to the next view controller.
The solution is always the same: any code that needs data from the database needs to be inside the callback/closure/completion handler, or be called from there.
For more on this and code examples, see:
Firebase with Swift 3 counting the number of children
Why isn't my function that pulls information from the database working?
Access Firebase variable outside Closure
getting data out of a closure that retrieves data from firebase
Swift / How to use dispatch_group with multiple called web service?
Completion handler Firebase observer in Swift

Swift load firebase database data when there is a change in data

I have a tableview set up that is fetching my firebase database info like so below. How do I set up a listener to look for any new database changes? At the moment, while looking at my tableview, I can add a new item to the database but won't see the change on my tableview until i reload that view again.
var markets: [loadMyData] = []
var marketSelectedText = ""
var myKey = [Any]()
override func viewDidLoad() {
super.viewDidLoad()
ref.child("Markets").observeSingleEvent(of: .value, with: { (snapshot) in
guard snapshot.exists() else
{
print("Null Markets")
return
}
let allMarkets = (snapshot.value as! NSMutableDictionary).allKeys
self.myKey = allMarkets
self.tableView.reloadData()
print(self.myKey, " This is the printing of my key")
})
ref.child("Markets").observe(.value, with: { snapshot in
print(snapshot.value as Any, " ref.observe")
self.tableView.reloadData()
})
}
Right now you're using:
ref.child("Markets").observeSingleEvent(of: .value,
This observes the current data under Markets and then stops observing.
If you want to get both the current data and any changes, use observe(DataEventType.value.
Firebase is providing listeners to observe database changes, kindly check the firebase documentation https://firebase.google.com/docs/database/ios/lists-of-data
FIRDataEventTypeChildChanged
this is the event for listening database changes
commentsRef.observe(.childChanged , with: { (snapshot) in
// here you will get the changed node value here in snapshot.value
})

Swift after iterating list.count always 0 [duplicate]

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

With Firebase, Swift removeObserver(withHandle does not remove the observer

With removeObserver(withHandle in Swift 3, the Observer is not removed on viewDidDisappear
var query = FIRDatabaseQuery()
var postRef: FIRDatabaseReference!
var postRefHandle: FIRDatabaseHandle?
override func viewDidLoad() {
super.viewDidLoad()
postRef = baseRef.child("Posts")
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if postRefHandle != nil {
//try 1:
//postRef.removeObserver(withHandle: postRefHandle!)
//try 2:
//postRef.queryOrdered(byChild: "sortTimestamp").removeObserver(withHandle: postRefHandle!)
//try 3:
//query.removeObserver(withHandle: postRefHandle!)
}
//try 4:
//postRef.removeAllObservers() //works
}
func getPosts()
{
var count = 20
query = postRef.queryOrdered(byChild: "sortTimestamp")
postRefHandle = query.queryLimited(toFirst: UInt(count)).observe(.childAdded //etc.
}
So I tried the three methods in viewDidDisappear, but the observer is not removed.
try 3 query.removeObserver(withHandle: postRefHandle!) as by answer from Firebase, how do I return a handle so that I can call removeObserver? by frank-van-puffelen
The only one that does work is the one outlined in try 4.
Any reason why I cannot remove the Observer with removeObserver(withHandle? (try 1 - 3)
Also "query.queryLimited(toFirst: UInt(count)).observe(.childAdded" does not get the latest data from Firebase. I was under the impression the observe always gets the updated data, as opposed to observeSingleEvent. Why does it not do that?
Any suggestions are much appreciated.
If you have the following code:
var postsRef: FIRDatabaseReference!
var postRefHandle: FIRDatabaseHandle!
var query = FIRDatabaseQuery()
func addHandler() {
self.postsRef = self.ref.child("posts")
var count = 20
self.query = self.postsRef.queryOrdered(byChild: "sortTimestamp")
self.postRefHandle = self.query.queryLimited(toFirst: UInt(count)).observe(.childAdded, with: { snapshot in
print(snapshot)
})
}
and at a later time you do this function
self.postsRef.removeObserver(withHandle: self.postRefHandle!)
It removes the observer. This is tested code.
To the second part of your question: querySingleEvent and observe do the same thing data wise but have different behaviors. They will both always get current data - modified by startAt, endAt, equalTo etc.
observeSingleEvent returns the data, does NOT leave an observer so you
will not be notified if that data changes
observe returns the data and leaves an observer attached to the node
and will notify you of future changes.
.childAdded: when any children are added to the node
.childChanges: when any children change in the node
.childRemoved: when a child is removed.
How I'm Able to Achieve this is by removing child reference.
var recentRef: FIRDatabaseReference!
recentRef.child("\(groupId)").observe(.value, with: { (snapshot) in
recentRef.removeAllObservers() // not_working
recentRef.child("\(groupId)").removeAllObservers() //working
if let obj = snapshot.value as? [String: AnyObject] {
//... code here
}
})
You can achieve this without making a query also(Swift 4) -
This removes the reference of the observer properly and works for me.
private let ref = Database.database().reference().child("classTalks")
private var refHandle: DatabaseHandle!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear()
refHandle = ref.observe(.value, with: { (snapshot) in
...
})
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear()
ref.removeObserver(withHandle: refHandle)
}