Skip to content

Commit 61a7b02

Browse files
committed
fix(ios): pass inputData to periodic background tasks
- Store inputData in UserDefaults when registering periodic tasks - Retrieve and pass stored inputData when executing periodic tasks - Fix issue where periodic tasks received nil inputData instead of provided data - Update documentation with periodic task inputData examples Closes #612
1 parent 7f4f870 commit 61a7b02

File tree

4 files changed

+105
-17
lines changed

4 files changed

+105
-17
lines changed

docs/quickstart.mdx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,19 @@ Workmanager().registerPeriodicTask(
176176
"cleanup",
177177
frequency: Duration(hours: 24),
178178
);
179+
180+
// Schedule a periodic task with input data
181+
Workmanager().registerPeriodicTask(
182+
"sync-task",
183+
"data_sync",
184+
frequency: Duration(hours: 6),
185+
inputData: <String, dynamic>{
186+
'server_url': 'https://api.example.com',
187+
'sync_type': 'full',
188+
'max_retries': 3,
189+
},
190+
);
191+
```
179192
```
180193
181194
## Task Results
@@ -200,4 +213,4 @@ Your background tasks can return:
200213
201214
- **[Task Customization](customization)** - Advanced configuration with constraints, input data, and management
202215
- **[Debugging Guide](debugging)** - Learn how to debug and troubleshoot background tasks
203-
- **[Example App](https://github.com/fluttercommunity/flutter_workmanager/tree/main/example)** - Complete working demo
216+
- **[Example App](https://github.com/fluttercommunity/flutter_workmanager/tree/main/example)** - Complete working demo

example/ios/Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ EXTERNAL SOURCES:
3838
SPEC CHECKSUMS:
3939
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
4040
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
41-
path_provider_foundation: 608fcb11be570ce83519b076ab6a1fffe2474f05
41+
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
4242
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
4343
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
4444
workmanager_apple: 904529ae31e97fc5be632cf628507652294a0778

workmanager_apple/ios/Sources/workmanager_apple/UserDefaultsHelper.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,15 @@ struct UserDefaultsHelper {
1515

1616
enum Key {
1717
case callbackHandle
18+
case periodicTaskInputData(taskIdentifier: String)
1819

1920
var stringValue: String {
20-
return "\(WorkmanagerPlugin.identifier).\(self)"
21+
switch self {
22+
case .callbackHandle:
23+
return "\(WorkmanagerPlugin.identifier).callbackHandle"
24+
case .periodicTaskInputData(let taskIdentifier):
25+
return "\(WorkmanagerPlugin.identifier).periodicTaskInputData.\(taskIdentifier)"
26+
}
2127
}
2228
}
2329

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

40+
// MARK: periodicTaskInputData
41+
42+
static func storePeriodicTaskInputData(_ inputData: [String: Any]?, forTaskIdentifier taskIdentifier: String) {
43+
store(inputData, key: .periodicTaskInputData(taskIdentifier: taskIdentifier))
44+
}
45+
46+
static func getStoredPeriodicTaskInputData(forTaskIdentifier taskIdentifier: String) -> [String: Any]? {
47+
return getValue(for: .periodicTaskInputData(taskIdentifier: taskIdentifier))
48+
}
49+
3450
// MARK: Private helper functions
3551

3652
private static func store<T>(_ value: T, key: Key) {

workmanager_apple/ios/Sources/workmanager_apple/WorkmanagerPlugin.swift

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,32 @@ public class WorkmanagerPlugin: FlutterPluginAppLifeCycleDelegate, FlutterPlugin
3232
operationQueue.addOperation(operation)
3333
}
3434

35+
/// Handles execution of a periodic background task.
36+
///
37+
/// This method is called by iOS when a BGAppRefreshTask is triggered.
38+
/// It retrieves stored inputData and executes the Flutter task.
39+
///
40+
/// - Parameters:
41+
/// - identifier: Task identifier
42+
/// - task: The BGAppRefreshTask instance from iOS
43+
/// - earliestBeginInSeconds: Optional delay before scheduling next occurrence
44+
/// - inputData: Input data passed from the Dart side (may be nil)
3545
@available(iOS 13.0, *)
36-
public static func handlePeriodicTask(identifier: String, task: BGAppRefreshTask, earliestBeginInSeconds: Double?) {
46+
public static func handlePeriodicTask(identifier: String, task: BGAppRefreshTask, earliestBeginInSeconds: NSNumber?, inputData: [String: Any]?) {
3747
guard let callbackHandle = UserDefaultsHelper.getStoredCallbackHandle(),
3848
let _ = FlutterCallbackCache.lookupCallbackInformation(callbackHandle)
3949
else {
4050
logError("[\(String(describing: self))] \(WMPError.workmanagerNotInitialized.message)")
4151
return
4252
}
4353

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

4757
let operationQueue = OperationQueue()
4858
let operation = createBackgroundOperation(
4959
identifier: task.identifier,
50-
inputData: nil,
60+
inputData: inputData,
5161
backgroundMode: .backgroundPeriodicTask(identifier: identifier)
5262
)
5363

@@ -57,6 +67,13 @@ public class WorkmanagerPlugin: FlutterPluginAppLifeCycleDelegate, FlutterPlugin
5767
operationQueue.addOperation(operation)
5868
}
5969

70+
/// Starts a one-off background task with the specified input data.
71+
///
72+
/// - Parameters:
73+
/// - identifier: Task identifier
74+
/// - taskIdentifier: iOS background task identifier for lifecycle management
75+
/// - inputData: Input data to pass to the Flutter task
76+
/// - delaySeconds: Delay before task execution
6077
@available(iOS 13.0, *)
6178
public static func startOneOffTask(identifier: String, taskIdentifier: UIBackgroundTaskIdentifier, inputData: [String: Any]?, delaySeconds: Int64) {
6279
let operationQueue = OperationQueue()
@@ -70,38 +87,67 @@ public class WorkmanagerPlugin: FlutterPluginAppLifeCycleDelegate, FlutterPlugin
7087
operationQueue.addOperation(operation)
7188
}
7289

90+
/// Registers a periodic background task with iOS BGTaskScheduler.
91+
///
92+
/// This method must be called during app initialization (typically in AppDelegate)
93+
/// to register the task identifier with iOS. The actual task scheduling with inputData
94+
/// happens later when called from the Dart/Flutter side.
95+
///
96+
/// - Parameters:
97+
/// - identifier: Unique task identifier that matches the one used in Dart
98+
/// - earliestBeginInSeconds: Optional delay before scheduling next occurrence
99+
///
100+
/// - Note: This registers the task handler only. Use Workmanager.registerPeriodicTask()
101+
/// from Dart to actually schedule the task with inputData.
73102
@objc
74-
public static func registerPeriodicTask(withIdentifier identifier: String, frequency: NSNumber?) {
103+
public static func registerPeriodicTask(withIdentifier identifier: String, earliestBeginInSeconds: NSNumber? = nil) {
75104
if #available(iOS 13.0, *) {
76-
var frequencyInSeconds: Double?
77-
if let frequencyValue = frequency {
78-
frequencyInSeconds = frequencyValue.doubleValue
79-
}
80-
81105
BGTaskScheduler.shared.register(
82106
forTaskWithIdentifier: identifier,
83107
using: nil
84108
) { task in
85109
if let task = task as? BGAppRefreshTask {
86-
handlePeriodicTask(identifier: identifier, task: task, earliestBeginInSeconds: frequencyInSeconds)
110+
// Retrieve the stored inputData for this periodic task
111+
let storedInputData = UserDefaultsHelper.getStoredPeriodicTaskInputData(forTaskIdentifier: task.identifier)
112+
handlePeriodicTask(identifier: identifier, task: task, earliestBeginInSeconds: earliestBeginInSeconds, inputData: storedInputData)
87113
}
88114
}
89115
}
90116
}
91117

118+
/// Registers a periodic background task with iOS BGTaskScheduler.
119+
///
120+
/// - Parameters:
121+
/// - identifier: Unique task identifier that matches the one used in Dart
122+
/// - frequency: Frequency hint in seconds (deprecated, use earliestBeginInSeconds instead)
123+
///
124+
/// - Note: Deprecated. Use registerPeriodicTask(withIdentifier:frequency:earliestBeginInSeconds:) instead.
125+
@available(*, deprecated, message: "Use registerPeriodicTask(withIdentifier:earliestBeginInSeconds:) instead")
92126
@objc
127+
public static func registerPeriodicTask(withIdentifier identifier: String, frequency: NSNumber?) {
128+
registerPeriodicTask(withIdentifier: identifier, earliestBeginInSeconds: frequency)
129+
}
130+
93131
@available(iOS 13.0, *)
94-
private static func schedulePeriodicTask(taskIdentifier identifier: String, earliestBeginInSeconds begin: Double) {
132+
private static func schedulePeriodicTask(taskIdentifier identifier: String, earliestBeginInSeconds begin: Double?) {
95133
let request = BGAppRefreshTaskRequest(identifier: identifier)
96-
request.earliestBeginDate = Date(timeIntervalSinceNow: begin)
134+
if let begin = begin {
135+
request.earliestBeginDate = Date(timeIntervalSinceNow: begin)
136+
}
97137
do {
98138
try BGTaskScheduler.shared.submit(request)
99-
logInfo("BGAppRefreshTask submitted \(identifier) earliestBeginInSeconds:\(begin)")
139+
logInfo("BGAppRefreshTask submitted \(identifier) earliestBeginInSeconds:\(String(describing: begin))")
100140
} catch {
101141
logInfo("Could not schedule BGAppRefreshTask \(error.localizedDescription)")
102142
}
103143
}
104144

145+
/// Registers a background processing task with iOS BGTaskScheduler.
146+
///
147+
/// This method must be called during app initialization (typically in AppDelegate)
148+
/// to register the task identifier with iOS for background processing tasks.
149+
///
150+
/// - Parameter identifier: Unique task identifier that matches the one used in Dart
105151
@objc
106152
public static func registerBGProcessingTask(withIdentifier identifier: String) {
107153
if #available(iOS 13.0, *) {
@@ -140,6 +186,12 @@ public class WorkmanagerPlugin: FlutterPluginAppLifeCycleDelegate, FlutterPlugin
140186

141187
// MARK: - FlutterPlugin conformance
142188

189+
/// Sets the plugin registrant callback for background task execution.
190+
///
191+
/// This callback is used to register additional plugins when background tasks
192+
/// run in a separate Flutter engine instance.
193+
///
194+
/// - Parameter callback: The callback to register plugins in the background engine
143195
@objc
144196
public static func setPluginRegistrantCallback(_ callback: @escaping FlutterPluginRegistrantCallback) {
145197
flutterPluginRegistrantCallback = callback
@@ -191,6 +243,13 @@ public class WorkmanagerPlugin: FlutterPluginAppLifeCycleDelegate, FlutterPlugin
191243

192244
executeIfSupportedVoid(completion: completion, feature: "PeriodicTask") {
193245
let initialDelaySeconds = Double(request.initialDelaySeconds ?? 0)
246+
247+
// Store the inputData for later retrieval when the task executes
248+
UserDefaultsHelper.storePeriodicTaskInputData(
249+
request.inputData as? [String: Any],
250+
forTaskIdentifier: request.uniqueName
251+
)
252+
194253
WorkmanagerPlugin.schedulePeriodicTask(
195254
taskIdentifier: request.uniqueName,
196255
earliestBeginInSeconds: initialDelaySeconds

0 commit comments

Comments
 (0)