Linh Kästner
Linh Kästner

Reputation: 41

How to make asynchronous background task finish in Swift for Firebase file upload

I have implemented background tasks in Swift and everything works fine, the function is executed when I run the app in background on a real device. When I want to upload an image to firebase storage using putData in the background task of type BGAppRefreshTask, the completion handler of putData is only called when the app comes back to the foreground and is not executed in the background task.

How can I ensure that the upload task is executed during the background task? Thank you all for your help!!

Here is where I register the task

//register for background tasks
    BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.foodnotifs.checknewfotos", using: nil) {(task) in
        self.handleAppRefreshTask(task: task as! BGAppRefreshTask)
        print("task registered")
        //
    }

Here is where I submit the task

let checkFotosTask = BGAppRefreshTaskRequest(identifier: "com.foodnotifs.checknewfotos")
checkFotosTask.earliestBeginDate = Date(timeIntervalSinceNow: 10*60)//every 10 min
do{
    try BGTaskScheduler.shared.submit(checkFotosTask)
} catch{
    print("unable to submit task: \(error.localizedDescription)")
}

Now here is the actual background task that gets executed:

func handleAppRefreshTask(task: BGAppRefreshTask)
    {

        let df = DateFormatter()
        df.dateFormat = "yyyy-MM-dd hh:mm:ss"
        let now:String = df.string(from: Date())
        let user = Auth.auth().currentUser
        if(user == nil){
            task.setTaskCompleted(success: false)} //if no user for some reason, end task
        let uid = user!.uid
        let databaseRef = Database.database().reference()
        databaseRef.child("users/" + uid + "/BACKGROUNDFETCHEDIMAGES/" + now).setValue(numberOfNewImages)
        print("number of new images written to database")

        let newAsset = fetchResults.object(at: 0)
        let size = CGSize(width: newAsset.pixelWidth/3, height: newAsset.pixelHeight/3)

        PHImageManager.default().requestImage(for: newAsset, targetSize: (size), contentMode: .aspectFill, options: nil, resultHandler: { image, _ in
            print("image retrieved successfully from phmanager")
            let storageRef = Storage.storage().reference(forURL: "gs://foodnotifs.appspot.com")
            let storageProfileRef = storageRef.child(uid).child(now)
                //create some self defined metadata
                let metadata = StorageMetadata()

                metadata.contentType = "image/jpg"
                metadata.customMetadata = ["uid": uid, "date":now, "image_name":now]

                //now upload the image to storage and update database entry
            //let uploadTask = storageProfileRef.putData(image!.jpegData(compressionQuality: 0.4)!, metadata: metadata)

           //CODE EXECUTES UP TO HERE, completion handler of putData is not executed
                storageProfileRef.putData(image!.jpegData(compressionQuality: 0.4)!, metadata: metadata) { (uploadedMetadata, error) in
                        if error != nil{
                            print("getting error uploading to storage")
                            print(error!.localizedDescription)
                            return
                        }
                        else{
                            print("upload to storage successful")
                            var DLurl:String = ""
                            storageProfileRef.downloadURL { (url,error) in
                                guard let DLurl = url?.absoluteString else{
                                    print("error getting download url for uploaded image")
                                    print(error!.localizedDescription)
                                    return
                                }
                                print("download url is: " + DLurl)

                            //successful upload, now update the database of this user with the image url
                                databaseRef.child("users").child(uid).child("IMAGE_URLs").child(now).setValue(DLurl){ (error, ref) in
                                if error == nil {
                                    //on sucess, also save url to most recent
                                        print("uploaded to database")
                                    databaseRef.child("users").child(user!.uid).child("IMAGE_URLs").child("MOST_RECENT").setValue(DLurl)



                                        }
                                        else{
                                        print("error in uploading to database")
                                        print(error!.localizedDescription)
                                        }
                                }//database updated succssfully
                            } //download url gotten successfully
                } //succesful upload
            }//storage profile ref putdata completion
        })



    }
    else{
        print("no camera roll found")
    }



   //CODE CONTINUES HERE AND REACHES THE END OF THE FUNCTION WITHOUT UPLOADING THE IMAGE
   self.appWillBecomeInactive() //schedule again
    print("background task finished")
    task.setTaskCompleted(success: true) //task completed

}//end function

Upvotes: 1

Views: 927

Answers (1)

Linh Kästner
Linh Kästner

Reputation: 41

just in case anyone will have the same problem, I finally solved it using closures. Here is the code that is working.

func uploadImage(completion:@escaping((String?) ->() )) {let df = DateFormatter()
        df.dateFormat = "yyyy-MM-dd hh:mm:ss"
        let now:String = df.string(from: Date())
        let user = Auth.auth().currentUser
        if(user == nil){
            completion(nil)} //if no user for some reason, end task
        let uid = user!.uid
        let databaseRef = Database.database().reference()
        databaseRef.child("users/" + uid + "/BACKGROUNDFETCHEDIMAGES/" + now).setValue(numberOfNewImages)
        print("number of new images written to database")


            let storageProfileRef = storageRef.child(uid).child(now)
                //create some self defined metadata
                let metadata = StorageMetadata()



                //now upload the image to storage and update database entry
            //let uploadTask = storageProfileRef.putData(image!.jpegData(compressionQuality: 0.4)!, metadata: metadata)

                storageProfileRef.putData(image!.jpegData(compressionQuality: 0.4)!, metadata: metadata) { (uploadedMetadata, error) in
                        if error != nil{
                            print("getting error uploading to storage")
                            print(error!.localizedDescription)
                            return
                        }
                        else{
                            print("upload to storage successful")
                            var DLurl:String = ""
                            storageProfileRef.downloadURL { (url,error) in
                                guard let DLurl = url?.absoluteString else{
                                    print("error getting download url for uploaded image")
                                    print(error!.localizedDescription)
                                    return
                                }
                                print("download url is: " + DLurl)


                                databaseRef.child("users").child(uid).child("IMAGE_URLs").child(now).setValue(DLurl){ (error, ref) in
                                if error == nil {
                                    //on sucess, also save url to most recent
                                        print("uploaded to database")
                                    databaseRef.child("users").child(user!.uid).child("IMAGE_URLs").child("MOST_RECENT").setValue(DLurl)
                                         completion(DLurl)
                                        }
                                        else{
                                        print("error in uploading to database")
                                        print(error!.localizedDescription)
                                        }
                                }//database updated succssfully
                            } //download url gotten successfully
                } //succesful upload
            }//storage profile ref putdata completion
        })

}

inside the handler handleAppRefreshTasks, include upload image:

//refresh task has a max run time of 30 seconds, process task has more but called less feequently
func handleAppRefreshTask(task: BGAppRefreshTask)
{
    task.expirationHandler = {
        print("expiration time for background foto update reached")
        //do other handling when expiration time is reached
        task.setTaskCompleted(success: false)
        return
    }
    print("going into background task")
    //first check if we have internet connection, if not stop the task
    let monitor = NWPathMonitor()
    monitor.pathUpdateHandler = { path in
        if path.status == .satisfied {
            print("We're connected!")
        } else {
            print("No connection.")
            //we have no connection, finish the background task
            task.setTaskCompleted(success: true)
            return
        }
    }
    print("background task entered")


   uploadImage { (dlurl) in

        if (dlurl != nil){
            print("finished upload with download url:" + dlurl!)}
        else{
            print("dl url is nil, upload failed")
        }

        self.appWillBecomeInactive() //schedule again
       print("background task finished")
       task.setTaskCompleted(success: true) //task completed
    }

}//end function

Upvotes: 1

Related Questions