Skip to content

Commit b783000

Browse files
absarxunreal75iosephmagnodelfme
authored
feat!: iOS major enhancements BGAppRefreshTask, BGProcessingTask, beginBackgroundTask, printScheduledTasks (#511)
* fix:Update Workmanager iOS because no callback in Background on iOS real device, added 30sec BGAppRefresh, Updated example #396 * added permissionhandler an requests for iOS added alert and MaterialApp to workmanager when no iOS permissions activated * fixed errormessage on xcode * feat:Added check for background refresh permissions #441 * text to display task event dates (show prefs) added. * fixed workmanager iOS Part fixed BGProcessing fixed inputdata in callback on task clarified timings * fixed warning dead code and ! check * improved Task description (hints) * Update README.md * Update README.md * Update README.md * Format readme iOS examples * Improve code documentation * Cleanups in SwiftWorkmanagerPlugin.swift * 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 * * iOS, Rename registeriOSBackgroundProcessingTask to a generic name registerProcessingTask to be consistent with rest of the plugin and possible future Android implementation * iOS, Rename wrongly named startOnOffTask to startOneOffTask * * Cleanup code to make it more close to original plugin so that change 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 * Add task identifiers to iOS AppRefresh and ProcessingTask so that user can define task names instead of using hardcoded names * * iOS AppRefresh task interval should be 15 minutes * Documentation update * Initialize should not auto open App settings if background refresh permission is not assigned. Initialize should return result * Continue work on task identifiers for iOS AppRefresh and ProcessingTask. * Temporarily commented old iOS background fetch * Fix extra commas on iOS * New iOS feature printScheduledTasks to print details of un-executed scheduled tasks. To be used during development/debugging. Format readme to improve readability * iOS Periodic and processing tasks will be immediately scheduled, instead 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 * Option to set frequency for iOS periodic tasks in AppDelegate.swift * Add initialDelay support for Workmanager.registerProcessingTask * Remove unnecessary WorkmanagerPlugin.registerBGProcessingTask calls from AppDelegate.swift * Cleanup unused params from Workmanager.registerProcessingTask * Update readme and iOS setup as per new iOS developments * Create migration steps for iOS Workmanager.registerOneOffTask to Workmanager.registerProcessingTask * Update iOS docs * TODO for cleanups later --------- Co-authored-by: Lars Huth <[email protected]> Co-authored-by: xunreal75 <[email protected]> Co-authored-by: Ioseph Magno <[email protected]> Co-authored-by: delfme <[email protected]>
1 parent 370df0c commit b783000

15 files changed

+922
-140
lines changed

Diff for: IOS_SETUP.md

+23-5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ This plugin is compatible with **Swift 4.2** and up. Make sure you are using **X
1010
> ⚠️ BGTaskScheduler is similar to Background Fetch described below and brings a similar set of constraints. Most notably, there are no guarantees when the background task will be run. Excerpt from the documentation:
1111
>
1212
> Schedule a processing task request to ask that the system launch your app when conditions are favorable for battery life to handle deferrable, longer-running processing, such as syncing, database maintenance, or similar tasks. The system will attempt to fulfill this request to the best of its ability within the next two days as long as the user has used your app within the past week.
13+
>
14+
> Workmanager BGTaskScheduler methods `registerOneOffTask`, `registerPeriodicTask`, and `registerProcessingTask` are only available on iOS 13+
1315
1416
![Screenshot of Background Fetch Capabilities tab in Xcode ](.art/ios_background_mode_background_processing.png)
1517

@@ -19,6 +21,9 @@ This will add the **UIBackgroundModes** key to your project's `Info.plist`:
1921
<key>UIBackgroundModes</key>
2022
<array>
2123
<string>processing</string>
24+
25+
<!-- If you need periodic tasks in iOS 13+ you need to enable Background Fetch as well -->
26+
<string>fetch</string>
2227
</array>
2328
```
2429

@@ -31,16 +36,25 @@ import workmanager
3136

3237
``` swift
3338
// In AppDelegate.application method
34-
WorkmanagerPlugin.registerTask(withIdentifier: "task-identifier")
39+
WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "task-identifier")
40+
41+
// Register a periodic task in iOS 13+
42+
WorkmanagerPlugin.registerPeriodicTask(withIdentifier: "be.tramckrijte.workmanagerExample.iOSBackgroundAppRefresh", frequency: NSNumber(value: 20 * 60))
3543
```
3644

3745
- Info.plist
3846
``` xml
3947
<key>BGTaskSchedulerPermittedIdentifiers</key>
40-
<array>
41-
<string>task-identifier</string>
42-
</array>
48+
<array>
49+
<string>task-identifier</string>
50+
51+
<!-- Register a periodic task in iOS 13+ -->
52+
<string>be.tramckrijte.workmanagerExample.iOSBackgroundAppRefresh</string>
53+
</array>
4354
```
55+
> ⚠️ On iOS 13+, adding a `BGTaskSchedulerPermittedIdentifiers` key to the Info.plist for new `BGTaskScheduler` API disables the `performFetchWithCompletionHandler` and `setMinimumBackgroundFetchInterval`
56+
methods, which means you cannot use both old Background Fetch and new `registerPeriodicTask` at the same time, you have to choose one based on your minimum iOS target version.
57+
For details see [Apple Docs](https://developer.apple.com/documentation/uikit/app_and_environment/scenes/preparing_your_ui_to_run_in_the_background/using_background_tasks_to_update_your_app)
4458

4559
And will set the correct *SystemCapabilities* for your target in the `project.pbxproj` file:
4660

@@ -64,7 +78,11 @@ e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWith
6478

6579
## Enabling Background Fetch
6680

67-
> ⚠️ Background fetch is one supported way to do background work on iOS with work manager: **Periodic tasks** are available on Android only for now! (see #109)
81+
> ⚠️ Background fetch is one supported way to do background work on iOS with work manager. Note that this API is deprecated starting iOS 13, however it still works on iOS 13+ as of writing this article
82+
83+
> ⚠️ On iOS 13+, adding a `BGTaskSchedulerPermittedIdentifiers` key to the Info.plist for new `BGTaskScheduler` API disables the `performFetchWithCompletionHandler` and `setMinimumBackgroundFetchInterval`
84+
methods, which means you cannot use both old Background Fetch and new `registerPeriodicTask` at the same time, you have to choose one based on your minimum iOS target version.
85+
For details see [Apple Docs](https://developer.apple.com/documentation/uikit/app_and_environment/scenes/preparing_your_ui_to_run_in_the_background/using_background_tasks_to_update_your_app)
6886

6987
Background fetching is very different compared to Android's Background Jobs.
7088
In order for your app to support Background Fetch, you have to add the *Background Modes* capability in Xcode for your app's Target and check *Background fetch*:

Diff for: README.md

+100-6
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ void callbackDispatcher() {
7373
```
7474

7575
Android tasks are identified using their `taskName`.
76-
iOS tasks are identitied using their `taskIdentifier`.
76+
iOS tasks are identified using their `taskIdentifier`.
7777

7878
However, there is an exception for iOS background fetch: `Workmanager.iOSBackgroundTask`, a constant for iOS background fetch task.
7979

@@ -93,25 +93,119 @@ Refer to the example app for a successful, retrying and a failed task.
9393

9494
# iOS specific setup and note
9595

96-
iOS supports **One off tasks** with a few basic constraints:
96+
Initialize Workmanager only once.
97+
Background app refresh can only be tested on a real device, it cannot be tested on a simulator.
98+
99+
### Migrate to 0.6.x
100+
Version 0.6.x of this plugin has some breaking changes for iOS:
101+
- 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**
102+
- If you need the old behavior you can use the new iOS only method `Workmanager.registerProcessingTask`:
103+
1. Replace `Workmanager().registerOneOffTask` with `Workmanager().registerProcessingTask` in your App
104+
1. Replace `WorkmanagerPlugin.registerTask` with `WorkmanagerPlugin.registerBGProcessingTask` in `AppDelegate.swift`
105+
- Workmanager.registerOneOffTask does not support **initialDelay**
106+
- Workmanager.registerOneOffTask now supports **inputData** which was always returning null in the previous solution
107+
- Workmanager.registerOneOffTask now does NOT require `WorkmanagerPlugin.registerTask` call in `AppDelegate.swift` hence remove the call
108+
109+
### One off tasks
110+
iOS supports **One off tasks** only on iOS 13+ with a few basic constraints:
111+
112+
`registerOneOffTask` starts immediately. It might run for only 30 seconds due to iOS restrictions.
97113

98114
```dart
99115
Workmanager().registerOneOffTask(
100116
"task-identifier",
101117
simpleTaskKey, // Ignored on iOS
102-
initialDelay: Duration(minutes: 30),
118+
initialDelay: Duration(minutes: 30), // Ignored on iOS
119+
inputData: ... // fully supported
120+
);
121+
```
122+
123+
### Periodic tasks
124+
iOS supports two types of **Periodic tasks**:
125+
- On iOS 12 and lower you can use deprecated Background Fetch API, see [iOS Setup](./IOS_SETUP.md), even though the API is
126+
deprecated by iOS it still works on iOS 13+ as of writing this article
127+
128+
- `registerPeriodicTask` is only supported on iOS 13+, it might run for only 30 seconds due to iOS restrictions, but doesn't start immediately, rather iOS will schedule it as per user's App usage pattern.
129+
130+
> ⚠️ On iOS 13+, adding a `BGTaskSchedulerPermittedIdentifiers` key to the Info.plist for new `BGTaskScheduler` API disables the `performFetchWithCompletionHandler` and `setMinimumBackgroundFetchInterval`
131+
methods, which means you cannot use both old Background Fetch and new `registerPeriodicTask` at the same time, you have to choose one based on your minimum iOS target version.
132+
For details see [Apple Docs](https://developer.apple.com/documentation/uikit/app_and_environment/scenes/preparing_your_ui_to_run_in_the_background/using_background_tasks_to_update_your_app)
133+
134+
To use `registerPeriodicTask` first register the task in `Info.plist` and `AppDelegate.swift` [iOS Setup](./IOS_SETUP.md). Unlike Android, for iOS you have to set the frequency in `AppDelegate.swift`. The frequency is not guaranteed rather iOS will schedule it as per user's App usage pattern, iOS might take a few days to learn usage pattern. In reality frequency just means do not repeat the task before x seconds/minutes. If frequency is not provided it will default to 15 minutes.
135+
136+
```objc
137+
// Register a periodic task with 20 minutes frequency. The frequency is in seconds.
138+
WorkmanagerPlugin.registerPeriodicTask(withIdentifier: "be.tramckrijte.workmanagerExample.iOSBackgroundAppRefresh", frequency: NSNumber(value: 20 * 60))
139+
```
140+
141+
Then schedule the task from your App
142+
```dart
143+
const iOSBackgroundAppRefresh = "be.tramckrijte.workmanagerExample.iOSBackgroundAppRefresh";
144+
Workmanager().registerPeriodicTask(
145+
iOSBackgroundAppRefresh,
146+
iOSBackgroundAppRefresh,
147+
initialDelay: Duration(seconds: 10),
148+
frequency: Duration(hours: 1), // Ignored on iOS, rather set in AppDelegate.swift
149+
inputData: ... // Not supported
150+
);
151+
```
152+
153+
For more information see [BGAppRefreshTask](https://developer.apple.com/documentation/backgroundtasks/bgapprefreshtask)
154+
155+
### Processing tasks
156+
iOS supports **Processing tasks** only on iOS 13+ which can run for more than 30 seconds.
157+
158+
`registerProcessingTask` is a long running one off background task, currently only for iOS. It can be run for more than 30 seconds but doesn't start immediately, rather iOS might schedule it when device is idle and charging.
159+
Processing tasks are for long processes like data processing and app maintenance. Processing tasks can run for minutes, but the system can interrupt these.
160+
iOS might terminate any running background processing tasks when the user starts using the device.
161+
For more information see [BGProcessingTask](https://developer.apple.com/documentation/backgroundtasks/bgprocessingtask)
162+
163+
```dart
164+
const iOSBackgroundProcessingTask = "be.tramckrijte.workmanagerExample.iOSBackgroundProcessingTask";
165+
Workmanager().registerProcessingTask(
166+
iOSBackgroundProcessingTask,
167+
iOSBackgroundProcessingTask,
168+
initialDelay: Duration(minutes: 2),
103169
constraints: Constraints(
104-
// connected or metered mark the task as requiring internet
170+
// Connected or metered mark the task as requiring internet
105171
networkType: NetworkType.connected,
106-
// require external power
172+
// Require external power
107173
requiresCharging: true,
108174
),
109-
inputData: ... // fully supported
110175
);
111176
```
112177

178+
### Background App Refresh permission
179+
On iOS user can disable `Background App Refresh` permission anytime, hence background tasks can only run if user has granted the permission.
180+
With `Workmanager.checkBackgroundRefreshPermission` you can check whether background app refresh is enabled. If it is not enabled you might ask
181+
the user to enable it in app settings.
182+
183+
```dart
184+
if (Platform.isIOS) {
185+
final hasPermission = await Workmanager().checkBackgroundRefreshPermission();
186+
if (hasPermission != BackgroundRefreshPermissionState.available){
187+
// Inform the user that background app refresh is disabled
188+
}
189+
}
190+
```
191+
113192
For more information see the [BGTaskScheduler documentation](https://developer.apple.com/documentation/backgroundtasks).
114193

194+
### Print scheduled tasks
195+
On iOS you can print scheduled tasks using `Workmanager.printScheduledTasks`
196+
197+
It prints task details to console. To be used during development/debugging.
198+
Currently only supported on iOS and only on iOS 13+.
199+
200+
```dart
201+
if (Platform.isIOS) {
202+
Workmanager().printScheduledTasks();
203+
// Prints: [BGTaskScheduler] Task Identifier: iOSBackgroundAppRefresh earliestBeginDate: 2023.10.10 PM 11:10:12
204+
// Or: [BGTaskScheduler] There are no scheduled tasks
205+
}
206+
```
207+
208+
115209
# Customisation (Android)
116210

117211
Not every `Android WorkManager` feature is ported.

Diff for: example/ios/Runner.xcodeproj/project.pbxproj

+3-3
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@
490490
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
491491
GCC_WARN_UNUSED_FUNCTION = YES;
492492
GCC_WARN_UNUSED_VARIABLE = YES;
493-
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
493+
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
494494
MTL_ENABLE_DEBUG_INFO = NO;
495495
SDKROOT = iphoneos;
496496
SWIFT_VERSION = 4.2;
@@ -577,7 +577,7 @@
577577
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
578578
GCC_WARN_UNUSED_FUNCTION = YES;
579579
GCC_WARN_UNUSED_VARIABLE = YES;
580-
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
580+
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
581581
MTL_ENABLE_DEBUG_INFO = YES;
582582
ONLY_ACTIVE_ARCH = YES;
583583
SDKROOT = iphoneos;
@@ -628,7 +628,7 @@
628628
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
629629
GCC_WARN_UNUSED_FUNCTION = YES;
630630
GCC_WARN_UNUSED_VARIABLE = YES;
631-
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
631+
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
632632
MTL_ENABLE_DEBUG_INFO = NO;
633633
SDKROOT = iphoneos;
634634
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";

Diff for: example/ios/Runner/AppDelegate.swift

+9-7
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ import workmanager
2020
GeneratedPluginRegistrant.register(with: registry)
2121
}
2222

23-
WorkmanagerPlugin.registerTask(withIdentifier: "be.tramckrijte.workmanagerExample.taskId")
24-
WorkmanagerPlugin.registerTask(withIdentifier: "be.tramckrijte.workmanagerExample.simpleTask")
25-
WorkmanagerPlugin.registerTask(withIdentifier: "be.tramckrijte.workmanagerExample.rescheduledTask")
26-
WorkmanagerPlugin.registerTask(withIdentifier: "be.tramckrijte.workmanagerExample.failedTask")
27-
WorkmanagerPlugin.registerTask(withIdentifier: "be.tramckrijte.workmanagerExample.simpleDelayedTask")
28-
WorkmanagerPlugin.registerTask(withIdentifier: "be.tramckrijte.workmanagerExample.simplePeriodicTask")
29-
WorkmanagerPlugin.registerTask(withIdentifier: "be.tramckrijte.workmanagerExample.simplePeriodic1HourTask")
23+
WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "be.tramckrijte.workmanagerExample.taskId")
24+
WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "be.tramckrijte.workmanagerExample.rescheduledTask")
25+
WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "be.tramckrijte.workmanagerExample.simpleDelayedTask")
26+
WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "be.tramckrijte.workmanagerExample.iOSBackgroundProcessingTask")
27+
28+
// When this task is scheduled from dart it will run with minimum 20 minute frequency. The
29+
// frequency is not guaranteed rather iOS will schedule it as per user's App usage pattern.
30+
// If frequency is not provided it will default to 15 minutes
31+
WorkmanagerPlugin.registerPeriodicTask(withIdentifier: "be.tramckrijte.workmanagerExample.iOSBackgroundAppRefresh", frequency: NSNumber(value: 20 * 60))
3032

3133
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
3234

Diff for: example/ios/Runner/Info.plist

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
<string>be.tramckrijte.workmanagerExample.simpleDelayedTask</string>
1212
<string>be.tramckrijte.workmanagerExample.simplePeriodicTask</string>
1313
<string>be.tramckrijte.workmanagerExample.simplePeriodic1HourTask</string>
14+
<string>be.tramckrijte.workmanagerExample.iOSBackgroundAppRefresh</string>
15+
<string>be.tramckrijte.workmanagerExample.iOSBackgroundProcessingTask</string>
1416
</array>
1517
<key>CFBundleDevelopmentRegion</key>
1618
<string>$(DEVELOPMENT_LANGUAGE)</string>

0 commit comments

Comments
 (0)