1

I have the following code and I'm trying to save data to firebase. Saving is very random. Sometimes only the first .setValue is posted to firebase. Sometime the first two and sometimes the first three. It never seems to do more than three. This only started happening when I added the .putData method to save images. If I comment out the "save photo" section it works perfect. Any ideas? BTW, I just added the dispatchqueue to see if it would solve the problem; it didn't.

private func saveEvents() {

    // Createe a database reference
    databaseRef = Database.database().reference()

    for event in events {
        DispatchQueue.global(qos: .userInitiated).async { [weak self] in
            guard let self = self else {
                return
            }
            let fireKey = event.eventName.trimmingCharacters(in: .whitespaces) + " " + event.eventCity.trimmingCharacters(in: .whitespaces)

            self.databaseRef?.child("Event").child(fireKey)
            self.databaseRef?.child("Event").child(fireKey).child("eventCategory").setValue(event.eventCategory)
            self.databaseRef?.child("Event").child(fireKey).child("eventCity").setValue(event.eventCity)
            self.databaseRef?.child("Event").child(fireKey).child("eventContact").setValue(event.eventContact)
            self.databaseRef?.child("Event").child(fireKey).child("eventCreator").setValue(event.eventCreator)
            self.databaseRef?.child("Event").child(fireKey).child("frequency").setValue(event.frequency)
            self.databaseRef?.child("Event").child(fireKey).child("eventDate").setValue(event.eventDate)
            self.databaseRef?.child("Event").child(fireKey).child("eventToDate").setValue(event.eventToDate)
            self.databaseRef?.child("Event").child(fireKey).child("eventEmail").setValue(event.eventEmail)
            self.databaseRef?.child("Event").child(fireKey).child("eventWebSite").setValue(event.eventWebSite)
            self.databaseRef?.child("Event").child(fireKey).child("eventName").setValue(event.eventName)
            self.databaseRef?.child("Event").child(fireKey).child("eventPhone").setValue(event.eventPhone)
            self.databaseRef?.child("Event").child(fireKey).child("eventState").setValue(event.eventState)
            self.databaseRef?.child("Event").child(fireKey).child("eventCountry").setValue(event.eventCountry)
            self.databaseRef?.child("Event").child(fireKey).child("eventStreet").setValue(event.eventStreet)
            for review in event.eventReviews {
                let commentKey = event.eventName + " " + review.vendorName
                self.databaseRef?.child("Event").child(fireKey).child("Reviews")
                self.databaseRef?.child("Event").child(fireKey).child("Reviews").child(commentKey)
                self.databaseRef?.child("Event").child(fireKey).child("Reviews").child(commentKey).child("Comment").setValue(review.vendorComment)
                self.databaseRef?.child("Event").child(fireKey).child("Reviews").child(commentKey).child("Rating").setValue(review.vendorRating)
                self.databaseRef?.child("Event").child(fireKey).child("Reviews").child(commentKey).child("Reviewer").setValue(review.vendorName)
            }

            //check if there is only a Default image before storing in firebase
            if event.eventImage != UIImage(named: "UploadPhoto") {
                DispatchQueue.main.async { [weak self] in
                    let imageRef = self!.databaseRef.child("Event").child(fireKey).child("images").childByAutoId()
                    let imageStorageKey = imageRef.key
                    if let imageData = UIImageJPEGRepresentation(event.eventImage, 0.6) {
                        let imageStorageRef = self!.storageRef.child("images").child(imageStorageKey!)
                        let uploadTask = imageStorageRef.putData(imageData, metadata: nil) { (metadata, error) in
                            guard let metadata = metadata else {
                                // Uh-oh, an error occurred!
                                return
                            }
                            // Metadata contains file metadata such as size, content-type
                            let size = metadata.size

                            // You can also access to download URL after upload.
                            imageStorageRef.downloadURL(completion: { (url, error) in
                                guard let downloadURL = url else {
                                    // Uh-oh, an error occurred!
                                    return
                                }
                                self!.imageDownloadURL = downloadURL.absoluteString
                                imageRef.child("imageDownloadURL").setValue(self!.imageDownloadURL)
                            })
                        }
                    }
                }
            }
        }
    }
    saveGeoFire()
}
KENdi
  • 7,576
  • 2
  • 16
  • 31

1 Answers1

0

You can create a database reference variable of children in your database, so you won't have to keep typing .child all of the time.

I would suggest trying to save all of the values at once using a Swift Dictionary like so:

// in your loop where you start writing to Firebase   
if let databaseRef = databaseRef?.child("Event").child(fireKey) {
    let objectToSave = [
        "eventCategory": event.eventCatagory,
        "eventCity": event.eventCity,
        "eventContact": event.eventContact,
        // and so on...
        "eventStreet": event.eventStreet

    ] as [String:Any]
    databaseRef.setValue(objectToSave) { error, ref in
         if error != nil {
              // error handle
         } else {
              // then upload your photo since the write was successful
         } 
    }
}

Secondly, since you are running asynchronous code (writing to Firebase) within a loop, you will need to handle this appropriately since the code will execute before the database call does.

I would suggest looking into using a DispatchQueue. Here are some relevant StackOverflow posts that can teach you how to implement this into your asynchronous functions:

Wait until swift for loop with asynchronous network requests finishes executing

  • Thanks for the answer! it was spot on and solved the problem. But I'm still curious as to why if I'm trying to 10 single lines + a photo to firebase, it only adds up to the first 3 lines but with no photo it adds all 10 lines. I stepped through the program with the debugger and all lines were executed as expected, but only select lines were added to firebase. – Theo Drackett Nov 29 '18 at 18:55
  • No problem! Stepping through with the debugger won't be much help with asynchronous code since the line of code finishing doesn't necessarily mean that the `.setValue` operation has been completed in Firebase. If you don't properly use completion handlers and error checking to handle multiple asynchronous operations, then unexpected results can occur such as write operations not being completed. – hackerman58888 Nov 29 '18 at 19:49