How to layout Firebase database - swift

I want to know how to layout a Firebase database so I can easily get Data about coordinates. (Only started using Firebase yesterday so am very very new to it)
ViewController
var reference: DatabaseReference!
var handle: DatabaseHandle?
viewDidLoad
reference = Database.database().reference()
handle = ref.child("Places/America/Capital/Washington/Coordinates").observe(.value, with: { (snapshot) in
for child in snapshot.children {
let theSnap = child as! DataSnapshot
let theValue = theSnap.value as! Double
print(theValue)
}
This prints the coordinates perfectly. But is there any way I can get all the coordinates in my Database? Am I laying out the Database wrong or what syntax should I use in order to get all data that has latitude and longitude in it?
Image of Firebase Database
edit 1
Is there no easy way of doing a for-in loop instead of just copying the handle line and changing the necessary file path?

You are so very close.
The path you specified (the Firebase Reference) is for a specific set of coordinates at a coordinate node. You then iterate over the two child nodes within Coordinates which prints the latitude and longitude child nodes.
If you want to get the coords for all, you need to go up a few levels and use the same technique to iterate over the child nodes.
It's a bit hard to wrap your brain around at first so let me give you a leg up. Here's the complete code and the output. Note that I stored the coords as int's so adjust accordingly. I would probably suggest storing them as strings.
let placesRef = self.ref.child("Places")
placesRef.observeSingleEvent(of: .value, with: { snapshot in
for countryChild in snapshot.children { //iterate over each country
let countryChildSnap = countryChild as! DataSnapshot //cast the child to a snapshot
let countryName = countryChildSnap.key //get it's key, the country name
print("country: \(countryName)")
let capitalSnap = countryChildSnap.childSnapshot(forPath: "Capital") //get the nodes within the capital node
for stateChild in capitalSnap.children { //iterate over each state within the country
let stateChildSnap = stateChild as! DataSnapshot
let stateName = stateChildSnap.key
print(" state: \(stateName)")
let coordsSnap = stateChildSnap.childSnapshot(forPath: "Coordinates")
let lat = coordsSnap.childSnapshot(forPath: "latitude").value as! Int
let lon = coordsSnap.childSnapshot(forPath: "longitude").value as! Int
print(" latitude: \(lat) longitude: \(lon)")
}
}
})
and the output
country: America
state: Washington
latitude: 38 longitude: -77
country: France
state: Paris
latitude: 48 longitude: 2
country: Germany
state: Berlin
latitude: 52 longitude: 13
Note that I only want to read this once so I am using .observeSingleEvent. If you want to be notified of future changes, leverage .observe with .childAdded, .childChanged and .childRemoved.

Related

Querying GeoHashes in Firestore returns nothing

The code that retrieves all locations from Firestore within a 50km location of a given point is given on the Firebase website. Here it is:
// Find cities within 50km of London
let center = CLLocationCoordinate2D(latitude: 51.5074, longitude: 0.1278)
let radiusInM: Double = 50 * 1000
// Each item in 'bounds' represents a startAt/endAt pair. We have to issue
// a separate query for each pair. There can be up to 9 pairs of bounds
// depending on overlap, but in most cases there are 4.
let queryBounds = GFUtils.queryBounds(forLocation: center,
withRadius: radiusInM)
let queries = queryBounds.map { bound -> Query in
return db.collection("cities")
.order(by: "geohash")
.start(at: [bound.startValue])
.end(at: [bound.endValue])
}
var matchingDocs = [QueryDocumentSnapshot]()
// Collect all the query results together into a single list
func getDocumentsCompletion(snapshot: QuerySnapshot?, error: Error?) -> () {
guard let documents = snapshot?.documents else {
print("Unable to fetch snapshot data. \(String(describing: error))")
return
}
for document in documents {
let lat = document.data()["lat"] as? Double ?? 0
let lng = document.data()["lng"] as? Double ?? 0
let coordinates = CLLocation(latitude: lat, longitude: lng)
let centerPoint = CLLocation(latitude: center.latitude, longitude: center.longitude)
// We have to filter out a few false positives due to GeoHash accuracy, but
// most will match
let distance = GFUtils.distance(from: centerPoint, to: coordinates)
if distance <= radiusInM {
matchingDocs.append(document)
}
}
}
// After all callbacks have executed, matchingDocs contains the result. Note that this
// sample does not demonstrate how to wait on all callbacks to complete.
for query in queries {
query.getDocuments(completion: getDocumentsCompletion)
}
The issue that I am having is that matchingDocs (the empty array that the locations from the database are supposed to append to) returns empty every time.
I have double checked that the center and example locations in my database are within 50km of each other. The code is able to retrieve the four documents in my database and I know this because if I put a print statement in the last for loop, I get something printed 4 times.
I need an explanation on exactly what the getDocumentsCompletion function does because I don't understand fully what it does, or how the call works. There are no arguments passed in where the function is called.
I have also added print statements within the getDocumentsCompletion function, but nothing ever gets printed out, so I believe that my issue lies there. I would like an explanation on what exactly is going on there so I can better address the issue.

GeoFire Circle Query NSException

I'm using Firebase with GeoFire 3.0 Cocoapod in my Swift App to populate a map with markers all over the world. Here is the code to perform the circle query to get the markers within the area currently displayed on the map:
dbRef = Database.database().reference()
let geoRef = GeoFire(firebaseRef: dbRef.child("markers"))
let center = CLLocation(latitude: currentLocation.latitude, longitude: currentLocation.longitude)
print("Center: "," Lat: ",currentLocation.latitude," Long: ",currentLocation.longitude )
let circleQuery = geoRef.query(at: center, withRadius: 100)
circleQuery.observe(.keyEntered, with: { key, location in
print("key: ",key,"Location: ",location)
let markerKey = key
let markerLat = location.coordinate.latitude
let markerLong = location.coordinate.longitude
//read "verified" flag from firebase record "key"
self.dbRef.child(markerKey).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let verified = value?["verified"] as? Bool
print("key: ",key,"Location: ",location,"verified: ",verified as Any)
...
})
})
When the circle query is expanded by the user zooming out to display the map of the entire world (radius of 8000 Km (4791 MI)), the query aborts with an NSException.
The Xcode debugger shows GeoFire has calculated a latitude of 105.9793939... and longitude of -112.05707936...
Geofire should restrict the latitude at +/- 90 and the longitude at +/- 180 and in that case all GeoFire data should be returned from the Query.
Here is a screenshot of the error in Xcode:
Xcode Error Screenshot
Has anyone else seen this issue and/or found a solution?
Since GeoFire clearly does not limit the latitude and longitude in the way you want it to, you have two options
Report an issue on the GeoFire repo, and possibly propose a PR yourself.
Limit the values to the ranges you want in your own application code.
I'd suggest going with the second approach, since you'll want/need to show that you clipped/restricted the range in the UI anyway.

Firebase-Swift How to sum a child values together

I need to retrieve price value of each product in the cart child, but how should I retrieve it and sum the retrieved value together?
Picture of my Firebase database structure
let uid = Auth.auth().currentUser?.uid
refProduct = Database.database().reference().child("users").child(uid!).child("cart")
refProduct.observeSingleEvent(of: .value) { (snapshot) in
for cartchild in snapshot.children{
let snap = cartchild as! DataSnapshot
let key = snap.value
.....
}
}
I would not store the price as a string, but as a number. You might want to add another field with currency if needed.
guard let uid = Auth.auth().currentUser?.uid else { return }
var sum: Double = 0
refProduct = Database.database().reference().child("users").child(uid).child("cart")
refProduct.observeSingleEvent(of: .value) { (snapshot) in
for cartchild in snapshot.children{
let snap = cartchild as! DataSnapshot
let data = snap.value as? [String: Any]
let price = data["ProductPrice"] as? Double ?? 0
sum += price
}
print("Final sum: \(sum)")
}
Not really tested, but this is the idea
Arvidurs is correct about storing the price as an int and the currency as a string, but the reason the answer isn't working for you is that it doesn't address that you're not correctly retrieving the data you want in the first place.
You have your cart folder, and it contains two product folders whose properties you're trying to retrieve. You can't retrieve and unwrap the values contained in those two folders by just referencing the parent cart folder. You need to individually access each folder within cart:
Database.database().reference().child("users").child(uid).child("cart").child("-Lf59bkQ5X3ivD6ue1SA")
Database.database().reference().child("users").child(uid).child("cart").child("-Lf5MiEGU357HWTMbxv8")
However, for this to work, you'll need access to each products autoID value, so you'll need to be storing each new product's childByAutoID value into an array or a dictionary so that you have them all available to access whatever data you need.
You'll need to implement this as you're storing the new product to the cart folder. I don't know exactly how you're currently saving each product, but you'll need to do something like this when you create your reference that you'll be saving to:
let newProductRef = Database.database().reference().child("users").child(uid).child("cart").childByAutoId()
let autoID = newProductRef.key
At that point, you'll be able to store autoID however you choose, and you'll have access to everything within the cart folder, and you can loop through all of your autoIDs and get whatever data you need. Example:
func getCartPriceSum(finished: #escaping ([String : Double]) -> Void){
let myGroup = DispatchGroup()
var sum = Double()
var currency = String()
for autoID in autoIdArray{
myGroup.enter()
let productRef = Database.database().reference().child("users").child(uid).child("cart").child(autoID)
productRef.observe(.value) { (snapshot) in
guard
let snapshotValue = snapshot.value as? NSDictionary,
let productPrice = snapshotValue["ProductPrice"] as? Double,
let priceCurrency = snapshotValue["PriceCurrency"] as? String//assuming you've adopted Arvidurs' method of storing the price data
else {
print("productPrice/priceCurreny nil")
return
}
sum += productPrice
currency = priceCurrency
}
myGroup.leave()
}
let priceSum = [currency : sum]
myGroup.notify(queue: .main) {
finished(priceSum)
}
}
And you could call the function like this:
getCartPriceSum { (priceSum) in
//do whatever you want with the priceSum
}
The only thing left for you to figure out is how you want to store those autoIDs.

Firestore get array data at index 0

Below I have some data in firestore.
I have a an array in the _geoloc field. Each of those indexes have latitude and longitude coordinates. So using SWIFT I want to be able to only get the lat and lng coordinates at index 0 and pass them to individually to a string. I have been researching for days and I am stuck. I have tried to create a new array of strings or an AnyObject array and I just get stuck with retrieving the data I need at index 0, and passing only those 2 lat/lng coordinates to string values. I have several failed snippets of code I could post.
Here is a snippet of what I was attempting to do: (I am really new to firebase so the code is a bit ugly as I am just trying to figure this out)
Firestore.firestore().collection("users").document("626").getDocument { (document, error) in
if let document = document {
// let geo_array = document["_geoloc"]
var yourArray = [String]()
// let geo_location = [geo_array] as [AnyObject]
let array: [Any] = document["_geoloc"] as! [Any]
let tmpArray = array.map({ return String(describing: $0)})
let string = tmpArray.joined(separator: ",")
yourArray.append(string)
print(yourArray[0])
You just need to cast your object from Any to an array of dictionaries and get the first property:
Firestore.firestore().collection("users").document("626").getDocument { document, error in
if let document = document {
var yourArray: [String] = []
if let location = (document["_geoloc"] as? [[String:Double]])?.first,
let latitude = location["lat"],
let longitude = location["lon"] {
let coordinate2d = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
yourArray.append("Latitude: \(location.latitude), Longitude: \(location.Longitude)")
}
}
}
Maybe first declare this:
var firstPositionValues = [AnyObject]()
Then get it like this:
let db = Firestore.firestore().collection("users").document("626")
db.getDocument { (document, error) in
if let document = document {
let geo_location = document["_geoloc"] as [AnyObject] // <- this is all of them in the document
var initialValues = geo_location[0] // <- here are your lat and lng at position 0
self.firstPositionValues = initialValues
}
Hope I understood correctly good luck.

Swift, Firebase: Implement Geolocation/ User posts in set circle [duplicate]

I want to find nearest users to my location. (For example up to 5km) My nodes in firebase database like below structure,
+ users
+ user_id0
- latitude: _
- longitude: _
Is there any way getting exact users in that radius. Otherwise I should check each user of them nearest position or not to me using CLLocation distance method.
I'd highly recommend using Geofire for something like this.
To set it up, your data structure will slightly change. You can still store lat/lng on your users, but you will also create a new Firebase table called something like users_locations
Your users_locations table will be populated through Geofire, and will look something like this
users_locations
user_id0:
g: 5pf666y
l:
0: 40.00000
1: -74.00000
In general, this is how you would store a location in Geofire, but you can set it up to save whenever your user object is created / updates location.
let geofireRef = FIRDatabase.database().reference().child("users_locations")
let geoFire = GeoFire(firebaseRef: geofireRef)
geoFire.setLocation(CLLocation(latitude: lat, longitude: lng), forKey: "user_id0")
When you've saved your locations in users_locations, you can then use a GFQuery to query for all the users in a certain range.
let center = CLLocation(latitude: yourLat, longitude: yourLong)
var circleQuery = geoFire.queryAtLocation(center, withRadius: 5)
var queryHandle = circleQuery.observeEventType(.KeyEntered, withBlock: { (key: String!, location: CLLocation!) in
println("Key '\(key)' entered the search area and is at location '\(location)'")
})