Skip to content

feat!: iOS major enhancements BGAppRefreshTask, BGProcessingTask, beginBackgroundTask, printScheduledTasks #511

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 35 commits into from
Apr 8, 2024

Conversation

absar
Copy link
Contributor

@absar absar commented Sep 27, 2023

Closes #295
Closes #450
Closes #497
Closes #306

Due to iOS side of this plugin falling behind Android it was necessary to do a major overhaul on iOS side. In summary the new changes are:

  • iOS now supports one off immediate tasks Workmanager.registerOneOffTask which now uses iOS beginBackgroundTask instead of previous BGAppRefreshTask solution, migration guide is created for this. The reason to not use BGAppRefreshTask is to make it in line with Android side design and tackle some items discussed in Improved iOS BGTaskScheduler request options and scheduling #295
  • New periodic tasks Workmanager.registerPeriodicTask which uses iOS BGAppRefreshTask
  • New long running tasks Workmanager.registerProcessingTask using iOS BGProcessingTask, which was previously being done by registerOneOffTask. If or when Android side feature request Feature request - Support for long-running workers using WorkManager foreground service #236 is done that should be linked to Workmanager.registerProcessingTask to have same feature on both platforms
  • New Workmanager.checkBackgroundRefreshPermission to check Background App Refresh permission. On iOS user can disable Background App Refresh permission anytime, hence background tasks can only run if user has granted the permission.
    With Workmanager.checkBackgroundRefreshPermission you can check whether background app refresh is enabled
  • Workmanager.printScheduledTasks to print details of scheduled tasks to console. To be used during development/debugging. This will to some extent give developers some confidence that tasks are really scheduled and iOS will trigger at some point in the future
  • Example App has been updated to use the new methods. A new button is added to make it easy to check the status of tasks, it prints scheduled task details to console and displays the saved shared preferences values on screen
  • iOS documentation updates to minimize confusion and iOS understanding issues. Readme, iOS setup and API level docs are updated
  • A migration section is created for breaking changes

Migration

BREAKING CHANGE: This PR has some breaking changes for iOS:

  • Workmanager.registerOneOffTask was previously using iOS BGProcessingTask, now it will be an immediate run task which will continue in the background if user leaves the App. Since the previous solution meant the one off task will only run if the device is idle and as often experienced only when device is charging, in practice it means somewhere at night, or not at all during that day, because BGProcessingTask is meant for long running tasks. The new solution makes it more in line with Android except it does not support initialDelay
  • If you need the old behavior you can use the new iOS only method Workmanager.registerProcessingTask:
    1. Replace Workmanager().registerOneOffTask with Workmanager().registerProcessingTask in your App
    2. Replace WorkmanagerPlugin.registerTask with WorkmanagerPlugin.registerBGProcessingTask in AppDelegate.swift
  • Workmanager.registerOneOffTask does not support initialDelay
  • Workmanager.registerOneOffTask now supports inputData which was always returning null in the previous solution
  • Workmanager.registerOneOffTask now does NOT require WorkmanagerPlugin.registerTask call in AppDelegate.swift hence remove the call

I am not a native iOS developer but tried to address my own needs after not finding any solution and waiting for too long, it will help me if you guys test and raise any issues even if this PR doesn't get merged, i will try to maintain my branch https://github.com/absar/flutter_workmanager/tree/ios-bg-tasks-enh-final

Shoutout to Lars Huth/xunreal75 for his attempt in 2022, his work helped a lot during this PR


Q&A 2025, based on issues mentioned in this thread:

I had updated the docs extensively as part of this PR in 2023, if you read carefully you will understand why iOS does not trigger periodic tasks or when it will:
https://github.com/absar/flutter_workmanager/blob/ios-bg-tasks-enh-final/IOS_SETUP.md#enable-bgtaskscheduler
https://github.com/absar/flutter_workmanager/tree/ios-bg-tasks-enh-final?tab=readme-ov-file#periodic-tasks

On iOS you can print scheduled tasks using Workmanager.printScheduledTasks, if a task is scheduled and handed over to iOS that's it, as a developer your job is done, after that you cannot influence when it will run, its totally based on Apple and their usage pattern algorithm, if you just schedule a task and never use the app again in the coming days iOS might not even give your app background time, I personally experienced on latest iOS it takes at least two days to learn and probably from the third day it will start triggering if you have been using the app, also in the meantime if you uninstall the app and re-install all the usage pattern is reset which means once again it will not trigger for a few days. Also note that unlike Android there is no concept of headless background periodic tasks, means if you kill the app from recent app periodic task will never fire, only processing task can run even if you kill the app. I also experienced if you use processing task it triggers it when your device is charging. My recommendation would be to register two tasks one periodic and one processing even if the periodic does not trigger the processing will ultimately trigger when device is charging and at the end of processing task schedule it again for the next day, but be careful iOS sometimes runs both at the same time you should design your tasks accordingly to not do the same work in parallel by maintaining a shared preferences flag, not handling it might create duplicate data if you are processing data.

And lastly watch these videos:
https://developer.apple.com/videos/play/wwdc2019/707
https://wwdcnotes.com/documentation/wwdcnotes/wwdc20-10063-background-execution-demystified/
https://developer.apple.com/forums/thread/685525

Regarding requiresNetworkConnectivity and requiresExternalPower these are swapped by mistake, there is already a PR but this repository is in abandoned state even though it is under Flutter Community which is sad since even Flutter docs refer to this plugin in official docs. Someone raised this PR to adopt this plugin as first party package unfortunately it was rejected

xunreal75 and others added 30 commits October 26, 2022 23:09
added alert and MaterialApp to workmanager when no iOS permissions activated
fixed BGProcessing
fixed inputdata in callback on task
clarified timings
Fix workmanager not working on iOS
Merge iOS BGTaskScheduler enhancements by PresenceApp
* Use logInfo instead of prints and NSLog
* Log unnecessary logs only in debug mode
* Remove unnecessary logs
* Remove isInitalized flag in SwiftWorkmanagerPlugin which was not set to true anywhere
…egisterProcessingTask to be consistent with rest of the plugin and possible future Android implementation

* iOS, Rename wrongly named startOnOffTask to startOneOffTask
…e size is reduced and it will make it easy to review

* Change new task identifier to be consistent with existing ones e.g. instead of app.workmanager... use be.tramckrijte...
* Documentation update
* Remove unnecessary logs, comments etc which were added in PRs which were not merged, and cleanup unnecessary code
* Revert using a custom log helper OS file to use the plugins existing shared prefs
* Bump example flutter sdk to < 4 instead of < 3
…r can define task names instead of using hardcoded names
…rmission is not assigned.

Initialize should return result
* Temporarily commented old iOS background fetch
…cheduled tasks. To be used during development/debugging.

Format readme to improve readability
…ead of waiting for App to go to background. Since doing on backgrounding will keep on changing earliest begin date.

* Add printScheduledTasks to example app
* Format example code
@NomadicDeveloper22
Copy link

I got this working perfectly now! Needed to use a different ref

workmanager:
    git:
      url: https://github.com/absar/flutter_workmanager.git
      ref: 73cc539cd2f1b992b468e6c44f5d0a7919d7ffda

@ened
Copy link
Collaborator

ened commented Feb 26, 2024

@absar permission_handler now supports the background app refresh check (I sent a PR a while back). We are clear to remove the permission code from your PR and start thinking about the merge. :)

@absar
Copy link
Contributor Author

absar commented Mar 4, 2024

@ened currently am quite occupied, will get back to it, let me know if you have any other reviews for the PR so that I address them in one shot.
For the ones using my branch either use the latest commit hash instead of just the branch name https://github.com/absar/flutter_workmanager/tree/ios-bg-tasks-enh-final or fork it and use it from your own repos, and start migrating the permission Workmanager.checkBackgroundRefreshPermission to permission_handler package mentioned by @ened so that once Workmanager.checkBackgroundRefreshPermission is removed from this PR you are not impacted

@Flutter36
Copy link

Flutter36 commented Mar 4, 2024

Hi, I was looking at refreshing tokens in the background using work manager. On IOS, is it possible to trigger one of task with same ids multiple times? If I have to permit the ids upfront, then I cant register one off task few mins before the token expiry to refresh it. Any suggestions on how to do this?

Also, is it possible to register the one of task (in swift code) with name instead of id?

@Flutter36
Copy link

Hi,

I am trying to run periodic task on ios with below code and settings. Unfortunately, the task isnt getting triggered. Any help would be appreciated.

Swift code:

import UIKit
import Flutter
import workmanager

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
self.window.makeSecure()
GeneratedPluginRegistrant.register(with: self)
UNUserNotificationCenter.current().delegate = self
WorkmanagerPlugin.setPluginRegistrantCallback { registry in
// Registry in this case is the FlutterEngine that is created in Workmanager's
// performFetchWithCompletionHandler or BGAppRefreshTask.
// This will make other plugins available during a background operation.
GeneratedPluginRegistrant.register(with: registry)
}

WorkmanagerPlugin.registerPeriodicTask(withIdentifier: "com.background_task_manager.token_refresh_task", frequency: NSNumber(value: 20*60))
//UIApplication.shared.setMinimumBackgroundFetchInterval(TimeInterval(20*60));

return super.application(application, didFinishLaunchingWithOptions: launchOptions)

}

override func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler(.alert) // shows banner even if app is in foreground
}
}

extension UIWindow {
func makeSecure() {
let isTestFlight = Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
let isSecureMode = Bundle.main.object(forInfoDictionaryKey: "EnableSecureMode") as! String == "YES"
if (isTestFlight || !isSecureMode) {
return
}

let field = UITextField()
let view = UIView(frame: CGRect(x: 0, y: 0, width: field.frame.self.width, height: field.frame.self.height))
field.isSecureTextEntry = true
self.addSubview(field)
self.layer.superlayer?.addSublayer(field.layer)
field.layer.sublayers?.last!.addSublayer(self.layer)
field.leftView = view
field.leftViewMode = .always

}
}

version of the library used

workmanager:
git:
url: https://github.com/absar/flutter_workmanager.git
ref: 73cc539

Target platform an other details:

platform :ios, '13.0'

Code to trigger period task

      Workmanager().registerPeriodicTask(
  BackgroundTaskKeys.refreshTokenTaskName, // Unique ID for your task
  BackgroundTaskKeys.refreshTokenTaskName,
  initialDelay: Duration(seconds: 10),
  frequency: Duration(seconds: 20*60)
);


Info LIst params

		<key>UIBackgroundModes</key>
    <array>
	    <string>fetch</string>
	    <string>processing</string>
    </array>
    
    
    		<key>BGTaskSchedulerPermittedIdentifiers</key>
    <array>
	    <string>com.kdi.background_task_manager.token_refresh_task</string>
    </array>

@ened
Copy link
Collaborator

ened commented Apr 8, 2024

Merging this, but will require a few iterations before packaging this into a release.

@ened ened merged commit b783000 into fluttercommunity:main Apr 8, 2024
@yashmnp1910
Copy link

@ened Can you share the ETA for the new release?

@absar absar mentioned this pull request Apr 19, 2024
@alfietapping
Copy link

alfietapping commented May 12, 2024

Trying to run periodic task on ios results in this error

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(unhandledMethod("registerPeriodicTask") error, Unhandled method registerPeriodicTask, null, null)

Works fine on Android though, just an isolated iOS issue

@absar
Copy link
Contributor Author

absar commented May 13, 2024

@alfietapping make sure to use the mentioned branch https://github.com/absar/flutter_workmanager/tree/ios-bg-tasks-enh-final instead of using master branch

@alfietapping
Copy link

@absar thanks for this. The error has cleared, but unfortunately nothing actually happens now on iOS, workmanager never triggers any work. I've followed the specific ios setup from the docs, and i do get this line of code fire

BGAppRefreshTask submitted dailyReminder earliestBeginInSeconds:0.0

but it never runs the period task work declared in the main file, perhaps im missing something else?

@eserdeiro
Copy link

eserdeiro commented May 20, 2024

@alfietapping I have tried it, and it has worked for me.

Did you try opening Xcode, running your app, then in xcode going to 'debug -> simulate background fetch' ?

In my case, by doing that, my periodicTask has been executed after 8 minutes.

I am using the following in my pubspec

workmanager:
   git:
     url: https://github.com/fluttercommunity/flutter_workmanager.git
     ref: b783000

@sinhpn92
Copy link

I have the same issue on iOS. registerProcessingTask is not trigger on iOS.

@alfietapping
Copy link

@alfietapping I have tried it, and it has worked for me.

Did you try opening Xcode, running your app, then in xcode going to 'debug -> simulate background fetch' ?

In my case, by doing that, my periodicTask has been executed after 8 minutes.

I am using the following in my pubspec

workmanager:
   git:
     url: https://github.com/fluttercommunity/flutter_workmanager.git
     ref: b783000

Did you try actually running the process on a device and checking it ran work? I haven't tested simulating anything through xcode etc, if it doesn't work on a device then it doesn't function in the real world.

@andersonribeir
Copy link

@alfietapping I have tried it, and it has worked for me.

Did you try opening Xcode, running your app, then in xcode going to 'debug -> simulate background fetch' ?

In my case, by doing that, my periodicTask has been executed after 8 minutes.

I am using the following in my pubspec

workmanager:
   git:
     url: https://github.com/fluttercommunity/flutter_workmanager.git
     ref: b783000

i'm in this reference, but not triggered. OneOfTask works normally...

@eserdeiro
Copy link

@alfietapping I have tested it on an Iphone 12 pro (physical device) with iOS 17.4.1 and it worked.

Remember to set minimum version iOS 13 in your flutter project.

@andersonribeir
Copy link

andersonribeir commented May 21, 2024

@alfietapping I have tested it on an Iphone 12 pro (physical device) with iOS 17.4.1 and it worked.

Remember to set minimum version iOS 13 in your flutter project.

I'm in iphone 11, ios 17 with minimum iOS 13...

OneOfTask works normally, but periodic doesn't, I'm not using initialValue, and I set the period to 15 minutes. What else should I try?

@FluffyDiscord
Copy link

I dont understand the steps here https://github.com/fluttercommunity/flutter_workmanager/blob/main/IOS_SETUP.md#testing-bgtaskscheduler

Where is the submit() that i need to find and add a breakpoint.

@FluffyDiscord
Copy link

Also I am getting the following error Could not schedule app refresh: Error Domain=BGTaskSchedulerErrorDomain Code=3 "(null)"

@xunreal75
Copy link
Contributor

Hi. Please read references for this issue and add related Information in info.plist

BGTaskSchedulerErrorCodeNotPermitted = 3 - The task request could not be submitted because the appropriate background mode is not included in the UIBackgroundModes array, or its identifier was not present in the BGTaskSchedulerPermittedIdentifiers array in the app's Info.plist.

@Mistic92
Copy link

I'm using

  workmanager:
    git:
      url: https://github.com/fluttercommunity/flutter_workmanager.git
      ref: b783000

While single everything works on Android on iOS I can't have working Workmanager().registerPeriodicTask.

In my AppDelegate.swift I have

        // In AppDelegate.application method
        WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "xxxxxxxxx")

        // Register a periodic task in iOS 13+
        WorkmanagerPlugin.registerPeriodicTask(withIdentifier: "yyyyyyyyyyy", frequency: NSNumber(value: 15 * 60))        

In info.plist I have both identifiers

	<key>BGTaskSchedulerPermittedIdentifiers</key>
	<array>
		<string>xxxxxxxxx</string>
		<string>yyyyyyyyyyy</string>
	</array>
	<key>UIBackgroundModes</key>
	<array>
		<string>processing</string>
		<string>remote-notification</string>
		<string>fetch</string>
		<string>audio</string>
	</array>

and when I try to call registerPeriodicTask I see log "BGAppRefreshTask submitted yyyyyyyy earliestBeginInSeconds:0.0" but nothing happens even after 24h.

@rapkatt
Copy link

rapkatt commented Oct 20, 2024

@Mistic92
Did you find solution? I have same issue.

@delfme
Copy link
Contributor

delfme commented Feb 9, 2025

Same issue, periodic task is never fired on iOS physical device, even after 48h. Configration is correct and one off task works.

@delfme
Copy link
Contributor

delfme commented Feb 9, 2025

Also we noticed this

requiresNetworkConnectivity: requiresCharging,
requiresExternalPower: requiresNetwork)
which looks like an error. Additionally, why inputData is not passed in schedulePeriodicTask and scheduleBackgroundProcessing? Why requiresNetworkConnectivity and requiresExternalPower is not passed in schedulePeriodicTask?

@Vito0912
Copy link

Same issue here.

@delfme / @rapkatt, did you ever find a workaround?

@absar
Copy link
Contributor Author

absar commented Feb 13, 2025

I had updated the docs extensively as part of this PR in 2023, if you read carefully you will understand why iOS does not trigger periodic tasks or when it will:
https://github.com/absar/flutter_workmanager/blob/ios-bg-tasks-enh-final/IOS_SETUP.md#enable-bgtaskscheduler
https://github.com/absar/flutter_workmanager/tree/ios-bg-tasks-enh-final?tab=readme-ov-file#periodic-tasks

On iOS you can print scheduled tasks using Workmanager.printScheduledTasks, if a task is scheduled and handed over to iOS that's it, as a developer your job is done, after that you cannot influence when it will run, its totally based on Apple and their usage pattern algorithm, if you just schedule a task and never use the app again in the coming days iOS might not even give your app background time, I personally experienced on latest iOS it takes at least two days to learn and probably from the third day it will start triggering if you have been using the app, also in the meantime if you uninstall the app and re-install all the usage pattern is reset which means once again it will not trigger for a few days. Also note that unlike Android there is no concept of headless background periodic tasks, means if you kill the app from recent app periodic task will never fire, only processing task can run even if you kill the app. I also experienced if you use processing task it triggers it when your device is charging. My recommendation would be to register two tasks one periodic and one processing even if the periodic does not trigger the processing will ultimately trigger when device is charging and at the end of processing task schedule it again for the next day, but be careful iOS sometimes runs both at the same time you should design your tasks accordingly to not do the same work in parallel by maintaining a shared preferences flag, not handling it might create duplicate data if you are processing data.

And lastly watch these videos:
https://developer.apple.com/videos/play/wwdc2019/707
https://wwdcnotes.com/documentation/wwdcnotes/wwdc20-10063-background-execution-demystified/
https://developer.apple.com/forums/thread/685525

Regarding requiresNetworkConnectivity and requiresExternalPower these are swapped by mistake, there is already a PR but this repository is in abandoned state even though it is under Flutter Community which is sad since even Flutter docs refer to this plugin in official docs. Someone raised this PR to adopt this plugin as first party package unfortunately it was rejected

@Vito0912
Copy link

Vito0912 commented Feb 13, 2025

why iOS does not trigger periodic tasks or when it will:

It's not that it works only irregularly; it doesn't seem to work at all.

Note: Everything mentioned works on the emulator but not on a real device (as stated above by some).

On iOS you can print scheduled tasks using Workmanager.printScheduledTasks,

This printed that no tasks were scheduled on one test branch, even after a second start-up. On our current test branch (using your ref b783000), it doesn't print anything (it is being called for sure, as the prints above and below are executed). I have not looked into it that much, but as many stated that it does not work while following the guide. See #511 (comment)
He did not state if he tested to force the backrgound task, but I would assume he did.

Also, triggering the background fetch from Xcode settings or executing the command to force a background fetch does not work at all.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.