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.
Related
I recently just finished an app that basically has an Observer for a Firebase Database, and when a certain attribute changes, sends a banner type notification.
The application works completely, but when I had the app in the background while using the phone, I realized that it doesn't run any code, or send notifications.
How can I implement both having the Observer listening for any changes from the Database in the background, as well as send a notification?
Here is the Notification Authorization Request function:
func requestNotificationAuthorization() {
let authOptions = UNAuthorizationOptions.init(arrayLiteral: .alert, .badge, .sound)
self.userNotificationCenter.requestAuthorization(options: authOptions) { (success, error) in
if let error = error {
print("Error: ", error)
}}}
The Notification Center function:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
completionHandler()
}
And the Send Notification function:
func sendNotification() {
// Create new notifcation content instance
let notificationContent = UNMutableNotificationContent()
// Add the content to the notification content
notificationContent.title = "P2P Tracker"
notificationContent.body = (PriceTimeDate ?? "")
print(notificationContent.body)
notificationContent.badge = NSNumber(value: 1)
// Add an attachment to the notification content
if let url = Bundle.main.url(forResource: "dune",
withExtension: "png") {
if let attachment = try? UNNotificationAttachment(identifier: "dune",
url: url,
options: nil) {
notificationContent.attachments = [attachment]
}
}
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3,
repeats: false)
let request = UNNotificationRequest(identifier: "testNotification",
content: notificationContent,
trigger: trigger)
userNotificationCenter.add(request) { (error) in
if let error = error {
print("Notification Error: ", error)
}
}
}
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.
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 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")
}
}
}
This is my code for UNUserNotification
func scheduleNotification() {
UNUserNotificationCenter.current().getNotificationSettings { (notificationSettings) in
switch notificationSettings.authorizationStatus {
case .notDetermined:
self.requestAuthorization(completionHandler: { (success) in
guard success else { return }
// Schedule Local Notification
self.scheduleLocalNotification()
})
case .authorized:
// Schedule Local Notification
self.scheduleLocalNotification()
case .denied:
print("Application Not Allowed to Display Notifications")
}
}
}
private func scheduleLocalNotification() {
// Create Notification Content
let notificationContent = UNMutableNotificationContent()
// Configure Notification Content
notificationContent.title = "Hello"
notificationContent.body = ""
// Add Trigger
let notificationTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 10.0, repeats: false)
// Create Notification Request
let notificationRequest = UNNotificationRequest(identifier: id, 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))")
}
}
}
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)
}
}
It schedules the notification 10 seconds after the call and it works, the notification is presented on the lock screen, the problem is that it does not trigger any sound/vibration.
Upon request I'm setting these options [.alert, .sound, .badge], what am I missing?
p.s. I am not willing to set a custom sound, the default one is enough
You are missing the critical element that will play the sound:
notificationContent.sound = UNNotificationSound.default()
Including UNMutableNotificationContent should solve your issue.