Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 14 additions & 1 deletion docs/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,19 @@ Workmanager().registerPeriodicTask(
"cleanup",
frequency: Duration(hours: 24),
);

// Schedule a periodic task with input data
Workmanager().registerPeriodicTask(
"sync-task",
"data_sync",
frequency: Duration(hours: 6),
inputData: <String, dynamic>{
'server_url': 'https://api.example.com',
'sync_type': 'full',
'max_retries': 3,
},
);
```
```

## Task Results
Expand All @@ -200,4 +213,4 @@ Your background tasks can return:

- **[Task Customization](customization)** - Advanced configuration with constraints, input data, and management
- **[Debugging Guide](debugging)** - Learn how to debug and troubleshoot background tasks
- **[Example App](https://github.com/fluttercommunity/flutter_workmanager/tree/main/example)** - Complete working demo
- **[Example App](https://github.com/fluttercommunity/flutter_workmanager/tree/main/example)** - Complete working demo
2 changes: 1 addition & 1 deletion example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
path_provider_foundation: 608fcb11be570ce83519b076ab6a1fffe2474f05
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
workmanager_apple: 904529ae31e97fc5be632cf628507652294a0778
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@ struct UserDefaultsHelper {

enum Key {
case callbackHandle
case periodicTaskInputData(taskIdentifier: String)

var stringValue: String {
return "\(WorkmanagerPlugin.identifier).\(self)"
switch self {
case .callbackHandle:
return "\(WorkmanagerPlugin.identifier).callbackHandle"
case .periodicTaskInputData(let taskIdentifier):
return "\(WorkmanagerPlugin.identifier).periodicTaskInputData.\(taskIdentifier)"
}
}
}

Expand All @@ -31,6 +37,16 @@ struct UserDefaultsHelper {
return getValue(for: .callbackHandle)
}

// MARK: periodicTaskInputData

static func storePeriodicTaskInputData(_ inputData: [String: Any]?, forTaskIdentifier taskIdentifier: String) {
store(inputData, key: .periodicTaskInputData(taskIdentifier: taskIdentifier))
}

static func getStoredPeriodicTaskInputData(forTaskIdentifier taskIdentifier: String) -> [String: Any]? {
return getValue(for: .periodicTaskInputData(taskIdentifier: taskIdentifier))
}

// MARK: Private helper functions

private static func store<T>(_ value: T, key: Key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,31 +32,55 @@
operationQueue.addOperation(operation)
}

/// Handles execution of a periodic background task.
///
/// This method is called by iOS when a BGAppRefreshTask is triggered.
/// It retrieves stored inputData and executes the Flutter task.
///
/// - Parameters:
/// - identifier: Task identifier
/// - task: The BGAppRefreshTask instance from iOS
/// - earliestBeginInSeconds: Optional delay before scheduling next occurrence
/// - inputData: Input data passed from the Dart side (may be nil)
@available(iOS 13.0, *)
public static func handlePeriodicTask(identifier: String, task: BGAppRefreshTask, earliestBeginInSeconds: Double?) {
public static func handlePeriodicTask(identifier: String, task: BGAppRefreshTask, earliestBeginInSeconds: NSNumber?, inputData: [String: Any]?) {

Check warning on line 46 in workmanager_apple/ios/Sources/workmanager_apple/WorkmanagerPlugin.swift

View workflow job for this annotation

GitHub Actions / format_swift

Line Length Violation: Line should be 120 characters or less; currently it has 149 characters (line_length)
guard let callbackHandle = UserDefaultsHelper.getStoredCallbackHandle(),
let _ = FlutterCallbackCache.lookupCallbackInformation(callbackHandle)
else {
logError("[\(String(describing: self))] \(WMPError.workmanagerNotInitialized.message)")
return
}

// If frequency is not provided it will default to 15 minutes
schedulePeriodicTask(taskIdentifier: task.identifier, earliestBeginInSeconds: earliestBeginInSeconds ?? (15 * 60))
// Schedule the next occurrence (iOS will determine actual timing based on usage patterns)
schedulePeriodicTask(taskIdentifier: task.identifier, earliestBeginInSeconds: earliestBeginInSeconds?.doubleValue)

Check warning on line 55 in workmanager_apple/ios/Sources/workmanager_apple/WorkmanagerPlugin.swift

View workflow job for this annotation

GitHub Actions / format_swift

Line Length Violation: Line should be 120 characters or less; currently it has 122 characters (line_length)

let operationQueue = OperationQueue()
let operation = createBackgroundOperation(
identifier: task.identifier,
inputData: nil,
backgroundMode: .backgroundPeriodicTask(identifier: identifier)
// Execute the Flutter task directly
let worker = BackgroundWorker(
mode: .backgroundPeriodicTask(identifier: identifier),
inputData: inputData,
flutterPluginRegistrantCallback: flutterPluginRegistrantCallback
)

task.expirationHandler = { operation.cancel() }
operation.completionBlock = { task.setTaskCompleted(success: !operation.isCancelled) }
// Set up expiration handler
task.expirationHandler = {
logInfo("BGAppRefreshTask expired: \(identifier)")
}

operationQueue.addOperation(operation)
// Execute on main thread (required for Flutter)
DispatchQueue.main.async {
worker.performBackgroundRequest { result in
task.setTaskCompleted(success: result == .newData)
}
}
}

/// Starts a one-off background task with the specified input data.
///
/// - Parameters:
/// - identifier: Task identifier
/// - taskIdentifier: iOS background task identifier for lifecycle management
/// - inputData: Input data to pass to the Flutter task
/// - delaySeconds: Delay before task execution
@available(iOS 13.0, *)
public static func startOneOffTask(identifier: String, taskIdentifier: UIBackgroundTaskIdentifier, inputData: [String: Any]?, delaySeconds: Int64) {
let operationQueue = OperationQueue()
Expand All @@ -70,38 +94,67 @@
operationQueue.addOperation(operation)
}

/// Registers a periodic background task with iOS BGTaskScheduler.
///
/// This method must be called during app initialization (typically in AppDelegate)
/// to register the task identifier with iOS. The actual task scheduling with inputData
/// happens later when called from the Dart/Flutter side.
///
/// - Parameters:
/// - identifier: Unique task identifier that matches the one used in Dart
/// - earliestBeginInSeconds: Optional delay before scheduling next occurrence
///
/// - Note: This registers the task handler only. Use Workmanager.registerPeriodicTask()
/// from Dart to actually schedule the task with inputData.
@objc
public static func registerPeriodicTask(withIdentifier identifier: String, frequency: NSNumber?) {
public static func registerPeriodicTask(withIdentifier identifier: String, earliestBeginInSeconds: NSNumber? = nil) {
if #available(iOS 13.0, *) {
var frequencyInSeconds: Double?
if let frequencyValue = frequency {
frequencyInSeconds = frequencyValue.doubleValue
}

BGTaskScheduler.shared.register(
forTaskWithIdentifier: identifier,
using: nil
) { task in
if let task = task as? BGAppRefreshTask {
handlePeriodicTask(identifier: identifier, task: task, earliestBeginInSeconds: frequencyInSeconds)
// Retrieve the stored inputData for this periodic task
let storedInputData = UserDefaultsHelper.getStoredPeriodicTaskInputData(forTaskIdentifier: task.identifier)
handlePeriodicTask(identifier: identifier, task: task, earliestBeginInSeconds: earliestBeginInSeconds, inputData: storedInputData)
}
}
}
}

/// Registers a periodic background task with iOS BGTaskScheduler.
///
/// - Parameters:
/// - identifier: Unique task identifier that matches the one used in Dart
/// - frequency: Frequency hint in seconds (deprecated, use earliestBeginInSeconds instead)
///
/// - Note: Deprecated. Use registerPeriodicTask(withIdentifier:frequency:earliestBeginInSeconds:) instead.
@available(*, deprecated, message: "Use registerPeriodicTask(withIdentifier:earliestBeginInSeconds:) instead")
@objc
public static func registerPeriodicTask(withIdentifier identifier: String, frequency: NSNumber?) {
registerPeriodicTask(withIdentifier: identifier, earliestBeginInSeconds: frequency)
}

@available(iOS 13.0, *)
private static func schedulePeriodicTask(taskIdentifier identifier: String, earliestBeginInSeconds begin: Double) {
private static func schedulePeriodicTask(taskIdentifier identifier: String, earliestBeginInSeconds begin: Double?) {
let request = BGAppRefreshTaskRequest(identifier: identifier)
request.earliestBeginDate = Date(timeIntervalSinceNow: begin)
if let begin = begin {
request.earliestBeginDate = Date(timeIntervalSinceNow: begin)
}
do {
try BGTaskScheduler.shared.submit(request)
logInfo("BGAppRefreshTask submitted \(identifier) earliestBeginInSeconds:\(begin)")
logInfo("BGAppRefreshTask submitted \(identifier) earliestBeginInSeconds:\(String(describing: begin))")
} catch {
logInfo("Could not schedule BGAppRefreshTask \(error.localizedDescription)")
}
}

/// Registers a background processing task with iOS BGTaskScheduler.
///
/// This method must be called during app initialization (typically in AppDelegate)
/// to register the task identifier with iOS for background processing tasks.
///
/// - Parameter identifier: Unique task identifier that matches the one used in Dart
@objc
public static func registerBGProcessingTask(withIdentifier identifier: String) {
if #available(iOS 13.0, *) {
Expand Down Expand Up @@ -140,6 +193,12 @@

// MARK: - FlutterPlugin conformance

/// Sets the plugin registrant callback for background task execution.
///
/// This callback is used to register additional plugins when background tasks
/// run in a separate Flutter engine instance.
///
/// - Parameter callback: The callback to register plugins in the background engine
@objc
public static func setPluginRegistrantCallback(_ callback: @escaping FlutterPluginRegistrantCallback) {
flutterPluginRegistrantCallback = callback
Expand Down Expand Up @@ -191,6 +250,13 @@

executeIfSupportedVoid(completion: completion, feature: "PeriodicTask") {
let initialDelaySeconds = Double(request.initialDelaySeconds ?? 0)

// Store the inputData for later retrieval when the task executes
UserDefaultsHelper.storePeriodicTaskInputData(
request.inputData as? [String: Any],
forTaskIdentifier: request.uniqueName
)

WorkmanagerPlugin.schedulePeriodicTask(
taskIdentifier: request.uniqueName,
earliestBeginInSeconds: initialDelaySeconds
Expand Down Expand Up @@ -383,4 +449,4 @@

return worker.performBackgroundRequest(completionHandler)
}
}

Check warning on line 452 in workmanager_apple/ios/Sources/workmanager_apple/WorkmanagerPlugin.swift

View workflow job for this annotation

GitHub Actions / format_swift

File Length Violation: File should contain 400 lines or less: currently contains 452 (file_length)
Loading