I'm using Firebase Messaging (Notifications) to send push reminders to users on iOS. For my app, that is a todo app, I'm using Swift 3. When the user gets the push notification I want them to be able to complete the task right from the push notification.
Everything works almost great. The user gets the push. When they 3d-touch they see the "complete button". When the "complete button" is tapped the didReceive response method in the app is triggered in the background.
Now to the problem, in that method I'm using a closure and then a closure in that closure. For some reason the first part of the code runs in the background without the user opening the app but the last part is only running when the user opens the app again (see below). Why is that?
This is my code:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if response.actionIdentifier == notificationActionComplete, let actionKey = userInfo["actionKey"] as? String {
getAction(actionKey: actionKey, completion: { (action) in
action.complete {
}
})
}
completionHandler()
}
func getAction(actionKey: String, completion:@escaping (Action)->Void) {
Database.database().reference(withPath: "actions/\(actionKey)").observeSingleEvent(of: .value, with: { snapshot in
let action = Action(snapshot: snapshot)
completion(action)
})
}
In action class:
var ref: DatabaseReference?
init(snapshot: DataSnapshot) {
key = snapshot.key
ref = snapshot.ref
//Other inits here
}
func complete(completion:@escaping (Void) -> Void) {
//This code to remove the node is running fine in background
ref.removeValue { (error, ref) in
//The code in here is not running until the user opens the app next time
otherRef.updateChildValues(self.toAnyObject(), withCompletionBlock: { (error, ref) in
completion()
})
}
Your app is basically suspended after the runloop cycle where userNotificationCenter() is called, so if your completion handler is in response to asynchronous work, that work will never happen until your app resumes again. To get around this you will probably need to begin a background task inside that function, and then have your completion handler end the background task when it is finished. This tells the system you need to stay alive for a while in the background (although it is not guaranteed, if you take too long)
See "Executing Finite Limit Tasks" at this URL (sorry, it's Obj-C, but there should be a Swift way to do it too):