My iOS application can receive push notifications with action buttons:
Button tap sends post http request via UrlSession and data task. It works well when application is suspended or not running. But it doesn't work when app is in background e.g. when user just now swiped up from bottom (or pressed home button). Push action handles but http request not sends.
From console os_logs of my iPhone I see that UrlSession can't establish connection from background and throwing an error Code=-1005 "The network connection was lost.". It can as soon as I kill the app. I tried different url session configurations, background url session, background tasks but nothing works. The only workaround I found is to use exit(0)
from applicationDidEnterBackground
method, but it's highly not recommended to kill app programmatically.
Question: how can I establish http connection from push notification action when app is in background?
My code:
class NotificationService: NSObject, UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
// notification processing if app is in foreground
...
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
let requestId = String(describing: response.notification.request.content.userInfo["requestId"] ?? "")
switch response.actionIdentifier {
case "CONFIRM_ACTION":
confirmAuthenticationRequest(requestId: requestId, isConfirmed: true)
case "REJECT_ACTION":
confirmAuthenticationRequest(requestId: requestId, isConfirmed: false)
default:
// on notification tap
...
}
completionHandler()
}
private func confirmAuthenticationRequest(requestId: String, isConfirmed: Bool) {
var request = URLRequest(url: url)
request.httpMethod = "POST"
let json: [String: Any] = [
"requestId": requestId,
"deny": !isConfirmed
]
request.httpBody = try? JSONSerialization.data(withJSONObject: json)
request.addValue("application/json", forHTTPHeaderField: "content-type")
let dataTask = urlSession.dataTask(with: request)
dataTask.resume()
}
}
Finally I found an answer.
Reason of unstable push notification actions work is wrong way to call completionHandler
from userNotificationCenterDidReceiveResponse
method. When perform asynchronous operation like http request you have to call completionHandler
right after operation completes (not after operation's call) and from the main thread. Here is working example:
class NotificationService: NSObject, UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
let requestId = String(describing: response.notification.request.content.userInfo["requestId"] ?? "")
switch response.actionIdentifier {
case "CONFIRM_ACTION":
confirmAuthenticationRequest(requestId: requestId, isConfirmed: true, completion: completionHandler)
case "REJECT_ACTION":
confirmAuthenticationRequest(requestId: requestId, isConfirmed: false, completion: completionHandler)
default:
// on notification tap
...
completionHandler()
}
}
private func confirmAuthenticationRequest(requestId: String, isConfirmed: Bool, completion: @escaping () -> Void) {
var request = URLRequest(url: url)
request.httpMethod = "POST"
let json: [String: Any] = [
"requestId": requestId,
"deny": !isConfirmed
]
request.httpBody = try? JSONSerialization.data(withJSONObject: json)
request.addValue("application/json", forHTTPHeaderField: "content-type")
let dataTask = urlSession.dataTask(with: request) { _, _, _ in
DispatchQueue.main.async {
completion()
}
}
dataTask.resume()
}
}