Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

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

Merged
merged 35 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b74dc93
fix:Update Workmanager iOS because no callback in Background on iOS r…
xunreal75 Oct 26, 2022
f7c1cef
added permissionhandler an requests for iOS
xunreal75 Nov 5, 2022
1d773de
fixed errormessage on xcode
xunreal75 Nov 5, 2022
8419017
feat:Added check for background refresh permissions #441
xunreal75 Nov 7, 2022
423f054
Merge branch 'fluttercommunity:main' into iOS_notWorkingUpdate
xunreal75 Nov 7, 2022
3b133fd
text to display task event dates (show prefs) added.
xunreal75 Jan 2, 2023
27726a8
fixed workmanager iOS Part
xunreal75 Jan 11, 2023
5589538
fixed warning dead code and ! check
xunreal75 Jan 11, 2023
6a42b10
improved Task description (hints)
xunreal75 Jan 12, 2023
c3f985f
Merge pull request #1 from xunreal75/iOS_notWorkingUpdate
iosephmagno Jan 14, 2023
52deef2
Merge pull request #2 from fluttercommunity/main
delfme Jul 22, 2023
c9f193f
Update README.md
delfme Jul 29, 2023
808e193
Update README.md
delfme Jul 29, 2023
0c39308
Update README.md
delfme Jul 29, 2023
08bd327
Merge pull request #1 from presence-app/main
absar Sep 11, 2023
8d184da
Format readme iOS examples
absar Sep 14, 2023
b06db87
Improve code documentation
absar Sep 18, 2023
7f807fe
Cleanups in SwiftWorkmanagerPlugin.swift
absar Sep 18, 2023
2fb38eb
* iOS, Rename registeriOSBackgroundProcessingTask to a generic name r…
absar Sep 18, 2023
2af1f55
* Cleanup code to make it more close to original plugin so that chang…
absar Sep 20, 2023
3558258
Add task identifiers to iOS AppRefresh and ProcessingTask so that use…
absar Sep 20, 2023
43df000
* iOS AppRefresh task interval should be 15 minutes
absar Sep 20, 2023
1bd6a6b
Initialize should not auto open App settings if background refresh pe…
absar Sep 21, 2023
222bdad
Continue work on task identifiers for iOS AppRefresh and ProcessingTask.
absar Sep 22, 2023
5d9ce95
Temporary merge branch 'ios-BGTaskScheduler-enhancements' into ios-ta…
absar Sep 23, 2023
9652d83
Merge pull request #2 from fluttercommunity/main
absar Sep 23, 2023
39aad70
Merge iOS enhancements into latest workmanager main branch
absar Sep 24, 2023
7c256ae
Fix extra commas on iOS
absar Sep 24, 2023
8a6f560
New iOS feature printScheduledTasks to print details of un-executed s…
absar Sep 26, 2023
9625658
iOS Periodic and processing tasks will be immediately scheduled, inst…
absar Sep 26, 2023
196bad2
Option to set frequency for iOS periodic tasks in AppDelegate.swift
absar Sep 27, 2023
5a8cbf2
Update iOS docs
absar Sep 27, 2023
c59058d
TODO for cleanups later
absar Sep 27, 2023
03e1edb
Branch all iOS developments to prepare merging to original workmanager
absar Sep 27, 2023
f3e717b
Sync with master
absar Oct 27, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions IOS_SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ This plugin is compatible with **Swift 4.2** and up. Make sure you are using **X
> ⚠️ 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:
>
> 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.
>
> Workmanager BGTaskScheduler methods `registerOneOffTask`, `registerPeriodicTask`, and `registerProcessingTask` are only available on iOS 13+

![Screenshot of Background Fetch Capabilities tab in Xcode ](.art/ios_background_mode_background_processing.png)

Expand All @@ -19,6 +21,9 @@ This will add the **UIBackgroundModes** key to your project's `Info.plist`:
<key>UIBackgroundModes</key>
<array>
<string>processing</string>

<!-- If you need periodic tasks in iOS 13+ you need to enable Background Fetch as well -->
<string>fetch</string>
</array>
```

Expand All @@ -31,16 +36,25 @@ import workmanager

``` swift
// In AppDelegate.application method
WorkmanagerPlugin.registerTask(withIdentifier: "task-identifier")
WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "task-identifier")

// Register a periodic task in iOS 13+
WorkmanagerPlugin.registerPeriodicTask(withIdentifier: "be.tramckrijte.workmanagerExample.iOSBackgroundAppRefresh", frequency: NSNumber(value: 20 * 60))
```

- Info.plist
``` xml
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>task-identifier</string>
</array>
<array>
<string>task-identifier</string>

<!-- Register a periodic task in iOS 13+ -->
<string>be.tramckrijte.workmanagerExample.iOSBackgroundAppRefresh</string>
</array>
```
> ⚠️ On iOS 13+, adding a `BGTaskSchedulerPermittedIdentifiers` key to the Info.plist for new `BGTaskScheduler` API disables the `performFetchWithCompletionHandler` and `setMinimumBackgroundFetchInterval`
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.
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)

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

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

## Enabling Background Fetch

> ⚠️ 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)
> ⚠️ 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

> ⚠️ On iOS 13+, adding a `BGTaskSchedulerPermittedIdentifiers` key to the Info.plist for new `BGTaskScheduler` API disables the `performFetchWithCompletionHandler` and `setMinimumBackgroundFetchInterval`
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.
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)

Background fetching is very different compared to Android's Background Jobs.
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*:
Expand Down
106 changes: 100 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ void callbackDispatcher() {
```

Android tasks are identified using their `taskName`.
iOS tasks are identitied using their `taskIdentifier`.
iOS tasks are identified using their `taskIdentifier`.

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

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

# iOS specific setup and note

iOS supports **One off tasks** with a few basic constraints:
Initialize Workmanager only once.
Background app refresh can only be tested on a real device, it cannot be tested on a simulator.

### Migrate to 0.6.x
Version 0.6.x of this plugin 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
1. 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

### One off tasks
iOS supports **One off tasks** only on iOS 13+ with a few basic constraints:

`registerOneOffTask` starts immediately. It might run for only 30 seconds due to iOS restrictions.

```dart
Workmanager().registerOneOffTask(
"task-identifier",
simpleTaskKey, // Ignored on iOS
initialDelay: Duration(minutes: 30),
initialDelay: Duration(minutes: 30), // Ignored on iOS
inputData: ... // fully supported
);
```

### Periodic tasks
iOS supports two types of **Periodic tasks**:
- On iOS 12 and lower you can use deprecated Background Fetch API, see [iOS Setup](./IOS_SETUP.md), even though the API is
deprecated by iOS it still works on iOS 13+ as of writing this article

- `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.

> ⚠️ On iOS 13+, adding a `BGTaskSchedulerPermittedIdentifiers` key to the Info.plist for new `BGTaskScheduler` API disables the `performFetchWithCompletionHandler` and `setMinimumBackgroundFetchInterval`
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.
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)

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.

```objc
// Register a periodic task with 20 minutes frequency. The frequency is in seconds.
WorkmanagerPlugin.registerPeriodicTask(withIdentifier: "be.tramckrijte.workmanagerExample.iOSBackgroundAppRefresh", frequency: NSNumber(value: 20 * 60))
```

Then schedule the task from your App
```dart
const iOSBackgroundAppRefresh = "be.tramckrijte.workmanagerExample.iOSBackgroundAppRefresh";
Workmanager().registerPeriodicTask(
iOSBackgroundAppRefresh,
iOSBackgroundAppRefresh,
initialDelay: Duration(seconds: 10),
frequency: Duration(hours: 1), // Ignored on iOS, rather set in AppDelegate.swift
inputData: ... // Not supported
);
```

For more information see [BGAppRefreshTask](https://developer.apple.com/documentation/backgroundtasks/bgapprefreshtask)

### Processing tasks
iOS supports **Processing tasks** only on iOS 13+ which can run for more than 30 seconds.

`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.
Processing tasks are for long processes like data processing and app maintenance. Processing tasks can run for minutes, but the system can interrupt these.
iOS might terminate any running background processing tasks when the user starts using the device.
For more information see [BGProcessingTask](https://developer.apple.com/documentation/backgroundtasks/bgprocessingtask)

```dart
const iOSBackgroundProcessingTask = "be.tramckrijte.workmanagerExample.iOSBackgroundProcessingTask";
Workmanager().registerProcessingTask(
iOSBackgroundProcessingTask,
iOSBackgroundProcessingTask,
initialDelay: Duration(minutes: 2),
constraints: Constraints(
// connected or metered mark the task as requiring internet
// Connected or metered mark the task as requiring internet
networkType: NetworkType.connected,
// require external power
// Require external power
requiresCharging: true,
),
inputData: ... // fully supported
);
```

### 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. If it is not enabled you might ask
the user to enable it in app settings.

```dart
if (Platform.isIOS) {
final hasPermission = await Workmanager().checkBackgroundRefreshPermission();
if (hasPermission != BackgroundRefreshPermissionState.available){
// Inform the user that background app refresh is disabled
}
}
```

For more information see the [BGTaskScheduler documentation](https://developer.apple.com/documentation/backgroundtasks).

### Print scheduled tasks
On iOS you can print scheduled tasks using `Workmanager.printScheduledTasks`

It prints task details to console. To be used during development/debugging.
Currently only supported on iOS and only on iOS 13+.

```dart
if (Platform.isIOS) {
Workmanager().printScheduledTasks();
// Prints: [BGTaskScheduler] Task Identifier: iOSBackgroundAppRefresh earliestBeginDate: 2023.10.10 PM 11:10:12
// Or: [BGTaskScheduler] There are no scheduled tasks
}
```


# Customisation (Android)

Not every `Android WorkManager` feature is ported.
Expand Down
6 changes: 3 additions & 3 deletions example/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_VERSION = 4.2;
Expand Down Expand Up @@ -577,7 +577,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
Expand Down Expand Up @@ -628,7 +628,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
Expand Down
16 changes: 9 additions & 7 deletions example/ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ import workmanager
GeneratedPluginRegistrant.register(with: registry)
}

WorkmanagerPlugin.registerTask(withIdentifier: "be.tramckrijte.workmanagerExample.taskId")
WorkmanagerPlugin.registerTask(withIdentifier: "be.tramckrijte.workmanagerExample.simpleTask")
WorkmanagerPlugin.registerTask(withIdentifier: "be.tramckrijte.workmanagerExample.rescheduledTask")
WorkmanagerPlugin.registerTask(withIdentifier: "be.tramckrijte.workmanagerExample.failedTask")
WorkmanagerPlugin.registerTask(withIdentifier: "be.tramckrijte.workmanagerExample.simpleDelayedTask")
WorkmanagerPlugin.registerTask(withIdentifier: "be.tramckrijte.workmanagerExample.simplePeriodicTask")
WorkmanagerPlugin.registerTask(withIdentifier: "be.tramckrijte.workmanagerExample.simplePeriodic1HourTask")
WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "be.tramckrijte.workmanagerExample.taskId")
WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "be.tramckrijte.workmanagerExample.rescheduledTask")
WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "be.tramckrijte.workmanagerExample.simpleDelayedTask")
WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "be.tramckrijte.workmanagerExample.iOSBackgroundProcessingTask")

// When this task is scheduled from dart it will run with minimum 20 minute frequency. The
// frequency is not guaranteed rather iOS will schedule it as per user's App usage pattern.
// If frequency is not provided it will default to 15 minutes
WorkmanagerPlugin.registerPeriodicTask(withIdentifier: "be.tramckrijte.workmanagerExample.iOSBackgroundAppRefresh", frequency: NSNumber(value: 20 * 60))

return super.application(application, didFinishLaunchingWithOptions: launchOptions)

Expand Down
2 changes: 2 additions & 0 deletions example/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
<string>be.tramckrijte.workmanagerExample.simpleDelayedTask</string>
<string>be.tramckrijte.workmanagerExample.simplePeriodicTask</string>
<string>be.tramckrijte.workmanagerExample.simplePeriodic1HourTask</string>
<string>be.tramckrijte.workmanagerExample.iOSBackgroundAppRefresh</string>
<string>be.tramckrijte.workmanagerExample.iOSBackgroundProcessingTask</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
Expand Down
Loading
Loading