How retrieve values from an object on Cloud Firestore? Swift - ios

I'm quite new on Firestore and Firebase libraries. Can someone explain how could I retrieve the fields from my object and stored in a variable on my code? I'm trying to access all the latMin, latMax, lonMin, and lonMax from each object in my Firestore, as well if is a way to retrieve each field with the same name (ex. latMin from place 1 and 2). I don't know if this is possible or maybe there is a better way to have organized my Firebase.
Here is my Cloud Firebase: Here is the image for my Firestore, requested in the comments.
place 1: //This is an Object in my Firestore
//Field = Number : Value ----> This is how object is orginazed
latMax : 39.727
latMin : 39.726
lonMax : -121.7997
lonMin : -121.8003
place 2:
latMax : 39.7559
latMin : 39.755
lonMax : -122.1988
lonMin : -122.1992
I was able to access the objects without any problem, this is the function I'm using to read the documents on my Firestore:
import UIKit
import FirebaseFirestore
import Firebase
import CoreLocation
class SecondViewController: UIViewController, CLLocationManagerDelegate {
//Creating access to locationManager
var locationManager : CLLocationManager!
#IBOutlet weak var latLabel: UILabel!
#IBOutlet weak var lonLabel: UILabel!
#IBOutlet weak var place: UILabel!
#IBOutlet weak var placeImage: UIImageView!
//Storing the pass data that we got from the firt View
var lonStore = String()
var latStore = String()
var fireLon = String()
var fireLat = String()
var lonNumberStore = Double()
var latNumberStore = Double()
var fireLonMax = Double()
var fireLatMax = Double()
var fireLonMin = Double()
var fireLatMin = Double()
override func viewDidLoad() {
//Here goes some code to display on the SecondViewController
readArray()
}
func readArray() {
let placeRef = Firestore.firestore().collection("places")
placeRef.getDocuments { (snapshot, error) in
guard let snapshot = snapshot else {
print("Error \(error!)")
return
}
for document in snapshot.documents {
let documentId = document.documentID
let latMax = document.get("latMax") as! String //Getting a nil error
print(documentId, latMax) //This print all objects
}
}
}
I'm missing something and I know it, the problem is that I can't find any reference of what I need to do in this case, I had read the documentation like 50 times and I can't find the solution. Thanks for taking the time to read my post.

Your readArray code is SUPER close. Just need to add code to read the individual fields within the document
func readArray()
self.db.collection("places").getDocuments { (snapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in snapshot!.documents {
let docId = document.documentID
let latMax = document.get("latMax") as! String
let latMin = document.get("latMin") as! String
let lonMax = document.get("lonMax") as! String
let lonMin = document.get("lonMin") as! String
print(docId, latMax, latMin, lonMax, lonMin)
}
}
}
The problem in the original question is the structure of the database. Here's a structure that will match the code in my answer and will be better suited for what the OP wants to accomplish.
The structure in the question has multiple places listed under one collection - that's OK but it groups the places together. If we change that to have one place per collection, it makes iterating over them and getting to the data much easier.

I think your places model have references to two objects bestBuy and house. So, the retrieval approach would be to retrieve the data from and store in the object only. Means you can directly set all the data in plcaes model. Then you can call getter methods of bestBuy and house to retrieve the data as you want. Here is a sampe code (here it is retrieving only one document) but you can apply the same for all documents by iterating a for loop and converting each document to object:-
let docRef = db.collection("cities").document("BJ")
docRef.getDocument { (document, error) in
if let city = document.flatMap({
$0.data().flatMap({ (data) in
return City(dictionary: data)
})
}) {
print("City: \(city)")
} else {
print("Document does not exist")
}
}
But according to firestore documentation the best way to store nested objects is creating a subcollection and then storing it in its document.

Some clarification since the iOS Firestore documentation is, IMO, light in many areas: there are two ways to retrieve the values from a document field (once the document has been gotten)--using a Firestore method (per the documentation) and using Swift's native method of getting values from dictionaries.
let query = Firestore.firestore().collection("zipCodes").whereField("california", arrayContains: 90210)
query.getDocuments { (snapshot, error) in
guard let snapshot = snapshot,
error == nil else {
return print("error: \(error!)")
}
guard !snapshot.isEmpty else {
return print("results: 0")
}
for doc in snapshot.documents {
// using firestore's convention
if let array = doc.get("zipCodes") as? [Int] {
if array.contains(90211) {
// doc contains both zip codes
}
}
// using swift's convention
if let array = doc.data()["zipCodes"] as? [Int] {
if array.contains(90211) {
// doc contains both zip codes
}
}
}
}
Personally, I would use as much of Firestore's framework as possible for safety and predictability and, therefore, use get() for all field retrieval.

Related

What is the proper way to read values from Firebase considering the MVC approach?

I'm quite new to Swift and currently dealing with the Firebase-Database.
I managed to realise the functions that I want to have, but my implementation feels not right.
Most I am struggling with the closures, that I need to get data from Firebase.
I tried to follow the MVC approach and have DataBaseManager, which is getting filling my model:
func getCollectionData(user: String, callback: #escaping([CollectionData]) -> Void) {
var dataArray: [CollectionData] = []
var imageArray:[String] = []
let db = Firestore.firestore()
db.collection(user).getDocuments() { (QuerySnapshot, err) in
if let err = err {
print("Error getting documents : \(err)")
}
else {
for document in QuerySnapshot!.documents {
let album = document.get("album") as! String
let artist = document.get("artist") as! String
let genre = document.get("genre") as! String
let location = document.get("location") as! String
var a = CollectionData(album: album, artist: artist, imageArray: imageArray, genre: genre, location: location)
a.imageArray!.append(document.get("fronturl") as? String ?? "No Image")
a.imageArray!.append(document.get("backurl") as? String ?? "No Image")
a.imageArray!.append(document.get("coverlurl") as? String ?? "No Image")
dataArray.append(a)
}
callback(dataArray)
}
}
}
With this I'm getting the information and the downloadlinks, which I later use in a gallery.
Is this the right way?
I feel not, because the fiddling starts, when I fetch the data from my ViewController:
var dataArray = []
dataBaseManager.getCollectionData(user: user) { data in
self.dataArray = data
I can see, that I sometimes run into problems with timing, when I use data from dataArray immediately after running the closure.
My question is, this a valid way to handle the data from Firebase or is there a more elegant way to achieve this?
You are on the right track. However, using dataArray immediately is where the issue could be.
Let me provide a high level example with some pseudo code as a template:
Suppose you have an ToDo app; the user logs in and the first view they see is all of their current To Do's
class viewController
var dataArray = [ToDo]() //a class property used as a tableViewDataSource
#IBOutlet toDoTableView
viewDidLoad {
loadToDos()
}
func loadToDos() {
thisUsersToDoCollection.getDocuments() { documents in
self.array.append( the to Do Documents)
self.toDoTableView.reloadData()
}
}
}
With the above template you can see that within the Firebase .getDocuments function, we get the To Do documents from the colleciton, populate the dataSource array and THEN reload the tableView to display that data.
Following this design pattern will alleviate this issue
I sometimes run into problems with timing, when I use data from
dataArray immediately after running the closure
Because the user cannot interact with the data until it's fully loaded and populated within the tableView.
You could of course do a callback within the loadToDos function if you prefer so it would then be called like this - only reload the tableView once the loadToDos function has completed
viewDidLoad {
loadToDos {
toDoTableView.reloadData()
}
}
The big picture concept here is that Firebase data is ONLY VALID WITHIN THE CLOSURE following the Firebase call. Let that sequence provide pacing to your app; only display info if it's valid, only allow the user to interact with the data when it's actually available.

How to Get Reference to Document Array from Firestore

This is the structure of my Firestore data right now:
I'm having trouble just accessing the favourites array. I seem to be accessing the collection and document just fine, but I can't seem to pull the favourites array in my code.
This is the relevant code in my data model
protocol UserFavouritesProtocol {
func userFavouritesRetrieved(userFavourites:[UserFavourites])
}
var delegateTest:UserFavouritesProtocol?
func getFavourites() {
// Detach any listener
listener?.remove()
// Get a reference to the database
let db = Firestore.firestore()
var query:Query = db.collection("userFavourites")
self.listener = query.addSnapshotListener({ (snapshot, error) in
// Check for errors
if error == nil && snapshot != nil {
var userFavourites = [UserFavourites]()
// Parse documents into mealPlans
for doc in snapshot!.documents {
let u = UserFavourites(
favouriteMealPlans: doc["favourites"] as? [String])
userFavourites.append(u)
}
// Call the delegate and pass back the notes in the main thread
DispatchQueue.main.async {
self.delegateTest?.userFavouritesRetrieved(userFavourites: userFavourites)
}
}
})
}
This is my UserFavourites struct:
struct UserFavourites {
var favouriteMealPlans:[String]?
}
At this point, how can I access the favourites array and call a reference to it? For example, I later want to see if a particular string is contained within my favourites array.
This will get you closer to your solution...
// struct
import FirebaseFirestore
struct UserFavourites {
var favouriteMealPlans:[DocumentReference]?
}
// usage
let u = UserFavourites(favouriteMealPlans: doc["favourites"] as? [DocumentReference])

SwiftUI: How to iterate over Documents in Cloud Firestore Collection and get Data?

I have this small project where a user can post an Image together with a quote, I would then like to display the Image and the quote togehter in their profile, as well as somewhere else so other users can see the post.
If I have this Cloud Firestore setup
where all of the Image Docs have the same 3 fields, but with different values.
How can I then iterate over all of the Image Docs and get the the Url and the quote? So I later can display the url together with the correct Quote?
And if this is for some reason not possible, is it then possible to get the number of Documents in a Collection?
BTW, I am not very experienced so I would appreciate a "kid friendly" answer if possible
Firestore
.firestore()
.collection("Images")
.getDocuments { (snapshot, error) in
guard let snapshot = snapshot, error == nil else {
//handle error
return
}
print("Number of documents: \(snapshot.documents.count ?? -1)")
snapshot.documents.forEach({ (documentSnapshot) in
let documentData = documentSnapshot.data()
let quote = documentData["Quote"] as? String
let url = documentData["Url"] as? String
print("Quote: \(quote ?? "(unknown)")")
print("Url: \(url ?? "(unknown)")")
})
}
You can get all of the documents in a collection by calling getDocuments.
Inside that, snapshot will be an optional -- it'll return data if the query succeeds. You can see I upwrap snapshot and check for error in the guard statement.
Once you have the snapshot, you can iterate over the documents with documents.forEach. On each document, calling data() will get you a Dictionary of type [String:Any].
Then, you can ask for keys from the dictionary and try casting them to String.
You can wee that right now, I'm printing all the data to the console.
Keep in mind that getDocuments is an asynchronous function. That means that it runs and then returns at an unspecified time in the future. This means you can just return values out of this function and expect them to be available right after the calls. Instead, you'll have to rely on things like setting properties and maybe using callback functions or Combine to tell other parts of your program that this data has been received.
If this were in SwiftUI, you might do this by having a view model and then displaying the data that is fetched:
struct ImageModel {
var id = UUID()
var quote : String
var url: String
}
class ViewModel {
#Published var images : [ImageModel] = []
func fetchData() {
Firestore
.firestore()
.collection("Images")
.getDocuments { (snapshot, error) in
guard let snapshot = snapshot, error == nil else {
//handle error
return
}
print("Number of documents: \(snapshot.documents.count ?? -1)")
self.images = snapshot.documents.compactMap { documentSnapshot -> ImageModel? in
let documentData = documentSnapshot.data()
if let quote = documentData["Quote"] as? String, let url = documentData["Url"] as? String {
return ImageModel(quote: quote, url: url)
} else {
return nil
}
}
}
}
}
struct ContentView {
#ObservedObject var viewModel = ViewModel()
var body : some View {
VStack {
ForEach(viewModel.images, id: \.id) { item in
Text("URL: \(item.url)")
Text("Quote: \(item.quote)")
}
}.onAppear { viewModel.fetchData() }
}
}
Note: there are now fancier ways to get objects decoded out of Firestore using FirebaseFirestoreSwift and Combine, but that's a little outside the scope of this answer, which shows the basics

How to make firebase .observeSingleEventOf function run synchronously

I am running Firebase's .getSingleEventOf function to read data from my database in my program, and it is an asynchronous function. How would I make it synchronous (or make another synchronous function to house the code)?
I've tried to use the data passed through the function, but it hasn't been working! It only returns 0 elements, even though I know that I have data with the specific letters in my database.
import Foundation
import Firebase
struct CompeteUserFinderService {
static func findCompetitors(contains letters: String?) -> [String] {
//Make sure there was an input
guard let letters = letters else { return [] }
var usernames = [String]()
//Database reference
let ref = Database.database().reference().child("usernames")
ref.observeSingleEvent(of: .value) { (snapshot) in
//Creates an array of all values in the usernames branch
let value = Array((snapshot.value as! [String: Any]).keys)
usernames = value.map { $0.lowercased() }.filter { $0.contains(letters.lowercased()) }
}
return usernames
}
}

Error when QuerySnapshot runs in Firestore - Throws error Skipping epollex becuase skipping EPOLLEX GRPC_LINUX_EPOLL is not defined

I have created a function to pull a random document from my firestore collection, which then returns a FIRQuery. Once i got that, I created a model file to parse any FIRQuery data from my firestore document. However, once i run it and attempt to print it to the log, I get the response of "Skipping epollsig becuase GRPC_LINUX_EPOLL is not defined." 4 times, which makes sense as i am trying to print 4 fields of my document. My code for the parsing is below - keep in mind any CAPS values are constants that are just defined as equal to exactly how they read in the firestore document field
class randomModel {
private(set) var quote: String!
private(set) var randomInt: Int!
private(set) var approved: Bool!
private(set) var source: String!
init(approved: Bool, quote: String, randomInt: Int, source: String)
{
self.approved = approved
self.quote = quote
self.randomInt = randomInt
self.source = source
}
class func parseData(snapshot: QuerySnapshot?) -> [randomModel] {
var quoteStructure = [randomModel]()
guard let snap = snapshot else {return quoteStructure}
for document in (snap.documents) {
let data = document.data()
let quote = data[DATABASE_QUOTE_VALUE_FIELD] as? String
let randomInt = data[DATABASE_RANDOM_INTEGER_VALUE_FIELD] as? Int
let approved = data[DATABASE_APPROVED_VALUE_FIELD] as? Bool
let source = data[DATABASE_SOURCE_VALUE_FIELD] as? String
let parsedData = randomModel(approved: approved!, quote: quote!, randomInt: randomInt!, source: source!)
quoteStructure.append(parsedData)
}
return quoteStructure
}
Then my code to actually print the data and pull a random document is here:
#IBAction func generatePressed(_ sender: Any) {
let quoteRef = Firestore.firestore().collection("Quotes")
let queryRef = quoteRef.whereField(DATABASE_RANDOM_INTEGER_VALUE_FIELD, isGreaterThan: 1)
.order(by: "randomInt")
.limit(to: 1)
.addSnapshotListener { (snapshot, error) in
if let err = error {
debugPrint("Error Fetching Document \(err)")
} else {
print(randomModel.parseData(snapshot: snapshot))
}
}
}
So can someone guide me as to what I am doing wrong to throw this error. My Document structure looks like this in firestore.. sorry, I don't have any rep to be able to embed into a post yet. Thanks for the help in advance!!
This is a bug that was fixed in gRPC 1.8.4. Run pod update.

Resources