iosswiftgoogle-cloud-firestoreios-background-mode

iOS applicationWillResignActive task not performed until app becomes active again


I'm trying to use applicationWillResignActive() in order to sync some data to my Firestore database before the application enters the background.

func applicationWillResignActive(_ application: UIApplication) {
        self.uploadWantToPlay()
}

When I call my upload function from applicationWillResignActive() it runs but no data is added to Firestore before the next time the application becomes active.

When I for testing purposes instead run the same function from one of my ViewControllers the data is added instantly to Firestore.

I've also tried calling the function from applicationDidEnterBackground(), I've tried running it in it's own DispatchQueue. But it's had the same result.

How can I run this function as the user is about to leave the app and have it perform the database sync properly?

The functions handling the database sync;

func uploadWantToPlay() {
    print ("Inside uploadWantToPlay")
    if let wantToPlay = User.active.wantToPlayList {
        if let listEntries = wantToPlay.list_entries {
            let cleanedEntries = listEntries.compactMap({ (entry: ListEntry) -> ListEntry? in
                if entry.game?.first_release_date != nil {
                    return entry
                } else {
                    return nil
                }
            })
            let gamesToUpload = cleanedEntries.filter {
                $0.game!.first_release_date! > Int64(NSDate().timeIntervalSince1970 * 1000)
            }
            DatabaseConnection().writeWantToPlayToDatabase(user: User.active,wantToPlay: gamesToUpload)
        }
    }
}

func writeWantToPlayToDatabase(user: User, wantToPlay: [ListEntry]) {
    firebaseSignIn()
    let deviceId = ["\(user.deviceId)": "Device ID"]
    for entry in wantToPlay {
        let wantToPlayGameRef = fireStore.collection(WANTTOPLAY).document("\(entry.game!.id!)")

        wantToPlayGameRef.updateData(deviceId) {(err) in
            if err != nil {
                wantToPlayGameRef.setData(deviceId) {(err) in
                    if let err = err {
                        Events().logException(withError: err, withMsg: "DatabaseConnection-writeWantToPlayToDatabase(user, [ListEntry]) Failed to write to database")
                    } else {
                        print("Document successfully written to WantToPlayGames")
                    }
                }
            } else {
                print("Document successfully updated in WantToPlayGames")
            }
        }
    }
}

Solution

  • According to the Apple documentation

    Apps moving to the background are expected to put themselves into a quiescent state as quickly as possible so that they can be suspended by the system. If your app is in the middle of a task and needs a little extra time to complete that task, it can call the beginBackgroundTaskWithName:expirationHandler: or beginBackgroundTaskWithExpirationHandler: method of the UIApplication object to request some additional execution time. Calling either of these methods delays the suspension of your app temporarily, giving it a little extra time to finish its work. Upon completion of that work, your app must call the endBackgroundTask: method to let the system know that it is finished and can be suspended.

    So, what you need to do here is to perform a finite length task while your app is being suspended. This will buy your app enough time to sync your records to the server.

    An example snippet:

    import UIKit
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
    
        var window: UIWindow?
        var backgroundTask: UIBackgroundTaskIdentifier!
    
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
            // Override point for customization after application launch.
            return true
        }
    
        func applicationWillResignActive(_ application: UIApplication) {
            self.registerBackgroundTask()
    
            // Do your background work here
            print("Do your background work here")
    
            // end the task when work is completed
            self.endBackgroundTask()
        }
    
        func registerBackgroundTask() {
            self.backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
                self?.endBackgroundTask()
            }
            assert(self.backgroundTask != UIBackgroundTaskInvalid)
        }
    
        func endBackgroundTask() {
            print("Background task ended.")
            UIApplication.shared.endBackgroundTask(backgroundTask)
            backgroundTask = UIBackgroundTaskInvalid
        }
    
    }
    

    For further information refer to this article.