BackgroundTasks not changing values when activated - ios

I am trying to use the BackgroundTask framework to run a block of code periodically when the user closes the app. Preferably, I would want the code to run once every hour. In the project file below, I have the code change a value stored in UserDefaults, but no matter how long I wait it never runs the code in the task.
AppDelegate.swift
import UIKit
import BackgroundTasks
import OSLog
#main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.refresh", using: nil) { task in
Logger().info("[BGTASK] Preform bg fetch \("com.example.refresh")")
task.setTaskCompleted(success: true)
self.scheduleAppRefresh()
}
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.restock", using: nil) { task in
Logger().info("[BGTASK] Preform bg fetch \("com.example.restock")")
AppUserDefaults.newString = "New String" // <-- The string is changed here
task.setTaskCompleted(success: true)
self.scheduleBackgroundProcessing()
}
return true
}
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "com.example.refresh")
request.earliestBeginDate = Date(timeIntervalSinceNow: 60)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule app refresh task \(error.localizedDescription)")
}
}
func scheduleBackgroundProcessing() {
let request = BGProcessingTaskRequest(identifier: "com.example.restock")
request.requiresNetworkConnectivity = true
request.requiresExternalPower = false
request.earliestBeginDate = Date(timeIntervalSinceNow: 1 * 60)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule image fetch: (error)")
}
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func applicationDidEnterBackground(_ application: UIApplication) {
Logger().info("App did enter background")
self.scheduleAppRefresh()
self.scheduleBackgroundProcessing()
}
}
When the app loads, I show newString in the ContentView:
ContentView.swift
import SwiftUI
import UserNotifications
struct ContentView: View {
var body: some View {
Text("New String: \(AppUserDefaults.newString)")
}
}
Project

Related

BGAppRefreshTask never called

BGAppRefreshTask is not working, I marked the option of background fetch capability
The method inside BGTaskScheduler.shared.register is never called
my code:
private let appRefreshTaskId = "internetDisconnectionsId"
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if #available(iOS 13, *) {
BGTaskScheduler.shared.register(forTaskWithIdentifier: appRefreshTaskId, using: nil) { task in
self.handleAppRefreshTask(task: task as! BGAppRefreshTask)
}
}
return true
}
func handleAppRefreshTask(task: BGAppRefreshTask) {
task.expirationHandler = {
print("Background Working")
task.setTaskCompleted(success: true)
}
}
#available(iOS 13.0, *)
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: appRefreshTaskId)
request.earliestBeginDate = Date(timeIntervalSinceNow: 2 * 60) // Refresh after 2 minutes.
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule app refresh task \(error.localizedDescription)")
}
}
func sceneDidEnterBackground(_ scene: UIScene) {
(UIApplication.shared.delegate as! AppDelegate).scheduleAppRefresh()
print("called")
}
I would love to understand where I am wrong

BGTaskScheduler.shared.register Not Working Swift 5

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.fetch.background", using: nil) { task in
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}
return true
}
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "com.fetch.background")
// Fetch no earlier than 15 minutes from now.
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule app refresh: \(error)")
}
}
func handleAppRefresh(task: BGAppRefreshTask) {
// Schedule a new refresh task.
scheduleAppRefresh()
print("this background fetch called")
}
Above Code BGTaskScheduler completion Not working in swift 5, I added identifier with info.plist file permission. I need to use API with app background state. Please assist how can I achieve this one. Thanks

Why has BGProcessingTask already been registered?

I am creating my own background task and I keep getting an error stating: 'Launch handler for task with identifier "processingTaskId" has already been registered'. I am still learning and would like to understand this error more.
code
import UIKit
import Flutter
import BackgroundTasks
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
let processingTaskId = "com.demo.processingtask"
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
GeneratedPluginRegistrant.register(with: self)
if #available(iOS 13, *) {
BGTaskScheduler.shared.register(forTaskWithIdentifier: processingTaskId, using: nil) { task in
self.handleTask(task: task as! BGProcessingTask)
}
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
#available(iOS 13.0, *)
func handleTask(task: BGProcessingTask) {
scheduleTask()
task.setTaskCompleted(success: true)
}
#available(iOS 13.0, *)
func scheduleTask() {
let request = BGProcessingTaskRequest(identifier: processingTaskId)
request.earliestBeginDate = nil
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule task: (error)")
}
}
}

didRegisterForRemoteNotificationsWithDeviceToken isn't called when requesting notification permission after app launch

I am following the push notification tutorial here:
https://www.raywenderlich.com/11395893-push-notifications-tutorial-getting-started
I've gotten everything working on a test device, and after allowing notifications, I can get the device token when didRegisterForRemoteNotificationsWithDeviceToken runs.
Now I want to change when I ask for notification permissions. I don't want to ask the user permission for notifications on app launch, as in the tutorial. I only want to ask later on, once the user has logged in. However, when I call the AppDelegate to register for notifications, it prompts the user, but when they accept, it doesn't run the didRegisterForRemoteNotificationsWithDeviceToken function, which has the device token I need to send to my back end. What gives?
AppDelegate.swift
import SwiftUI
import UserNotifications
class AppDelegate: NSObject, UIApplicationDelegate {
#EnvironmentObject var authenticatedUser: AuthenticatedUser
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
return true
}
func registerForPushNotifications() {
UNUserNotificationCenter.current()
.requestAuthorization(
options: [.alert, .sound, .badge]) { [weak self] granted, _ in
print("Permission granted: \(granted)")
guard granted else { return }
self?.getNotificationSettings()
}
}
func getNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
print("Notification settings: \(settings)")
guard settings.authorizationStatus == .authorized else { return }
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
// this function never gets called!
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
let token = tokenParts.joined()
print("Device Token: \(token)")
// send token to backend here
}
func application(
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
) {
print("Failed to register: \(error)")
}
}
MyGroupView.swift where I want to prompt for notification permission
import SwiftUI
struct MyGroupView: View {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
#EnvironmentObject var authenticatedUser: AuthenticatedUser
// other variables and state here...
var body: some View {
GeometryReader { proxy in
VStack {
// content here...
}
.onAppear() {
appDelegate.registerForPushNotifications()
}
.edgesIgnoringSafeArea(.bottom)
}
}
// other functions...
}
In your AppDelegates didFinishLaunchingWithOptions you need to set the delegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
UNUserNotificationCenter.current().delegate = self
return true
}

SwiftUI prompt notification permission in second view

I have 2 view in my project which is login and home view. I tried to prompt notification permission in the home view after the user successful login. After I tried, I found the Push Notification for Beginner. The permission prompt while the application first launch in the login view. Is there any way to set the view of the permission agreement?
struct ContentView: View {
#EnvironmentObject var authService:AuthService
var body: some View{
ZStack{
if(!authService.signedIn){
RegisterView()
}
else{
HomePageView() //The view I want to ask for permission after signedIn
}
}
}
}
import UIKit
import UserNotifications
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let auth = UserDefaults.standard.object(forKey: "Auth")
//if auth != nil {
// registerForPushNotifications()
//} //what I tried, but It prompt while user restart the app after login.
registerForPushNotifications()
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
func registerForPushNotifications() {
//1
UNUserNotificationCenter.current()
//2
.requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] granted, _ in
print("Permission granted: \(granted)")
guard granted else { return }
self?.getNotificationSettings()
}
}
func getNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
print("Notification settings: \(settings)")
guard settings.authorizationStatus == .authorized else { return }
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
let token = tokenParts.joined()
print("Device Token: \(token)")
}
}
Actually, you are requesting notification access, when the user launches the app (inside application(_:, didFinishLaunchingWithOptions:). you have to call your registerForPushNotifications method, after a successful login.
for example:
// successful login
// e.g: authService..signedIn = true
(UIApplication.shared.delegate as? AppDelegate).registerForPushNotifications()

Resources