Adding a custom object to an array in Firestore - ios

I am making an ordering app for customers to order their specific specs. When the user logs in they can go to a tab that contains a tableview with all their specs, once they click on a cell it will take them to a new view controller that will display more information on the spec. Once on this view controller they will have the ability to add x amount of pallets/rolls/etc of that item. I am able to add the spec to Firestore, but I cannot get it to an array in Firestore which I need. My goal is that on anther tab the user can view all the current specs they are trying to order until they hit submit. I am currently using the user.uid to get to that specific customers orders inside Firestore.
Code:
#IBAction func addPallet(_ sender: Any) {
// Get the current user
let user = Auth.auth().currentUser
if let user = user {
_ = user.uid
}
if spec != nil {
// Get the qty ordered for that spec
let totalQty: Int? = Int(palletsToAddTextField.text!)
let qty = spec!.palletCount * totalQty!
let specToAdd = Spec(specNumber: spec!.specNumber,
specDescription: spec!.specDescription,
palletCount: spec!.palletCount,
palletsOrdered: qty)
orderedArray.append(specToAdd)
let specAdded: [String: Any] = [
"SpecDesc": spec!.specDescription,
"SpecNum": spec!.specNumber,
"PalletCount": spec!.palletCount,
"PalletsOrder": qty
]
db.collection("orders").document(user?.uid ?? "error").setData(specAdded) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
}
}
code for spec:
struct Spec: Codable {
// Properties
var specNumber: String
var specDescription: String
var palletCount: Int
var palletsOrdered = 0
enum CodingKeys: String, CodingKey {
case specNumber
case specDescription
case palletCount
case palletsOrdered
}
}
I need something added like the below picture. The user will add x amount of pallets, then going to the next spec they want and add it to the array in Firestore as well.

Ok i guess i get what you want to do. Try:
db.collection("orders").document(userID).setData(["allMyData" : myArray])
"allMyData" will be the name of the field in which you want to save your array and myArray would be your array (specAdded). Thats what you are looking for?
If the document already exists you will want to use .updateData instead of .setData to keep all other fields that might already exist in that specific doc.
Kind regards

Related

Trying to figure out a better way to delete element from Firestore dictionary

I'm using Firestore in my application, and I have a map field called "votes" for user's upvotes or downvotes. It looks like this:
I want to add an option to delete an element from there, this is what I got now:
//getting the user's votes dictionary and removing the post from it.
userRef.getDocument { (doc, error) in
if let _ = error { completion(false) }
guard let dict = doc?.data()?[USER_VOTES] as? [String: Any] else { return }
currentDict = dict
currentDict.removeValue(forKey: id)
}
//setting the votes dictionary with the updated one.
userRef.setData(currentDict) { (error) in
if let _ = error { completion(false) }
else { completion(true) }
}
to me, It looks not really efficient, because each time a user is trying to remove an element from this dictionary, I have to write to the database. which can slow down the process and to my understanding, the free tier of Firestore limits the number of writes.
Is there a better way, maybe deleting it right from the user's document? I tried to look for answers, but couldn't find anything that worked for me.
This one for example: Removing a dictionary element in Firebase looks like what I need to do, but I couldn't get it to work.
EDIT:
I tried deleting it like that
userRef.updateData([
USER_VOTES:[
id: FieldValue.delete()
]
]) { (error) in
if let _ = error { completion(false) }
}
The app crashes says:
Terminating app due to uncaught exception 'FIRInvalidArgumentException', reason: 'FieldValue.delete() can only appear at the top level of your update data
To be able to delete a specific field you should follow the steps mentioned here.
For your case I have created the following under collection 'voting':
So to delete vote2 field you should use:
// Get the `FieldValue` object
let FieldValue = require('firebase-admin').firestore.FieldValue;
// Create a document reference
let fieldRef = db.collection('voting').doc('votes');
// Remove the 'vote2' field from the document 'votes'
let removeField = fieldRef.update({
vote2: FieldValue.delete()
});
And here is the document after running the above:
EDIT :
If the data model is a map inside a document, for example:
Then here is how you can delete a field inside the array which is inside the document:
let docRef = db.collection('voting').doc('user1');
let removeField = docRef.set({'votes':
{['id_vote_1'] : FieldValue.delete()}}, {merge: true});
Here is the document after running the above:

Memory leak using Firebase

I execute an API call in Firebase for retrieving the user profile information and storing it in a ViewController member variable.
The API is declared as a static function inside a class MyApi:
// Get User Profile
static func getUserProfile(byID userId:String,response:#escaping (_ result:[User]?,_ error:Error?)->()) {
// check ID is valid
guard userId.length > 0 else {
print("Error retrieving Creator data: invalid user id provided")
response(nil,ApiErrors.invalidParameters)
return
}
// retrieve profile
let profilesNode = Database.database().reference().child(MyAPI.profilesNodeKey)
profilesNode.child(userId).observe(.value, with: { (snapshot) in
// check if a valid data structure is returned
guard var dictionary = snapshot.value as? [String:AnyObject] else {
print("Get User Profile API: cannot find request")
response([],nil)
return
}
// data mapping
dictionary["key"] = userId as AnyObject
guard let user = User(data:dictionary) else {
print("Get User Profile API: error mapping User profile data")
response(nil,ApiErrors.mappingError)
return
}
response([user], nil)
}) { (error) in
response(nil,ApiErrors.FirebaseError(description: error.localizedDescription))
}
}
and I call it like that:
MyAPI.getUserProfile(byID: creatorId) { (profiles, error) in
guard let profiles = profiles, profiles.count > 0 else {
Utility.showErrorBanner(message: "Error retrieving Creator profile")
print("Error retrieving creator profile ID:[\(creatorId)] \(String(describing: error?.localizedDescription))")
return
}
self.currentProfile = profiles.first!
}
The ViewController is called in Modal mode so it should be deallocated every time I exit the screen.
Problem: a huge chunk of memory get allocated when I enter the screen, but it doesn't get freed up when I leave it. I'm sure about this because the problem doesn't appear if I remove the line self.currentProfile = profiles.first! (obviously)
How can I avoid this from happening?
NOTE: currentProfile is of type User, which was used to be a struct. I made it a class so I could use a weak reference for storing the information:
weak var currentCreator: User? {
didSet {
updateView()
}
}
but the problem still persists.
You are adding an observer:
profilesNode.child(userId).observe(...)
But you never remove it. As long as that observe is still added, it will hold on to memory from the entire set of results, and continually retrieve new updates. It's a really bad practice not to remove your observers.
If you want to read data just a single time, there is a different API for that using observeSingleEvent.

why I can't get all documents stored in firestore sub collection?

I have users collection that has sub collection called attendedEvents like the picture below. as you can see there are 3 documents in the attendedEvents sub collection
I try to get all documents available on that sub collection by using the code below, I just want to get it all, without order, limit or anything using getDocuments
func getAttendedEventsFromBeginning(completion: #escaping (_ eventID: [String]?,QueryDocumentSnapshot?)->Void) {
FirestoreDocumentReference.users(uidUser: uid).reference().collection("attendedEvents")
.getDocuments { (snapshot, error) in
let lastDocument = snapshot?.documents.last
if let error = error {
completion(nil,lastDocument)
print("Error when fetching attended events documents in user subcollection: \(error.localizedDescription)")
} else {
print("Successfully fetching attended events documents in user subcollection from Firestore ")
guard let documentsSnapshot = snapshot else {
completion(nil,lastDocument)
return
}
let eventDocuments = documentsSnapshot.documents
print("xxxxx")
print(eventDocuments)
var attendedeventIDs = [String]()
for document in eventDocuments {
let eventDictionary = document.data()
let theEvent = eventDictionary["eventID"] as! String
attendedeventIDs.append(theEvent)
}
print(attendedeventIDs)
completion(attendedeventIDs,lastDocument)
}
}
}
but as a result, I just got 2 document snapshot, it should be 3 documents
but if I delete the app from simulator and install it again, I got all the three data. is is cached or what?
I have been in the same situation. In my case, this happens because in my document it only contains subcollection. And that will cause the document itself to not be shown in queries or snapshots.
My walkaround method is to add some random info in the document to make it exist.

how to make getting data faster from Firebase Firestore?

I am new in programming and in iOS development. I am trying to make an app using Firestore database from Firebase. I don't know if it is normal or not, but when I am trying to get a data from firestore database, it seems too long for me. I don't know if I make a mistake or not
here is my code to get all city data from firestore
reference :
import Foundation
import FirebaseFirestore
import Firebase
enum FirestoreCollectionReference {
case users
case events
case cities
private var path : String {
switch self {
case .users : return "users"
case .events : return "events"
case .cities : return "cities"
}
}
func reference () -> CollectionReference {
return Firestore.firestore().collection(path)
}
}
I use getAllCitiesDataFromFirestore method in CityKM class to get the city data that stored in firestore
class CityKM {
var name : String
var coordinate : GeoPoint
init (name: String , coordinate: GeoPoint ) {
self.name = name
self.coordinate = coordinate
}
init (dictionary: [String:Any]) {
// this init will be used if we get data from firebase observation to construct an event object
name = dictionary["name"] as! String
coordinate = dictionary["coordinate"] as! GeoPoint
}
static func getAllCitiesDataFromFirestore (completion: #escaping ( [CityKM]? )->Void) {
// to retrieve all cities data from Firebase database by one read only, not using realtime fetching listener
let startTime = CFAbsoluteTimeGetCurrent() // to track time consumption of this method
FirestoreCollectionReference.cities.reference().getDocuments { (snapshot, error) in
if let error = error {
print("Failed to retrieve all cities data: \(error.localizedDescription)")
} else {
print("Sucessfully get all cities data from firestore")
guard let documentsSnapshot = snapshot, !documentsSnapshot.isEmpty else {
completion(nil)
return
}
let citiesDocuments = documentsSnapshot.documents
var cityArray = [CityKM]()
for document in citiesDocuments {
guard let cityName = document.data()["name"] as? String,
let cityCoordinate = document.data()["coordinate"] as? GeoPoint else {return}
let theCity = CityKM(name: cityName, coordinate: cityCoordinate)
cityArray.append(theCity)
}
completion(cityArray)
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime // to track time consumption of this method
print("Time needed to get all cities data from Firestore : \(timeElapsed) s.") // to track time consumption of this method
}
}
}
}
extension CityKM {
// MARK: - User Helper Methods
func toDictionary() -> [String:Any]{
return [
"name" : name,
"coordinate" : coordinate
]
}
}
from my debugging area, it is printed
"Time needed to get all cities data from Firestore : 1.8787678903 s."
is it possible to make it faster? Is 1.8s normal? am i make a mistake in my code that make the request data takes too long time ? I hope that I can make request time is below one second
I don't think the internet speed is the problem, since I can open video on youtube without buffering
That performance sounds a bit worse than what I see, but nothing excessive. Loading data from the cloud simply takes time. A quick approach to hide that latency is by making use of Firebase's built-in caching.
When you call getDocuments, the Firebase client needs to check on the server what the document's value is before it can call your code, which then shows the value to the user. As said: there is no way to speed up this reading in your code, so it'll always take at least 1.8s before the user sees a document.
If instead you listen for realtime updates from the database with addSnapshotListener, the Firebase client may be able to immediately call your code with values from its local cache, and then later re-invoke your code in case there has been an update to the data on the server.

Accessing Specific key on Firebase (Swift)

I am trying to update child values within firebase.
User first will create a new order, it creates two nodes, one in the main orders section and second under user to keep clean records. this seem to have worked but I am struggling to update values
then while he is on the form and makes updates, I want firebase to update simultaneously on firebase. How do I access to that specific key as nothing seem to have worked for me when I tried using observe method.
What will be the best way to access that key that the form is on and update values?
This is how you can update values in Firebase:
func updateDatabaseForEdits() {
let updates : [AnyHashable: Any] = ["variableName": value,
"variableName2": value]
ref.child("COrders").child("specificKeyYouWantToEdit").updateChildValues(updates, withCompletionBlock: { (error, success) in
if error != nil {
// upload failed
return
}
else {
// upload worked
// update your locally stored values
}
})
}
There are other issues with you app though. Specifically, how you're storing the data. How do you expect to know which key to access? I recommend you update your data store to be something like this:
desikhanapeena {
COrder {
key123 {
orderInformation
}
}
UserOrders {
uid {
key123
orderInformation
}
}
}
Let me know if you have questions:
If you want to get the key from a snapshot you can do that like this:
for child in snap.children {
let child = child as? DataSnapshot
if let key = child?.key {
// print(key)
}
}
If you want to get the key before you upload you can do that like this:
let key = ref.child("COrders").childByAutoId().key
in case if you are still looking for answer.
Answer for Prob #1.
you need to update order value for user, if it successfull then take parent key (in your case , its AutoID) from there , and using this key, update in "Corders".
here is the code
func updateBothValue(){
let value = ["key":"data","key1": "data2"]
REF.child("Users").child(UID).child("UserCorder").childByAutoId().setValue(value){
error, ref in
guard error == nil else { return }
let key = ref.key
REF.child("Coorders").child(key).setvalue(value)
}
}
for Prob #2, as you written "access that key that the form is on". for each form data , store the key which you get from firebase in previous steps. and use this key for updates.

Resources