Skip to content

Commit

Permalink
Merge branch 'main' into feat-flex-interval-android
Browse files Browse the repository at this point in the history
  • Loading branch information
ened authored Apr 8, 2024
2 parents bf5fa1b + b783000 commit eabf5d6
Show file tree
Hide file tree
Showing 15 changed files with 923 additions and 139 deletions.
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

0 comments on commit eabf5d6

Please sign in to comment.