In my iOS application, I want to execute a piece of code after 5 seconds of receiving a silent notification from Firebase.
The tentative workflow is something like this:
A silent notification from Firebase arrives on the device => OK
Application extracts some data and displays a local notification => OK
After 5 seconds, I want to list the notifications delivered via UNUserNotificationCenter.current().getDeliveredNotifications => Not OK
In all the above steps, there's no user intervention and the app is always in background.
The attempted code is as follows:
//Receive the silent notification
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID 2: \(messageID)")
}
showLocalNotification(userInfo: userInfo, fetchCompletionHandler: completionHandler)
/* This was one of the attempts
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) {
completionHandler(UIBackgroundFetchResult.newData)
}
*/
}
//Show Local notification with the data received in silent notification
func showLocalNotification(userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("notificationTitle", comment: "")
content.body = NSLocalizedString("notificationBody", comment: "")
content.userInfo = userInfo
// Create the request
let uuidString = UUID().uuidString
let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: nil)
// Schedule the request with the system.
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.add(request) { (error) in
if error != nil {
// Handle any errors.
print(error!)
} else {
self.startTimer()
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) {
completionHandler(UIBackgroundFetchResult.newData)
}
//completionHandler(UIBackgroundFetchResult.newData)
/* This was another approach instead of using timer
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5)) {
self.deliveredNotifications()
}
*/
}
}
}
// Start timer to wait for 5 seconds
func startTimer() {
print("starting timer")
if timer == nil {
timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
timer!.schedule(deadline: .now() + .seconds(5))
timer!.setEventHandler {
print("in event handler")
self.deliveredNotifications()
self.timer = nil
}
timer!.resume()
}
print("timer started")
}
//List notifications
func deliveredNotifications() {
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.getDeliveredNotifications(completionHandler: { notifications -> () in
print("in dispatch")
print(notifications.count)
for request in notifications {
print(request.request.identifier)
}
})
}
In the above code, print("in event handler") never gets printed on the console and hence deliveredNotifications() does not execute. The other two print messages in startTimer() are printed successfully.
However, if I directly call deliveredNotifications() after displaying the local notification, it executes fine.
Also, if I launch the app or send another silent notification, the timer callback gets executed but that is not the desired behavior.
So, is there anything that I am missing or I can do to get this working?
I saw some similar questions, but could not achieve anything concrete:
Silent push only work properly when second push arrive (app in background)
How to clear a remote pushed notification for iOS?
Assuming your App support background task, can you try something like this:
func startTimer() {
print("starting timer")
if timer == nil {
let task = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
timer!.schedule(deadline: .now() + .seconds(5))
timer!.setEventHandler {
print("in event handler")
self.deliveredNotifications()
self.timer = nil
UIApplication.shared.endBackgroundTask(task)
}
timer!.resume()
}
print("timer started")
}
I think the main reason is that you are not starting background task, so as soon as your App enters background, execution stops.
NOTE: You probably need to a expirationHandler in case you exceed the allow background time, and do some error handling.
Related
I am currently trying to create notifications when a user has new messages. I'm trying to do this with local notifications because I'm very much a beginner and it seems(?) easier than push notifications. My question is, can I check my Firebase database during my background fetch?
What I've experienced is that the background fetch function works- but only before my app memory has been suspended, thus negating the point of the background fetch. I run it, I simulate a background fetch, but unless the app was just open, it does nothing and tells me "Warning: Application delegate received call to -application:performFetchWithCompletionHandler: but the completion handler was never called."
Here's my code if it's useful. I know it probably seems like a funky way to go about this.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
//Firebase
FirebaseApp.configure()
//there was other firebase stuff here that I don't think is relevant to this question
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (didAllow, error) in
}
UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
return true
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
myDatabase.child("users").child(userID!).child("hasNewMessages").observeSingleEvent(of: .value) { (snapshot) in
if snapshot.value as! Bool == true {
let content = UNMutableNotificationContent()
content.title = "You have unread messages"
content.badge = 1
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let request = UNNotificationRequest(identifier: "testing", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
}
}
It is probably better to look at using push notifications as then your user's don't have to wait until iOS decides to invoke your background fetch; they can be notified of new messages immediately.
However, your problem is as described by the message you see in the console; you need to invoke the completionHandler that was passed to the background fetch method when you have finished your background operation to let iOS know what happened. It uses this information to tune how often and when your background fetch method is called.
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
myDatabase.child("users").child(userID!).child("hasNewMessages").observeSingleEvent(of: .value) { (snapshot) in
if snapshot.value as! Bool == true {
let content = UNMutableNotificationContent()
content.title = "You have unread messages"
content.badge = 1
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let request = UNNotificationRequest(identifier: "testing", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
completionHandler(.newData)
}
}
I'm trying to implement Background Fetch API in my app for that I've configured as below.
I've enabled Background Fetch from Capabilities.
In AppDelegate.swift
Added this in didFinishLaunchingWithOptions method
UIApplication.shared.setMinimumBackgroundFetchInterval(30)
Implemented this method too to perform task.
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
debugPrint("New notification fired from AppDelegate...!!")
let notif = UNMutableNotificationContent()
notif.title = "New notification from App delegate"
notif.subtitle = "Cool App!"
notif.body = "I liked it!"
UNUserNotificationCenter.current().requestAuthorization(options: [.sound, .badge, .alert], completionHandler: { (isGranted, error) in
DispatchQueue.main.async {
let notifTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
let request = UNNotificationRequest(identifier: "myNotification", content: notif, trigger: notifTrigger)
UNUserNotificationCenter.current().add(request) { (error) in
if error != nil{
print(error!)
} else {
// do something
}
}
}
})
}
After configuring all the things local notification not firing. Why so?
Am I missing something?
I've also tried this tutorial
Any help will be appreciated!
You are not calling completionHandler in performFetchWithCompletionHandler. I am able to test BackgroundFetch with below code:
DispatchQueue.main.async {
let notifTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 6.0, repeats: false)
let request = UNNotificationRequest(identifier: "myNotification", content: notif, trigger: notifTrigger)
UNUserNotificationCenter.current().add(request) { (error) in
if error != nil{
print(error!)
completionHandler(.failed) // Add this line
} else {
completionHandler(.noData) // Add this line
}
}
}
You can test Background Fetch with below steps:
Run your application.
Goto Xcode toolbar.
Select Debug > Simulate Background Fetch.
Now you will able to Test Background Fetch.
There is similar question about this:
Background Fetch Does Not Appear to Fire
Try to force it to run on the simulator, If fetch event works in
simulator, that proves that everything is correctly set up. There's
nothing you can do but wait.
I have a problem with local notifications scheduling (I'm using all available slots - 64). Main problem that it took a lot of time, on slow devices (iPhone 5C) up to 20 seconds !
Here how I'm doing this:
let notificationCenter = UNUserNotificationCenter.current()
for notification in unNotifications { //64 notifications
notificationCenter.add(notification) { _ in
//do nothing here
}
}
I didn't find any bunch method to schedule all notifications with one method call. What could be wrong ?
Simply follow the steps you will get what you are missing.
Request Notification
// Request Notification Settings
UNUserNotificationCenter.current().getNotificationSettings { (notificationSettings) in
switch notificationSettings.authorizationStatus {
case .notDetermined:
self.requestAuthorization(completionHandler: { (success) in
guard success else { return }
// Schedule Local Notification
})
case .authorized:
// Schedule Local Notification
case .denied:
print("Application Not Allowed to Display Notifications")
}
}
Requesting Authorization
// MARK: - Private Methods
private func requestAuthorization(completionHandler: #escaping (_ success: Bool) -> ()) {
// Request Authorization
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (success, error) in
if let error = error {
print("Request Authorization Failed (\(error), \(error.localizedDescription))")
}
completionHandler(success)
}
}
Scheduling a Notification
private func scheduleLocalNotification() {
// Create Notification Content
let notificationContent = UNMutableNotificationContent()
// Configure Notification Content
notificationContent.title = "Cocoacasts"
notificationContent.subtitle = "Local Notifications"
notificationContent.body = "In this tutorial, you learn how to schedule local notifications with the User Notifications framework."
// Add Trigger
let notificationTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 10.0, repeats: false)
// Create Notification Request
let notificationRequest = UNNotificationRequest(identifier: "cocoacasts_local_notification", content: notificationContent, trigger: notificationTrigger)
// Add Request to User Notification Center
UNUserNotificationCenter.current().add(notificationRequest) { (error) in
if let error = error {
print("Unable to Add Notification Request (\(error), \(error.localizedDescription))")
}
}
}
Implementing the Delegate Protocol
// Configure User Notification Center
UNUserNotificationCenter.current().delegate = self
extension ViewController: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert])
}
}
While testing result is:
Reference: https://cocoacasts.com/local-notifications-with-the-user-notifications-framework
I was handling push notification data and then after call API based in push notification custom data. This will work fine when app is in Active and background state.
But when app is not running and then click on notification, I was able to get custom data from custom date But, API is not called and app getting stuck.
I checked in iOS 10 and 11, but not working
Handling push is like this.
AppDelegate
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
redirectToScreen(notificaiton: userInfo)
completionHandler(UIBackgroundFetchResult.newData)
}
//Notify through Notification center
func redirectToScreen(notificaiton: [AnyHashable: Any]) {
let dictPayload = notificaiton as NSDictionary
print(dictPayload)
if let type = dictPayload.value(forKey: "type") as? String {
var dict = ["type" : type]
NotificationCenter.default.post(name: NSNotification.Name.init(rawValue: "handlePush"), object: dict)
}
}
HomeViewController
//Notification Observer goes here and call API
let spinner = showLoader(view: self.view) // App goes stuck here and loaded process continuously, response is not getting
Alamofire.request(kURl, method: .post, parameters: param, encoding: URLEncoding.httpBody, headers: nil).authenticate(user: R.string.keys.basicAuthUsername(), password: R.string.keys.basicAuthPassword()).responseSwiftyJSON(completionHandler: {
spinner.dismissLoader()
})
Swift 4.0
As per #TarasChernyshenko statement, I put Post Notification code in DispatchQueue.main.async { } block and now it works fine.
DispatchQueue.main.async {
NotificationCenter.default.post(name: NSNotification.Name.init(rawValue: "handlePush"), object: dict)
}
As mentioned by #TarasChernyshenko,
When you get callback from Notification Observer via didRecieveNotification(_:), app remains in background. Any UI updates such as :
let spinner = showLoader(view: self.view)
Must keep in Main thread queue as given below :
DispatchQueue.main.async {
let spinner = showLoader(view: self.view)
//other ui stuffs...
}
I'm attempting to run a a simple iOS application that pushes a notification to a user's screen after a specified time.
So far, this is what I have (borrowed from another thread):
DispatchQueue.global(qos: .background).async {
print( "background task" )
DispatchQueue.main.asyncAfter( deadline: .now() + milliseconds( 2000 )) {
let content = UNMutableNotificationContent()
content.body = "Testing :)"
content.badge = 1
let trigger = UNTimeIntervalNotificationTrigger( timeInterval: 2, repeats: false )
let request = UNNotificationRequest( identifier: "test", content: content, trigger: trigger )
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
print( "background finish" )
}
}
My only issue is that the aSync After doesn't run whenever the app is in the background.
For example, if a user goes into their lockscreen or a different app, the notification never gets triggered.
Would anyone have a suggestion for how I could achieve this?
Thank you! :)
Approach:
Use UNNotificationRequest with time interval
Below mentioned solution would work in the following scenarios:
Foreground
Background
App is closed
Steps:
Set the delegate (to be alerted in foreground)
Request authorisation from user to be alerted
Create the notification
Add it to the notification center
AppDelegate:
AppDelegate must conform to UNUserNotificationCenterDelegate.
Set the notification center's delegate to the AppDelegate
import UserNotifications
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
UNUserNotificationCenter.current().delegate = self
return true
}
//MARK: UNUserNotificationCenterDelegate
//This is required to be alerted when app is in foreground
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
print("will present")
completionHandler([.alert, .badge, .sound])
}
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
print("did receive")
}
}
Setting up notification:
import UserNotifications
private func setupNotification() {
requestAuthorization { [weak self] isGranted, error in
if let error = error {
print("Request Authorization Error: \(error)")
return
}
guard isGranted else {
print("Authorization Denied")
return
}
self?.addNotification()
}
}
private func requestAuthorization(completionBlock: #escaping (Bool, Error?) -> ()) {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .badge, .sound]) { isGranted, error in
completionBlock(isGranted, error)
}
}
private func addNotification() {
let content = UNMutableNotificationContent()
content.title = "Testing Notification"
content.body = "This is a test for notifications"
content.sound = .default()
let timeInterval = TimeInterval(5)
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: false)
let request = UNNotificationRequest(identifier: "Something",
content: content,
trigger: trigger)
let center = UNUserNotificationCenter.current()
center.add(request) { error in
if let error = error {
print("Error adding notification request: \(error)")
}
else {
print("Successfully added notification request")
}
}
}