Skip to content

Commit fcb6452

Browse files
enedclaude
andcommitted
feat: Migrate to federated plugin architecture for v0.8.0
BREAKING CHANGES: - Plugin now uses federated architecture with separate platform packages - Platform-specific implementations moved to dedicated packages - Main workmanager package depends on platform interface and implementations New packages: - workmanager_platform_interface: Shared interface for all implementations - workmanager_android: Android-specific WorkManager implementation - workmanager_ios: iOS-specific BGTaskScheduler implementation Benefits: - Foundation for macOS support using NSBackgroundActivityScheduler - Platform experts can contribute to specific implementations - Better modular development and testing - Independent platform package releases This architectural change sets up the plugin for easy platform expansion while maintaining all existing functionality and incorporating community bugfixes. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent e1b44b0 commit fcb6452

43 files changed

Lines changed: 3547 additions & 11 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

melos.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
name: workmanager
22
packages:
33
- workmanager
4+
- workmanager_platform_interface
5+
- workmanager_android
6+
- workmanager_ios
47
- example
58
scripts:
69
get: melos exec -- dart pub get

workmanager/CHANGELOG.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
# 0.7.1
2-
1+
# 0.8.0
2+
3+
* **BREAKING**: Migrate to federated plugin architecture for better platform extensibility
4+
* **BREAKING**: Platform-specific implementations moved to separate packages
5+
* Create `workmanager_platform_interface` for shared platform interface
6+
* Create `workmanager_android` package with Android WorkManager implementation
7+
* Create `workmanager_ios` package with iOS BGTaskScheduler implementation
8+
* Foundation for future macOS support using NSBackgroundActivityScheduler
39
* Android: Fix v2 embedding import in BackgroundWorker by @jogapps (from PR #595)
410
* Android: Fix documentation formatting and typo in BackgroundWorker by @jogapps (from PR #595)
511
* iOS: Fix swapped constraints bug for requiresNetworkConnectivity and requiresExternalPower by @thegriffen (from PR #562)
612
* iOS: Add Privacy Manifest for App Store compliance by @navaronbracke (from PR #555)
713
* iOS: Replace print statements with proper os_log for better logging
814
* iOS: printScheduledTasks now returns String instead of void by @yarith28 (from PR #585)
9-
* iOS: Prepare for Swift Package Manager support (requires proper implementation without file duplication)
1015

1116
# 0.7.0
1217

workmanager/lib/src/workmanager.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import 'dart:ui';
44

55
import 'package:flutter/services.dart';
66
import 'package:flutter/widgets.dart';
7-
8-
import 'options.dart';
7+
import 'package:workmanager_platform_interface/workmanager_platform_interface.dart';
98

109
/// Function that executes your background work.
1110
/// You should return whether the task ran successfully or not.
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
import 'dart:async';
2+
import 'dart:ui';
3+
4+
import 'package:flutter/services.dart';
5+
import 'package:flutter/widgets.dart';
6+
import 'package:workmanager_platform_interface/workmanager_platform_interface.dart';
7+
8+
/// Function that executes your background work.
9+
/// You should return whether the task ran successfully or not.
10+
///
11+
/// [taskName] Returns the value you provided when registering the task.
12+
/// iOS will pass [Workmanager.iOSBackgroundTask] (for background-fetch) or
13+
/// custom task IDs for BGTaskScheduler based tasks.
14+
///
15+
/// The behavior for retries is different on each platform:
16+
/// - Android: return `false` from the this method will reschedule the work
17+
/// based on the policy given in [Workmanager.registerOneOffTask], for example
18+
/// - iOS: The return value is ignored, but if work has failed, you can schedule
19+
/// another attempt using [Workmanager.registerOneOffTask]. This depends on
20+
/// BGTaskScheduler being set up correctly. Please follow the README for
21+
/// instructions.
22+
typedef BackgroundTaskHandler = Future<bool> Function(
23+
String taskName, Map<String, dynamic>? inputData);
24+
25+
/// Make sure you followed the platform setup steps first before trying to register any task.
26+
///
27+
/// Android:
28+
/// - Custom Application class
29+
///
30+
/// iOS:
31+
/// - Enabled the Background Fetch API
32+
///
33+
/// Inside your Dart code
34+
///
35+
/// Initialize the plugin first
36+
///
37+
/// ```
38+
/// @pragma('vm:entry-point')
39+
/// void callbackDispatcher() {
40+
/// Workmanager().executeTask((taskName, inputData) {
41+
/// switch(taskName) {
42+
/// case "":
43+
/// print("Replace this print statement with your code that should be executed in the background here");
44+
/// break;
45+
/// }
46+
/// return Future.value(true);
47+
/// });
48+
/// }
49+
///
50+
/// void main() {
51+
/// Workmanager().initialize(callbackDispatcher);
52+
/// }
53+
/// ```
54+
///
55+
/// ## You can schedule a specific iOS task using:
56+
/// - `Workmanager().registerOneOffTask()`
57+
/// Please read the documentation on limitations for background processing on iOS.
58+
///
59+
///
60+
/// iOS periodic background fetch task is automatically scheduled if you setup the plugin properly for Background Fetch.
61+
///
62+
/// If you are targeting iOS 13+, you can use `Workmanager().registerPeriodicTask()`
63+
///
64+
/// Note: On iOS 13+, adding a BGTaskSchedulerPermittedIdentifiers key to the Info.plist
65+
/// disables the performFetchWithCompletionHandler and setMinimumBackgroundFetchInterval
66+
/// methods, which means you cannot use both old Background Fetch and new registerPeriodicTask
67+
/// at the same time, you have to choose one based on your minimum iOS target version.
68+
/// For details see [Using background tasks to update your app](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/)
69+
///
70+
///
71+
/// ## You can schedule Android tasks using:
72+
/// - `Workmanager().registerOneOffTask()` or `Workmanager().registerPeriodicTask()`
73+
class Workmanager {
74+
factory Workmanager() => _instance;
75+
76+
Workmanager._internal();
77+
78+
static final Workmanager _instance = Workmanager._internal();
79+
80+
/// Use this constant inside your callbackDispatcher to identify when an iOS Background Fetch occurred.
81+
///
82+
/// ```
83+
/// @pragma('vm:entry-point')
84+
/// void callbackDispatcher() {
85+
/// Workmanager().executeTask((taskName, inputData) {
86+
/// switch (taskName) {
87+
/// case Workmanager.iOSBackgroundTask:
88+
/// stderr.writeln("The iOS background fetch was triggered");
89+
/// break;
90+
/// }
91+
///
92+
/// return Future.value(true);
93+
/// });
94+
/// }
95+
/// ```
96+
static const String iOSBackgroundTask = "iOSPerformFetch";
97+
98+
/// The method channel used to interact with the native platform.
99+
static const MethodChannel _backgroundChannel = MethodChannel(
100+
"be.tramckrijte.workmanager/background_channel_work_manager");
101+
102+
static BackgroundTaskHandler? _backgroundTaskHandler;
103+
104+
/// Platform implementation
105+
static WorkmanagerPlatform get _platform => WorkmanagerPlatform.instance;
106+
107+
/// Initialize the Workmanager with a [callbackDispatcher].
108+
///
109+
/// The [callbackDispatcher] is a top level function which will be invoked by Android or iOS whenever a scheduled task is due.
110+
/// The [isInDebugMode] will post local notifications for every background worker that ran. This is very useful when trying to debug what's happening in the background.
111+
Future<void> initialize(
112+
Function callbackDispatcher, {
113+
bool isInDebugMode = false,
114+
}) async {
115+
_backgroundChannel.setMethodCallHandler(_handleBackgroundMessage);
116+
return _platform.initialize(callbackDispatcher, isInDebugMode: isInDebugMode);
117+
}
118+
119+
/// Handle background method calls from the platform
120+
Future<dynamic> _handleBackgroundMessage(MethodCall call) async {
121+
if (call.method == "backgroundChannelInitialized") {
122+
return _backgroundTaskHandler?.call(
123+
call.arguments["be.tramckrijte.workmanager.DART_TASK"],
124+
call.arguments["be.tramckrijte.workmanager.INPUT_DATA"],
125+
);
126+
}
127+
return null;
128+
}
129+
130+
/// This method needs to be called from within your [callbackDispatcher].
131+
///
132+
/// [backgroundTaskHandler] is the callback that is provided when a background task is run.
133+
///
134+
/// This is used by iOS and Android to identify which task was selected to run in the background.
135+
/// The [BackgroundTaskHandler] will provide you with the [taskName] and the [inputData].
136+
/// The [taskName] will always be the value you provided when registering the task.
137+
/// The [inputData] will contain all the data you registered the task with.
138+
///
139+
/// You need to return a [Future<bool>] that will tell the OS if the task was successful or not.
140+
///
141+
/// You can perfectly call other Flutter plugins inside this callback, as the callback is simply running within a Flutter background isolate.
142+
///
143+
/// Scheduling other background tasks inside the [BackgroundTaskHandler] is allowed.
144+
void executeTask(BackgroundTaskHandler backgroundTaskHandler) async {
145+
_backgroundTaskHandler = backgroundTaskHandler;
146+
await _backgroundChannel.invokeMethod("backgroundChannelInitialized");
147+
}
148+
149+
/// Schedule a one-off task.
150+
///
151+
/// A [uniqueName] is required so only one task can be registered.
152+
/// Calling this method again with the same [uniqueName] will update the current pending task, unless an [ExistingWorkPolicy] is provided.
153+
/// The [taskName] is the value that will be returned in the [BackgroundTaskHandler], ignored on iOS where you should use [uniqueName].
154+
/// The [inputData] is the input data for task. Valid value types are: int, bool, double, String and their list
155+
/// The [initialDelay] is an [Duration] after which the task will run. Ignored on iOS where you should schedule the task in AppDelegate.swift
156+
/// The [constraints] are the requirements that need to be met before the task runs.
157+
/// The [backoffPolicy] is the backoff policy to use when retrying work.
158+
/// The [backoffPolicyDelay] is the delay for the backoff policy.
159+
/// The [tag] is an optional tag that can be used to identify or cancel the task.
160+
/// The [existingWorkPolicy] is the policy to use when work with the same [uniqueName] already exists.
161+
/// The [outOfQuotaPolicy] is the policy to use when the device is out of quota. (Android only)
162+
Future<void> registerOneOffTask(
163+
String uniqueName,
164+
String taskName, {
165+
Map<String, dynamic>? inputData,
166+
Duration? initialDelay,
167+
Constraints? constraints,
168+
ExistingWorkPolicy? existingWorkPolicy,
169+
BackoffPolicy? backoffPolicy,
170+
Duration? backoffPolicyDelay,
171+
String? tag,
172+
OutOfQuotaPolicy? outOfQuotaPolicy,
173+
}) async {
174+
return _platform.registerOneOffTask(
175+
uniqueName,
176+
taskName,
177+
inputData: inputData,
178+
initialDelay: initialDelay,
179+
constraints: constraints,
180+
existingWorkPolicy: existingWorkPolicy,
181+
backoffPolicy: backoffPolicy,
182+
backoffPolicyDelay: backoffPolicyDelay,
183+
tag: tag,
184+
outOfQuotaPolicy: outOfQuotaPolicy,
185+
);
186+
}
187+
188+
/// Schedules a periodic task that will run every provided [frequency].
189+
///
190+
/// On iOS it is not guaranteed when or how often it will run, iOS will schedule
191+
/// it as per user's App usage pattern, iOS might terminate the task or throttle
192+
/// it's frequency if it takes more than 30 seconds.
193+
///
194+
/// A [uniqueName] is required so only one task can be registered.
195+
/// The [taskName] is the value that will be returned in the [BackgroundTaskHandler], ignored on iOS where you should use [uniqueName].
196+
/// a [frequency] is not required and will be defaulted to 15 minutes if not provided.
197+
/// a [frequency] has a minimum of 15 min. Android will automatically change your frequency to 15 min if you have configured a lower frequency.
198+
/// the [flexInterval] If the nature of the work is time-sensitive, you can configure the PeriodicWorkRequest to run in a flexible period at each interval.
199+
/// The [inputData] is the input data for task. Valid value types are: int, bool, double, String and their list
200+
///
201+
/// Unlike Android, you cannot set [frequency] for iOS here rather you have to set in `AppDelegate.swift` while registering the task.
202+
/// The [inputData] is the input data for task. Valid value types are: int, bool, double, String and their list. It is not supported on iOS.
203+
///
204+
/// For iOS see Apple docs:
205+
/// [iOS 13+ Using background tasks to update your app](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/)
206+
///
207+
/// [iOS 13+ BGAppRefreshTask](https://developer.apple.com/documentation/backgroundtasks/bgapprefreshtask/)
208+
Future<void> registerPeriodicTask(
209+
String uniqueName,
210+
String taskName, {
211+
Duration? frequency,
212+
Duration? flexInterval,
213+
Map<String, dynamic>? inputData,
214+
Duration? initialDelay,
215+
Constraints? constraints,
216+
ExistingWorkPolicy? existingWorkPolicy,
217+
BackoffPolicy? backoffPolicy,
218+
Duration? backoffPolicyDelay,
219+
String? tag,
220+
}) async {
221+
return _platform.registerPeriodicTask(
222+
uniqueName,
223+
taskName,
224+
frequency: frequency,
225+
flexInterval: flexInterval,
226+
inputData: inputData,
227+
initialDelay: initialDelay,
228+
constraints: constraints,
229+
existingWorkPolicy: existingWorkPolicy,
230+
backoffPolicy: backoffPolicy,
231+
backoffPolicyDelay: backoffPolicyDelay,
232+
tag: tag,
233+
);
234+
}
235+
236+
/// Checks whether a period task is scheduled by its [uniqueName].
237+
///
238+
/// Scheduled means the work state is either ENQUEUED or RUNNING
239+
///
240+
/// Only available on Android.
241+
Future<bool> isScheduledByUniqueName(String uniqueName) async {
242+
return _platform.isScheduledByUniqueName(uniqueName);
243+
}
244+
245+
/// Schedule a background long running task, currently only available on iOS.
246+
///
247+
/// Processing tasks are for long processes like data processing and app maintenance.
248+
/// Processing tasks can run for minutes, but the system can interrupt these.
249+
/// Processing tasks run only when the device is idle. iOS might terminate any
250+
/// running background processing tasks when the user starts using the device.
251+
/// However background refresh tasks aren't affected.
252+
///
253+
/// For iOS see Apple docs:
254+
/// [iOS 13+ Using background tasks to update your app](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/)
255+
///
256+
/// [iOS 13+ BGProcessingTask](https://developer.apple.com/documentation/backgroundtasks/bgprocessingtask/)
257+
Future<void> registerProcessingTask(
258+
String uniqueName,
259+
String taskName, {
260+
Duration? initialDelay,
261+
Map<String, dynamic>? inputData,
262+
Constraints? constraints,
263+
}) async {
264+
return _platform.registerProcessingTask(
265+
uniqueName,
266+
taskName,
267+
initialDelay: initialDelay,
268+
inputData: inputData,
269+
constraints: constraints,
270+
);
271+
}
272+
273+
/// Cancels task by [uniqueName]
274+
Future<void> cancelByUniqueName(String uniqueName) async =>
275+
_platform.cancelByUniqueName(uniqueName);
276+
277+
/// Cancels task by [tag]
278+
Future<void> cancelByTag(String tag) async =>
279+
_platform.cancelByTag(tag);
280+
281+
/// Cancels all tasks
282+
Future<void> cancelAll() async => _platform.cancelAll();
283+
284+
/// Prints details of un-executed scheduled tasks to console. To be used during
285+
/// development/debugging.
286+
///
287+
/// Currently only supported on iOS and only on iOS 13+.
288+
/// Returns a string containing the scheduled tasks information.
289+
Future<String> printScheduledTasks() async =>
290+
_platform.printScheduledTasks();
291+
}

workmanager/lib/workmanager.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
library workmanager;
22

3-
export 'src/options.dart';
4-
export 'src/workmanager.dart' show Workmanager;
3+
export 'src/workmanager_impl.dart' show Workmanager, BackgroundTaskHandler;
4+
export 'package:workmanager_platform_interface/workmanager_platform_interface.dart';

workmanager/pubspec.yaml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: workmanager
22
description: Flutter Workmanager. This plugin allows you to schedule background work on Android and iOS.
3-
version: 0.7.1
3+
version: 0.8.0
44
homepage: https://github.com/fluttercommunity/flutter_workmanager
55
repository: https://github.com/fluttercommunity/flutter_workmanager
66
issue_tracker: https://github.com/fluttercommunity/flutter_workmanager/issues
@@ -12,6 +12,12 @@ environment:
1212
dependencies:
1313
flutter:
1414
sdk: flutter
15+
workmanager_platform_interface:
16+
path: ../workmanager_platform_interface
17+
workmanager_android:
18+
path: ../workmanager_android
19+
workmanager_ios:
20+
path: ../workmanager_ios
1521

1622
dev_dependencies:
1723
build_runner: ^2.4.0
@@ -26,7 +32,6 @@ flutter:
2632
plugin:
2733
platforms:
2834
android:
29-
package: dev.fluttercommunity.workmanager
30-
pluginClass: WorkmanagerPlugin
35+
default_package: workmanager_android
3136
ios:
32-
pluginClass: WorkmanagerPlugin
37+
default_package: workmanager_ios
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
*.iml
2+
.gradle
3+
/local.properties
4+
/.idea/workspace.xml
5+
/.idea/libraries
6+
.DS_Store
7+
/build
8+
/captures

0 commit comments

Comments
 (0)