1

I have code that needs to be executed as soon as Firebase finishes downloading what I tasked it to download before hand. The issue is that this code is always running before the download is complete.

if currentVersionNumber < newVersionNumber {
            print("Feed: button donwloading cards")
            //self.databaseButton.setTitle("Downloading New Cards...", for: .normal)
            ref.observe(.value, with: { snapshot in
                print("Feed: Checking for new cards from firebase")
                for item in snapshot.children {
                    // Download in memory with a maximum allowed size of 1MB (1 * 1024 * 1024 bytes)
                    cardRef.data(withMaxSize: 1 * 1024 * 1024) { (data, error) -> Void in
                        if (error != nil) {
                            // Uh-oh, an error occurred!
                            print("Feed: error occured")
                            print(error)
                        } else {
                            // Data for "images/island.jpg" is returned
                            cards.imageName = data!
                            print("Feed: downloaded \(cards.name)")
                        }
                    }
                    // add to updated list of cards

                    updateCards.append(cards);
                }
            })

        } else {
             print("Feed: cards are up to date. \(currentVersionNumber)")
        }

    })

This code downloads the items I want from Firebase Database, but will run any code after it, before it is complete. How do I make it so that I can choose to execute a code block as soon as the download finishes?

AL.
  • 36,815
  • 10
  • 142
  • 281
Trevor Jordy
  • 598
  • 1
  • 7
  • 27

3 Answers3

3

This happens because all the updates from Firebase are happening in a background thread and your code is executed on the main thread. To deal with this call your firebase method inside a function that has a closure that gets called as soon as your firebase download is completed.

For example:

In your viewDidLoad:

override func viewDidLoad() {

   super.viewDidLoad()

   fetchData {

       //do whatever action you wish to perform on download completion
       mainTableView.reloadData()
   }
}

func fetchData(andOnCompletion completion:@escaping ()->()){

   ref.observe(.value, with: { snapshot in
            print("Feed: Checking for new cards from firebase")
            for item in snapshot.children {
                // Download in memory with a maximum allowed size of 1MB (1 * 1024 * 1024 bytes)
                cardRef.data(withMaxSize: 1 * 1024 * 1024) { (data, error) -> Void in
                    if (error != nil) {
                        // Uh-oh, an error occurred!
                        print("Feed: error occured")
                        print(error)
                    } else {
                        // Data for "images/island.jpg" is returned
                        cards.imageName = data!
                        print("Feed: downloaded \(cards.name)")
                    }
                }
                // add to updated list of cards

                updateCards.append(cards);
            }
         //call the block when done processing          
     completion()
  })
}
Rikh
  • 4,078
  • 3
  • 15
  • 35
  • I saw an answer was added but I didn't realize how close they would be. If I didn't spend some time on it I would remove it but I guess 2 examples are better than one. Just a heads up, you didn't move updateCards.append to the inner closure. – JustinM Dec 20 '16 at 11:26
  • this doesn't seem to work for me, as it calls the completion before finishing the download – Trevor Jordy Dec 21 '16 at 05:14
2

these network requests run async so any code after them will continue to run while the network request is being completed.

you should move updateCards.append(cards) inside the inner closure so it doesn't get called until that second closure finishes, then if you have other code you need to run when this is complete you can either move it inside of this function or use a closure with a completion handler to make sure all network requests are completed before you run any more code the relies on the response.

getCardData { [weak self] in
// do whatever you need to do after completion
}

func getCardData(_ completion: () -> ()) {
print("Feed: button donwloading cards")
//self.databaseButton.setTitle("Downloading New Cards...", for: .normal)
ref.observe(.value, with: { snapshot in
    print("Feed: Checking for new cards from firebase")
    for item in snapshot.children {
        // Download in memory with a maximum allowed size of 1MB (1 * 1024 * 1024 bytes)
        cardRef.data(withMaxSize: 1 * 1024 * 1024) { (data, error) -> Void in
            if (error != nil) {
                // Uh-oh, an error occurred!
                print("Feed: error occured")
                print(error)
                completion() // this is where you would normally throw an error or have a closure that accepts an optional error you would pass in to know it failed
            } else {
                // Data for "images/island.jpg" is returned
                cards.imageName = data!
                print("Feed: downloaded \(cards.name)")
                updateCards.append(cards);
                completion()// now you know all network requests are complete
            }
        }
     }
  })
}
JustinM
  • 2,202
  • 2
  • 14
  • 24
1

Managed to fix my problem by adding an if statement inside the download that checks to see if the number of cards appended to updateCards is equal to the number of cards that was in snapshot. Thank you to the two who answered this question, as I also used the completion() method and am glad that I go to learn about this concept that I didn't know existed.

Trevor Jordy
  • 598
  • 1
  • 7
  • 27