diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml
index e4a6523d..174352ec 100644
--- a/.github/workflows/analysis.yml
+++ b/.github/workflows/analysis.yml
@@ -4,10 +4,28 @@ on: pull_request
jobs:
package-analysis:
runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ package: [workmanager, workmanager_platform_interface, workmanager_android, workmanager_apple]
steps:
- uses: actions/checkout@v4
- - uses: axel-op/dart-package-analyzer@v3
+ - uses: subosito/flutter-action@v2
with:
- # Required:
- githubToken: ${{ secrets.GITHUB_TOKEN }}
- relativePath: workmanager/
\ No newline at end of file
+ channel: "stable"
+ cache: true
+ - uses: bluefireteam/melos-action@v3
+
+ # unused until https://github.com/dart-lang/pana/issues/1020 is fixed
+ # # Only run dart-package-analyzer on the main workmanager package
+ # # The platform-specific packages are not meant to be published individually
+ # - uses: axel-op/dart-package-analyzer@v3
+ # if: matrix.package == 'workmanager'
+ # with:
+ # githubToken: ${{ secrets.GITHUB_TOKEN }}
+ # relativePath: ${{ matrix.package }}/
+
+ - name: Analyze package
+ run: |
+ cd ${{ matrix.package }}
+ flutter analyze
+ dart pub publish --dry-run
\ No newline at end of file
diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml
index 04ca605b..49c96e08 100644
--- a/.github/workflows/format.yml
+++ b/.github/workflows/format.yml
@@ -16,6 +16,7 @@ jobs:
channel: 'stable'
- name: Format
run: |
+ flutter pub get
dart format --set-exit-if-changed .
format_kotlin:
@@ -45,12 +46,14 @@ jobs:
with:
channel: 'stable'
+ - uses: bluefireteam/melos-action@v3
- name: publish checks
run: |
- dart pub global activate melos
- melos bootstrap
- cd workmanager
- flutter pub get
+ cd workmanager_platform_interface
+ flutter pub publish -n
+ cd ../workmanager_android
+ flutter pub publish -n
+ cd ../workmanager_apple
flutter pub publish -n
- flutter pub global activate tuneup
- flutter pub global run tuneup check
\ No newline at end of file
+ cd ../workmanager
+ flutter pub publish -n
\ No newline at end of file
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 60f9d387..0ac3f275 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -15,10 +15,7 @@ jobs:
with:
channel: 'stable'
cache: true
- - name: Install melos
- run: dart pub global activate melos
- - name: Bootstrap packages
- run: melos bootstrap
+ - uses: bluefireteam/melos-action@v3
- name: Test
run: |
cd workmanager
@@ -32,10 +29,7 @@ jobs:
with:
channel: 'stable'
cache: true
- - name: Install melos
- run: dart pub global activate melos
- - name: Bootstrap packages
- run: melos bootstrap
+ - uses: bluefireteam/melos-action@v3
- name: Build iOS App
run: cd example && flutter build ios --debug --no-codesign
- name: Run native iOS tests
@@ -53,14 +47,11 @@ jobs:
with:
channel: 'stable'
cache: true
- - name: Install melos
- run: dart pub global activate melos
- - name: Bootstrap packages
- run: melos bootstrap
+ - uses: bluefireteam/melos-action@v3
- name: Build Android App
run: cd example && flutter build apk --debug
- name: Run native Android tests
- run: cd example/android && ./gradlew :workmanager:test
+ run: cd example/android && ./gradlew :workmanager_android:test
drive_ios:
strategy:
@@ -78,10 +69,7 @@ jobs:
- uses: futureware-tech/simulator-action@v3
with:
model: '${{ matrix.device }}'
- - name: Install melos
- run: dart pub global activate melos
- - name: Bootstrap packages
- run: melos bootstrap
+ - uses: bluefireteam/melos-action@v3
# Run flutter integrate tests
- name: Run Flutter integration tests
run: cd example && flutter test integration_test/workmanager_integration_test.dart
@@ -93,7 +81,7 @@ jobs:
strategy:
#set of different configurations of the virtual environment.
matrix:
- api-level: [34]
+ api-level: [35]
# api-level: [21, 29]
target: [default]
steps:
@@ -111,16 +99,46 @@ jobs:
with:
channel: 'stable'
cache: true
- - name: Install melos
- run: dart pub global activate melos
- - name: Bootstrap packages
- run: melos bootstrap
- - name: Run Flutter Driver tests
+ - uses: bluefireteam/melos-action@v3
+
+ # Gradle cache for better performance
+ - name: Gradle cache
+ uses: gradle/actions/setup-gradle@v3
+
+ # AVD cache to speed up emulator startup
+ - name: AVD cache
+ uses: actions/cache@v4
+ id: avd-cache
+ with:
+ path: |
+ ~/.android/avd/*
+ ~/.android/adb*
+ key: avd-${{ matrix.api-level }}-${{ matrix.target }}-${{ runner.os }}
+
+ # Generate AVD snapshot for caching if not already cached
+ - name: Create AVD and generate snapshot
+ if: steps.avd-cache.outputs.cache-hit != 'true'
+ uses: reactivecircus/android-emulator-runner@v2
+ with:
+ api-level: ${{ matrix.api-level }}
+ target: ${{ matrix.target }}
+ arch: x86_64
+ force-avd-creation: false
+ emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
+ disable-animations: true
+ script: echo "Generated AVD snapshot"
+
+ # Run actual tests using cached AVD
+ - name: Run Flutter integration tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: x86_64
+ force-avd-creation: false
+ emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
+ disable-animations: true
disk-size: 6000M
heap-size: 600M
- script: cd example && flutter test integration_test/workmanager_integration_test.dart
+ script: |
+ cd example && flutter test integration_test/workmanager_integration_test.dart
diff --git a/IOS_SETUP.md b/IOS_SETUP.md
index a7525bb6..68a5c448 100644
--- a/IOS_SETUP.md
+++ b/IOS_SETUP.md
@@ -39,7 +39,7 @@ import workmanager
WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "task-identifier")
// Register a periodic task in iOS 13+
-WorkmanagerPlugin.registerPeriodicTask(withIdentifier: "be.tramckrijte.workmanagerExample.iOSBackgroundAppRefresh", frequency: NSNumber(value: 20 * 60))
+WorkmanagerPlugin.registerPeriodicTask(withIdentifier: "dev.fluttercommunity.workmanagerExample.iOSBackgroundAppRefresh", frequency: NSNumber(value: 20 * 60))
```
- Info.plist
@@ -49,7 +49,7 @@ WorkmanagerPlugin.registerPeriodicTask(withIdentifier: "be.tramckrijte.workmanag
task-identifier
- be.tramckrijte.workmanagerExample.iOSBackgroundAppRefresh
+ dev.fluttercommunity.workmanagerExample.iOSBackgroundAppRefresh
```
> ⚠️ On iOS 13+, adding a `BGTaskSchedulerPermittedIdentifiers` key to the Info.plist for new `BGTaskScheduler` API disables the `performFetchWithCompletionHandler` and `setMinimumBackgroundFetchInterval`
diff --git a/README.md b/README.md
index 97db31bb..fc35d877 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,31 @@
# Flutter Workmanager
[](https://pub.dartlang.org/packages/workmanager)
-[](https://cirrus-ci.com/github/vrtdev/flutter_workmanager/)
-=======
+[](https://pub.dev/packages/workmanager/score)
+[](https://pub.dev/packages/workmanager/score)
+[](https://pub.dev/packages/workmanager/score)
+[](https://github.com/fluttercommunity/flutter_workmanager/actions)
+[](https://github.com/fluttercommunity/flutter_workmanager/blob/main/LICENSE)
Flutter WorkManager is a wrapper around [Android's WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager), [iOS' performFetchWithCompletionHandler](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623125-application) and [iOS BGAppRefreshTask](https://developer.apple.com/documentation/backgroundtasks/bgapprefreshtask), effectively enabling headless execution of Dart code in the background.
-For iOS users, please watch this video on a general introduction to background processing: https://developer.apple.com/videos/play/wwdc2019/707 ( and old link for [Background execution demystified (WWDC 2020)](https://devstreaming-cdn.apple.com/videos/wwdc/2020/10063/3/2E1C3BA0-2643-4330-A5B2-3A9878453987/wwdc2020_10063_hd.mp4). All of the constraints discussed in the video also apply to this plugin.
+For iOS users, please watch this video on a general introduction to background processing: https://developer.apple.com/videos/play/wwdc2019/707. All of the constraints discussed in the video also apply to this plugin.
This is especially useful to run periodic tasks, such as fetching remote data on a regular basis.
> This plugin was featured in this [Medium blogpost](https://medium.com/vrt-digital-studio/flutter-workmanager-81e0cfbd6f6e)
+## Federated Plugin Architecture
+
+This plugin uses a federated architecture, which means that the main `workmanager` package provides the API, while platform-specific implementations are in separate packages:
+
+- **workmanager**: The main package that provides the unified API
+- **workmanager_platform_interface**: The common platform interface
+- **workmanager_android**: Android-specific implementation
+- **workmanager_apple**: Apple platform (iOS/macOS) implementation
+
+This architecture allows for better platform-specific optimizations and easier maintenance. When you add `workmanager` to your `pubspec.yaml`, the platform-specific packages are automatically included through the endorsed federated plugin system.
+
# Platform Setup
In order for background work to be scheduled correctly you should follow the Android and iOS setup first.
@@ -19,6 +33,33 @@ In order for background work to be scheduled correctly you should follow the And
- [Android Setup](https://github.com/fluttercommunity/flutter_workmanager/blob/master/ANDROID_SETUP.md)
- [iOS Setup](https://github.com/fluttercommunity/flutter_workmanager/blob/master/IOS_SETUP.md)
+## Publishing (For Maintainers)
+
+This project uses a federated plugin architecture with multiple packages. To publish updates:
+
+1. **Update versions** in all `pubspec.yaml` files:
+ - `workmanager/pubspec.yaml`
+ - `workmanager_platform_interface/pubspec.yaml`
+ - `workmanager_android/pubspec.yaml`
+ - `workmanager_apple/pubspec.yaml`
+
+2. **Publish packages in order**:
+ ```bash
+ # 1. Publish platform interface first
+ cd workmanager_platform_interface && dart pub publish
+
+ # 2. Publish platform implementations
+ cd ../workmanager_android && dart pub publish
+ cd ../workmanager_apple && dart pub publish
+
+ # 3. Publish main package last
+ cd ../workmanager && dart pub publish
+ ```
+
+3. **Update dependencies** in main package to point to pub.dev versions instead of path dependencies before publishing
+
+4. **Tag the release** with the version number: `git tag v0.8.0 && git push origin v0.8.0`
+
# How to use the package?
See sample folder for a complete working example.
@@ -135,12 +176,12 @@ To use `registerPeriodicTask` first register the task in `Info.plist` and `AppDe
```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))
+WorkmanagerPlugin.registerPeriodicTask(withIdentifier: "dev.fluttercommunity.workmanagerExample.iOSBackgroundAppRefresh", frequency: NSNumber(value: 20 * 60))
```
Then schedule the task from your App
```dart
-const iOSBackgroundAppRefresh = "be.tramckrijte.workmanagerExample.iOSBackgroundAppRefresh";
+const iOSBackgroundAppRefresh = "dev.fluttercommunity.workmanagerExample.iOSBackgroundAppRefresh";
Workmanager().registerPeriodicTask(
iOSBackgroundAppRefresh,
iOSBackgroundAppRefresh,
@@ -161,7 +202,7 @@ iOS might terminate any running background processing tasks when the user starts
For more information see [BGProcessingTask](https://developer.apple.com/documentation/backgroundtasks/bgprocessingtask)
```dart
-const iOSBackgroundProcessingTask = "be.tramckrijte.workmanagerExample.iOSBackgroundProcessingTask";
+const iOSBackgroundProcessingTask = "dev.fluttercommunity.workmanagerExample.iOSBackgroundProcessingTask";
Workmanager().registerProcessingTask(
iOSBackgroundProcessingTask,
iOSBackgroundProcessingTask,
@@ -249,7 +290,7 @@ Workmanager().registerOneOffTask("1", "simpleTask", tag: "tag");
## Existing Work Policy
Indicates the desired behaviour when the same task is scheduled more than once.
-The default is `KEEP`
+The default is `keep`
```dart
Workmanager().registerOneOffTask("1", "simpleTask", existingWorkPolicy: ExistingWorkPolicy.append);
diff --git a/workmanager/analysis_options.yml b/analysis_options.yml
similarity index 75%
rename from workmanager/analysis_options.yml
rename to analysis_options.yml
index b05d812c..70b8b42d 100644
--- a/workmanager/analysis_options.yml
+++ b/analysis_options.yml
@@ -1,5 +1,8 @@
include: package:flutter_lints/flutter.yaml
+formatter:
+ page_width: 120
+
linter:
rules:
- public_member_api_docs
\ No newline at end of file
diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties
index 1a1b0f9c..3a2a9c32 100644
--- a/example/android/gradle/wrapper/gradle-wrapper.properties
+++ b/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Fri May 30 01:37:19 JST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/example/android/settings.gradle b/example/android/settings.gradle
index a034ebcc..0f2377d9 100644
--- a/example/android/settings.gradle
+++ b/example/android/settings.gradle
@@ -18,7 +18,7 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
- id "com.android.application" version '8.10.1' apply false
+ id "com.android.application" version '8.11.0' apply false
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
}
diff --git a/example/integration_test/workmanager_integration_test.dart b/example/integration_test/workmanager_integration_test.dart
index 1a2fce78..550549ba 100644
--- a/example/integration_test/workmanager_integration_test.dart
+++ b/example/integration_test/workmanager_integration_test.dart
@@ -3,11 +3,57 @@ import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+import 'package:uuid/uuid.dart';
import 'package:workmanager/workmanager.dart';
+const String dataTransferTaskName =
+ 'dev.fluttercommunity.integrationTest.dataTransferTask';
+const String retryTaskName = 'dev.fluttercommunity.integrationTest.retryTask';
+
+/// One retry is enough to test the retry logic
+const int kMaxRetryAttempts = 1;
+
@pragma('vm:entry-point')
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) async {
+ print(
+ 'CallbackDispatcher called with task: $task and inputData: $inputData');
+
+ if (task == retryTaskName) {
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ await prefs.reload();
+ var counterName = inputData!['counter_name'];
+ final count = prefs.getInt(counterName) ?? 0;
+ if (count == kMaxRetryAttempts) {
+ return Future.value(true);
+ } else {
+ await prefs.setInt(counterName, count + 1);
+ return Future.value(false);
+ }
+ }
+ if (task == dataTransferTaskName) {
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+
+ for (String key in inputData!.keys) {
+ var value = inputData[key];
+ if (value is String) {
+ await prefs.setString(key, value);
+ } else if (value is int) {
+ await prefs.setInt(key, value);
+ } else if (value is double) {
+ await prefs.setDouble(key, value);
+ } else if (value is bool) {
+ await prefs.setBool(key, value);
+ } else if (value is List) {
+ await prefs.setStringList(key, List.from(value));
+ } else if (value is Map) {
+ await prefs.setString(key, value.toString());
+ } else {
+ print('Unsupported data type for key $key: $value');
+ }
+ }
+ }
return true;
});
}
@@ -15,63 +61,420 @@ void callbackDispatcher() {
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
- testWidgets('initialize & schedule task - android',
- (WidgetTester tester) async {
- final wm = Workmanager();
- await wm.initialize(callbackDispatcher);
- await wm.registerOneOffTask(
- 'be.tramckrijte.workmanagerExample.taskId',
- 'taskName',
- );
- }, skip: !Platform.isAndroid);
-
- testWidgets('initialize & schedule task - iOS', (WidgetTester tester) async {
- final wm = Workmanager();
- await wm.initialize(callbackDispatcher);
- try {
- await wm.registerOneOffTask(
- 'be.tramckrijte.workmanagerExample.taskId',
- 'taskName',
+ setUp(() async {
+ await SharedPreferences.getInstance().then((prefs) {
+ return prefs.clear(); // Clear shared preferences before each test
+ });
+ });
+
+ group('Workmanager Integration Tests', () {
+ late Workmanager workmanager;
+
+ setUp(() {
+ workmanager = Workmanager();
+ });
+
+ testWidgets('initialize should succeed on all platforms',
+ (WidgetTester tester) async {
+ await workmanager.initialize(callbackDispatcher, isInDebugMode: true);
+ // No exception means success
+ });
+
+ testWidgets('input data is correctly transferred to native side',
+ (WidgetTester tester) async {
+ await workmanager.initialize(callbackDispatcher);
+
+ final prefix = Uuid().v4().toString();
+
+ final testData = {
+ '$prefix.string': 'input string',
+ '$prefix.number': 42,
+ '$prefix.boolean': true,
+ '$prefix.list': ['1', '2', '3'],
+ '$prefix.double': 3.14,
+ };
+
+ await workmanager.registerOneOffTask(
+ dataTransferTaskName,
+ dataTransferTaskName,
+ inputData: testData,
);
- await wm.cancelAll();
- } on PlatformException catch (e) {
- if (e.code !=
- 'bgTaskSchedulingFailed(Error Domain=BGTaskSchedulerErrorDomain Code=1 "(null)") error') {
- rethrow;
+
+ // Look for 20 seconds & observe if the settings have been written
+ for (int i = 0; i < 20; i++) {
+ await Future.delayed(const Duration(seconds: 1));
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ await prefs.reload();
+ if (prefs.getString('$prefix.string') == 'input string' &&
+ prefs.getInt('$prefix.number') == 42 &&
+ prefs.getBool('$prefix.boolean') == true &&
+ prefs.getStringList('$prefix.list')!.length == 3 &&
+ prefs.getDouble('$prefix.double') == 3.14) {
+ return;
+ }
}
- }
- }, skip: !Platform.isIOS);
-
- testWidgets('initialize & cancelAll - iOS', (WidgetTester tester) async {
- final wm = Workmanager();
- await wm.initialize(callbackDispatcher);
- try {
- await wm.cancelAll();
- } on PlatformException catch (e) {
- if (e.code !=
- 'bgTaskSchedulingFailed(Error Domain=BGTaskSchedulerErrorDomain Code=1 "(null)") error') {
- rethrow;
+ fail('Input data was not transferred correctly to native side.');
+ });
+
+ testWidgets('retry task should retry up to ${kMaxRetryAttempts} times',
+ (WidgetTester tester) async {
+ await workmanager.initialize(callbackDispatcher);
+
+ final counterName = Uuid().v4().toString() + 'retryCounter';
+ final initialCount = 0;
+
+ // Set initial count in shared preferences
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ await prefs.setInt(counterName, initialCount);
+
+ try {
+ await workmanager.registerOneOffTask(
+ retryTaskName,
+ retryTaskName,
+ inputData: {'counter_name': counterName},
+ backoffPolicy: BackoffPolicy.linear,
+ backoffPolicyDelay: const Duration(seconds: 1),
+ );
+
+ // Wait for the task to complete
+ for (int i = 0; i < 45; i++) {
+ await Future.delayed(const Duration(seconds: 1));
+ await prefs.reload();
+ if (prefs.getInt(counterName) == kMaxRetryAttempts) {
+ return;
+ }
+ }
+ fail('Retry task did not reach maximum attempts.');
+ } catch (e) {
+ fail('Retry task failed with exception: $e');
+ } finally {
+ await workmanager.cancelByUniqueName(retryTaskName);
}
- }
- }, skip: Platform.isIOS);
-
- testWidgets('initialize & cancelByUniqueName - iOS',
- (WidgetTester tester) async {
- final wm = Workmanager();
- await wm.initialize(callbackDispatcher);
- try {
- await wm.registerOneOffTask(
- 'be.tramckrijte.workmanagerExample.taskId',
- 'taskName',
- );
- await wm.cancelByUniqueName(
- 'be.tramckrijte.workmanagerExample.taskId',
- );
- } on PlatformException catch (e) {
- if (e.code !=
- 'bgTaskSchedulingFailed(Error Domain=BGTaskSchedulerErrorDomain Code=1 "(null)") error') {
- rethrow;
+ });
+ testWidgets('registerOneOffTask basic should succeed',
+ (WidgetTester tester) async {
+ await workmanager.initialize(callbackDispatcher);
+
+ try {
+ await workmanager.registerOneOffTask(
+ 'test.oneoff.basic',
+ 'basicTask',
+ );
+ // Clean up
+ await workmanager.cancelByUniqueName('test.oneoff.basic');
+ } on PlatformException catch (e) {
+ // iOS may fail with BGTaskSchedulerErrorDomain in testing environment
+ if (Platform.isIOS && e.code.contains('bgTaskSchedulingFailed')) {
+ // This is expected in test environment on iOS
+ } else {
+ rethrow;
+ }
}
- }
- }, skip: Platform.isIOS);
+ });
+
+ testWidgets('registerOneOffTask with inputData should succeed',
+ (WidgetTester tester) async {
+ await workmanager.initialize(callbackDispatcher);
+
+ try {
+ await workmanager.registerOneOffTask(
+ 'test.oneoff.data',
+ 'dataTask',
+ inputData: {
+ 'string': 'test',
+ 'number': 42,
+ 'boolean': true,
+ 'list': ['1', '2', '3'],
+ },
+ );
+ // Clean up
+ await workmanager.cancelByUniqueName('test.oneoff.data');
+ } on PlatformException catch (e) {
+ if (Platform.isIOS && e.code.contains('bgTaskSchedulingFailed')) {
+ // Expected on iOS in test environment
+ } else {
+ rethrow;
+ }
+ }
+ });
+
+ testWidgets('registerOneOffTask with all parameters (Android)',
+ (WidgetTester tester) async {
+ await workmanager.initialize(callbackDispatcher);
+
+ if (Platform.isAndroid) {
+ await workmanager.registerOneOffTask(
+ 'test.oneoff.full',
+ 'fullTask',
+ inputData: {'test': 'data'},
+ initialDelay: const Duration(seconds: 1),
+ constraints: Constraints(
+ networkType: NetworkType.connected,
+ requiresBatteryNotLow: true,
+ requiresCharging: false,
+ requiresDeviceIdle: false,
+ requiresStorageNotLow: true,
+ ),
+ existingWorkPolicy: ExistingWorkPolicy.replace,
+ backoffPolicy: BackoffPolicy.exponential,
+ backoffPolicyDelay: const Duration(seconds: 30),
+ tag: 'test-tag',
+ // Don't use outOfQuotaPolicy with non-supported constraints
+ );
+ // Clean up
+ await workmanager.cancelByUniqueName('test.oneoff.full');
+ }
+ });
+
+ testWidgets('registerOneOffTask with expedited job (Android)',
+ (WidgetTester tester) async {
+ await workmanager.initialize(callbackDispatcher);
+
+ if (Platform.isAndroid) {
+ // Expedited jobs only support network and storage constraints
+ await workmanager.registerOneOffTask(
+ 'test.oneoff.expedited',
+ 'expeditedTask',
+ inputData: {'expedited': 'true'},
+ constraints: Constraints(
+ networkType: NetworkType.connected,
+ requiresStorageNotLow: true,
+ // Can't use battery, charging, or idle constraints with expedited jobs
+ ),
+ existingWorkPolicy: ExistingWorkPolicy.replace,
+ tag: 'expedited-tag',
+ outOfQuotaPolicy: OutOfQuotaPolicy.runAsNonExpeditedWorkRequest,
+ );
+ // Clean up
+ await workmanager.cancelByUniqueName('test.oneoff.expedited');
+ }
+ });
+
+ testWidgets('registerPeriodicTask should work on supported platforms',
+ (WidgetTester tester) async {
+ await workmanager.initialize(callbackDispatcher);
+
+ try {
+ await workmanager.registerPeriodicTask(
+ 'test.periodic.basic',
+ 'periodicTask',
+ frequency: const Duration(minutes: 15),
+ );
+ // Clean up
+ await workmanager.cancelByUniqueName('test.periodic.basic');
+ } on PlatformException catch (e) {
+ if (Platform.isIOS && e.code.contains('bgTaskSchedulingFailed')) {
+ // Expected on iOS in test environment
+ } else {
+ rethrow;
+ }
+ }
+ });
+
+ testWidgets('registerPeriodicTask with parameters (Android)',
+ (WidgetTester tester) async {
+ await workmanager.initialize(callbackDispatcher);
+
+ if (Platform.isAndroid) {
+ await workmanager.registerPeriodicTask(
+ 'test.periodic.full',
+ 'periodicFullTask',
+ frequency: const Duration(minutes: 15),
+ flexInterval: const Duration(minutes: 5),
+ inputData: {'periodic': 'data'},
+ initialDelay: const Duration(seconds: 1),
+ constraints: Constraints(
+ networkType: NetworkType.unmetered,
+ requiresBatteryNotLow: false,
+ requiresCharging: true,
+ ),
+ existingWorkPolicy: ExistingWorkPolicy.keep,
+ backoffPolicy: BackoffPolicy.linear,
+ backoffPolicyDelay: const Duration(seconds: 10),
+ tag: 'periodic-tag',
+ );
+ // Clean up
+ await workmanager.cancelByUniqueName('test.periodic.full');
+ }
+ });
+
+ testWidgets('registerProcessingTask should work on iOS only',
+ (WidgetTester tester) async {
+ await workmanager.initialize(callbackDispatcher);
+
+ if (Platform.isIOS) {
+ try {
+ await workmanager.registerProcessingTask(
+ 'test.processing',
+ 'processingTask',
+ initialDelay: const Duration(seconds: 1),
+ inputData: {'processing': 'data'},
+ constraints: Constraints(
+ networkType: NetworkType.connected,
+ requiresCharging: true,
+ ),
+ );
+ // Clean up
+ await workmanager.cancelByUniqueName('test.processing');
+ } on PlatformException catch (e) {
+ if (e.code.contains('bgTaskSchedulingFailed')) {
+ // Expected in test environment
+ } else {
+ rethrow;
+ }
+ }
+ } else {
+ // Should throw UnsupportedError on Android
+ expect(
+ () => workmanager.registerProcessingTask(
+ 'test.processing',
+ 'processingTask',
+ ),
+ throwsA(isA()),
+ );
+ }
+ });
+
+ testWidgets('cancelByUniqueName should succeed',
+ (WidgetTester tester) async {
+ await workmanager.initialize(callbackDispatcher);
+
+ // Should not throw even if task doesn't exist
+ await workmanager.cancelByUniqueName('nonexistent.task');
+ });
+
+ testWidgets('cancelByTag should work on Android only',
+ (WidgetTester tester) async {
+ await workmanager.initialize(callbackDispatcher);
+
+ if (Platform.isAndroid) {
+ // Should not throw even if no tasks with tag exist
+ await workmanager.cancelByTag('nonexistent-tag');
+ } else {
+ // Should throw UnsupportedError on iOS
+ expect(
+ () => workmanager.cancelByTag('test-tag'),
+ throwsA(isA()),
+ );
+ }
+ });
+
+ testWidgets('cancelAll should succeed', (WidgetTester tester) async {
+ await workmanager.initialize(callbackDispatcher);
+
+ try {
+ await workmanager.cancelAll();
+ } on PlatformException catch (e) {
+ if (Platform.isIOS && e.code.contains('bgTaskSchedulingFailed')) {
+ // Expected on iOS in some test environments
+ } else {
+ rethrow;
+ }
+ }
+ });
+
+ testWidgets('isScheduled should work on Android only',
+ (WidgetTester tester) async {
+ await workmanager.initialize(callbackDispatcher);
+
+ if (Platform.isAndroid) {
+ // Test with a task that doesn't exist
+ final isScheduled =
+ await workmanager.isScheduledByUniqueName('nonexistent.task');
+ expect(isScheduled, false);
+
+ // Register a task and check if it's scheduled
+ try {
+ await workmanager.registerOneOffTask(
+ 'test.scheduled',
+ 'scheduledTask',
+ );
+ final isScheduledAfterRegister =
+ await workmanager.isScheduledByUniqueName('test.scheduled');
+ expect(isScheduledAfterRegister, true);
+
+ // Clean up
+ await workmanager.cancelByUniqueName('test.scheduled');
+
+ // Check again after cancellation
+ final isScheduledAfterCancel =
+ await workmanager.isScheduledByUniqueName('test.scheduled');
+ expect(isScheduledAfterCancel, false);
+ } catch (e) {
+ // Clean up even if test fails
+ await workmanager.cancelByUniqueName('test.scheduled');
+ rethrow;
+ }
+ } else {
+ // Should throw UnsupportedError on iOS
+ expect(
+ () => workmanager.isScheduledByUniqueName('test-task'),
+ throwsA(isA()),
+ );
+ }
+ });
+
+ testWidgets('printScheduledTasks should work on iOS only',
+ (WidgetTester tester) async {
+ await workmanager.initialize(callbackDispatcher);
+
+ if (Platform.isIOS) {
+ final result = await workmanager.printScheduledTasks();
+ expect(result, isA());
+ } else {
+ // Should throw UnsupportedError on Android
+ expect(
+ () => workmanager.printScheduledTasks(),
+ throwsA(isA()),
+ );
+ }
+ });
+
+ testWidgets('multiple task registration and cancellation flow',
+ (WidgetTester tester) async {
+ await workmanager.initialize(callbackDispatcher);
+
+ final taskIds = ['test.multi.1', 'test.multi.2', 'test.multi.3'];
+
+ try {
+ // Register multiple tasks
+ for (int i = 0; i < taskIds.length; i++) {
+ await workmanager.registerOneOffTask(
+ taskIds[i],
+ 'multiTask$i',
+ inputData: {'index': i},
+ );
+ }
+
+ // Cancel individual tasks
+ await workmanager.cancelByUniqueName(taskIds[0]);
+
+ if (Platform.isAndroid) {
+ // Verify first task is cancelled, others remain
+ expect(await workmanager.isScheduledByUniqueName(taskIds[0]), false);
+ expect(await workmanager.isScheduledByUniqueName(taskIds[1]), true);
+ expect(await workmanager.isScheduledByUniqueName(taskIds[2]), true);
+ }
+
+ // Cancel all remaining tasks
+ await workmanager.cancelAll();
+
+ if (Platform.isAndroid) {
+ // Verify all tasks are cancelled
+ for (final taskId in taskIds) {
+ expect(await workmanager.isScheduledByUniqueName(taskId), false);
+ }
+ }
+ } on PlatformException catch (e) {
+ if (Platform.isIOS && e.code.contains('bgTaskSchedulingFailed')) {
+ // Expected on iOS in test environment
+ } else {
+ rethrow;
+ }
+ } finally {
+ // Ensure cleanup
+ await workmanager.cancelAll();
+ }
+ });
+ });
}
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
index d1c3adab..d7cd05f3 100644
--- a/example/ios/Podfile.lock
+++ b/example/ios/Podfile.lock
@@ -10,7 +10,7 @@ PODS:
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - workmanager (0.0.1):
+ - workmanager_apple (0.0.1):
- Flutter
DEPENDENCIES:
@@ -19,7 +19,7 @@ DEPENDENCIES:
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- - workmanager (from `.symlinks/plugins/workmanager/ios`)
+ - workmanager_apple (from `.symlinks/plugins/workmanager_apple/ios`)
EXTERNAL SOURCES:
Flutter:
@@ -32,8 +32,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/permission_handler_apple/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
- workmanager:
- :path: ".symlinks/plugins/workmanager/ios"
+ workmanager_apple:
+ :path: ".symlinks/plugins/workmanager_apple/ios"
SPEC CHECKSUMS:
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
@@ -41,8 +41,8 @@ SPEC CHECKSUMS:
path_provider_foundation: 608fcb11be570ce83519b076ab6a1fffe2474f05
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
- workmanager: b89e4e4445d8b57ee2fdbf1c3925696ebe5b8990
+ workmanager_apple: f540d652595dfe5c8b8200c4c85ba622d6fb5c5b
-PODFILE CHECKSUM: b63d507eb7cc768afa26646638aaf07f371f6370
+PODFILE CHECKSUM: 4225ca2ac155c3e63d4d416fa6b1b890e2563502
COCOAPODS: 1.16.2
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
index a38a6587..702716be 100644
--- a/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/example/ios/Runner.xcodeproj/project.pbxproj
@@ -299,7 +299,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
+ shellScript = "# SwiftLint script disabled to prevent build failures\n# Type a script or drag a script file from your workspace to insert its path.\n# if which swiftlint >/dev/null; then\n# swiftlint\n# else\n# echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\n# fi\necho \"SwiftLint step skipped\"\n";
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
@@ -382,14 +382,14 @@
"${BUILT_PRODUCTS_DIR}/integration_test/integration_test.framework",
"${BUILT_PRODUCTS_DIR}/path_provider_foundation/path_provider_foundation.framework",
"${BUILT_PRODUCTS_DIR}/shared_preferences_foundation/shared_preferences_foundation.framework",
- "${BUILT_PRODUCTS_DIR}/workmanager/workmanager.framework",
+ "${BUILT_PRODUCTS_DIR}/workmanager_apple/workmanager_apple.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/integration_test.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_foundation.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_foundation.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/workmanager.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/workmanager_apple.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -540,7 +540,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
- PRODUCT_BUNDLE_IDENTIFIER = be.tramckrijte.workmanagerExample;
+ PRODUCT_BUNDLE_IDENTIFIER = dev.fluttercommunity.workmanagerExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
@@ -680,7 +680,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
- PRODUCT_BUNDLE_IDENTIFIER = be.tramckrijte.workmanagerExample;
+ PRODUCT_BUNDLE_IDENTIFIER = dev.fluttercommunity.workmanagerExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -712,7 +712,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
- PRODUCT_BUNDLE_IDENTIFIER = be.tramckrijte.workmanagerExample;
+ PRODUCT_BUNDLE_IDENTIFIER = dev.fluttercommunity.workmanagerExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift
index d3b40c1f..79a5d40c 100644
--- a/example/ios/Runner/AppDelegate.swift
+++ b/example/ios/Runner/AppDelegate.swift
@@ -1,6 +1,6 @@
import UIKit
import Flutter
-import workmanager
+import workmanager_apple
@UIApplicationMain
@@ -20,15 +20,18 @@ import workmanager
GeneratedPluginRegistrant.register(with: registry)
}
- 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")
+ WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "dev.fluttercommunity.workmanagerExample.taskId")
+ WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "dev.fluttercommunity.workmanagerExample.rescheduledTask")
+ WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "dev.fluttercommunity.workmanagerExample.simpleDelayedTask")
+ WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "dev.fluttercommunity.workmanagerExample.iOSBackgroundProcessingTask")
+
+ WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "dev.fluttercommunity.integrationTest.dataTransferTask")
+ WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "dev.fluttercommunity.integrationTest.retryTask")
// 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))
+ WorkmanagerPlugin.registerPeriodicTask(withIdentifier: "dev.fluttercommunity.workmanagerExample.iOSBackgroundAppRefresh", frequency: NSNumber(value: 20 * 60))
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist
index bb219cca..6d7c7901 100644
--- a/example/ios/Runner/Info.plist
+++ b/example/ios/Runner/Info.plist
@@ -4,15 +4,17 @@
BGTaskSchedulerPermittedIdentifiers
- be.tramckrijte.workmanagerExample.taskId
- be.tramckrijte.workmanagerExample.simpleTask
- be.tramckrijte.workmanagerExample.rescheduledTask
- be.tramckrijte.workmanagerExample.failedTask
- be.tramckrijte.workmanagerExample.simpleDelayedTask
- be.tramckrijte.workmanagerExample.simplePeriodicTask
- be.tramckrijte.workmanagerExample.simplePeriodic1HourTask
- be.tramckrijte.workmanagerExample.iOSBackgroundAppRefresh
- be.tramckrijte.workmanagerExample.iOSBackgroundProcessingTask
+ dev.fluttercommunity.workmanagerExample.taskId
+ dev.fluttercommunity.workmanagerExample.simpleTask
+ dev.fluttercommunity.workmanagerExample.rescheduledTask
+ dev.fluttercommunity.workmanagerExample.failedTask
+ dev.fluttercommunity.workmanagerExample.simpleDelayedTask
+ dev.fluttercommunity.workmanagerExample.simplePeriodicTask
+ dev.fluttercommunity.workmanagerExample.simplePeriodic1HourTask
+ dev.fluttercommunity.workmanagerExample.iOSBackgroundAppRefresh
+ dev.fluttercommunity.workmanagerExample.iOSBackgroundProcessingTask
+ dev.fluttercommunity.integrationTest.dataTransferTask
+ dev.fluttercommunity.integrationTest.retryTask
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
@@ -62,5 +64,11 @@
UIApplicationSupportsIndirectInputEvents
+ NSLocalNetworkUsageDescription
+This app needs local network access for debugging and communication.
+NSBonjourServices
+
+ _dartobservatory._tcp
+
diff --git a/example/ios/RunnerTests/WorkmanagerTests.swift b/example/ios/RunnerTests/WorkmanagerTests.swift
index c9309680..721f2f40 100644
--- a/example/ios/RunnerTests/WorkmanagerTests.swift
+++ b/example/ios/RunnerTests/WorkmanagerTests.swift
@@ -8,7 +8,7 @@
import XCTest
-@testable import workmanager
+@testable import workmanager_apple
class WorkmanagerTests: XCTestCase {
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 80021afa..a8e498e1 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -1,6 +1,7 @@
+import 'dart:developer';
import 'dart:async';
import 'dart:io';
-import 'dart:math';
+import 'dart:math' show Random;
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
@@ -10,18 +11,20 @@ import 'package:workmanager/workmanager.dart';
void main() => runApp(MaterialApp(home: MyApp()));
-const simpleTaskKey = "be.tramckrijte.workmanagerExample.simpleTask";
-const rescheduledTaskKey = "be.tramckrijte.workmanagerExample.rescheduledTask";
-const failedTaskKey = "be.tramckrijte.workmanagerExample.failedTask";
-const simpleDelayedTask = "be.tramckrijte.workmanagerExample.simpleDelayedTask";
+const simpleTaskKey = "dev.fluttercommunity.workmanagerExample.simpleTask";
+const rescheduledTaskKey =
+ "dev.fluttercommunity.workmanagerExample.rescheduledTask";
+const failedTaskKey = "dev.fluttercommunity.workmanagerExample.failedTask";
+const simpleDelayedTask =
+ "dev.fluttercommunity.workmanagerExample.simpleDelayedTask";
const simplePeriodicTask =
- "be.tramckrijte.workmanagerExample.simplePeriodicTask";
+ "dev.fluttercommunity.workmanagerExample.simplePeriodicTask";
const simplePeriodic1HourTask =
- "be.tramckrijte.workmanagerExample.simplePeriodic1HourTask";
+ "dev.fluttercommunity.workmanagerExample.simplePeriodic1HourTask";
const iOSBackgroundAppRefresh =
- "be.tramckrijte.workmanagerExample.iOSBackgroundAppRefresh";
+ "dev.fluttercommunity.workmanagerExample.iOSBackgroundAppRefresh";
const iOSBackgroundProcessingTask =
- "be.tramckrijte.workmanagerExample.iOSBackgroundProcessingTask";
+ "dev.fluttercommunity.workmanagerExample.iOSBackgroundProcessingTask";
final List allTasks = [
simpleTaskKey,
@@ -37,7 +40,9 @@ final List allTasks = [
// Pragma is mandatory if the App is obfuscated or using Flutter 3.1+
@pragma('vm:entry-point')
void callbackDispatcher() {
+ log('callbackDispatcher called');
Workmanager().executeTask((task, inputData) async {
+ log("callbackDispatcher called with task: $task");
final prefs = await SharedPreferences.getInstance();
await prefs.reload();
@@ -91,6 +96,8 @@ void callbackDispatcher() {
return Future.value(false);
}
+ // Return true to indicate that the task was successful
+ print("$task finished successfully");
return Future.value(true);
});
}
@@ -146,8 +153,8 @@ class _MyAppState extends State {
style: Theme.of(context).textTheme.headlineSmall,
),
- //This task runs once.
- //Most likely this will trigger immediately
+ // This task runs once.
+ // Most likely this will trigger immediately
ElevatedButton(
child: Text("Register OneOff Task"),
onPressed: () {
@@ -160,6 +167,7 @@ class _MyAppState extends State {
'double': 1.0,
'string': 'string',
'array': [1, 2, 3],
+ // 'map': {'key': 'value'},
},
);
},
@@ -205,16 +213,17 @@ class _MyAppState extends State {
//It will wait at least 10 seconds before its first launch
//Since we have not provided a frequency it will be the default 15 minutes
ElevatedButton(
- child: Text("Register Periodic Task (Android)"),
- onPressed: Platform.isAndroid
- ? () {
- Workmanager().registerPeriodicTask(
- simplePeriodicTask,
- simplePeriodicTask,
- initialDelay: Duration(seconds: 10),
- );
- }
- : null),
+ child: Text("Register Periodic Task (Android)"),
+ onPressed: Platform.isAndroid
+ ? () {
+ Workmanager().registerPeriodicTask(
+ simplePeriodicTask,
+ simplePeriodicTask,
+ initialDelay: Duration(seconds: 10),
+ );
+ }
+ : null,
+ ),
//This task runs periodically
//It will run about every hour
ElevatedButton(
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index d099a30c..3b72e212 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -3,7 +3,7 @@ description: Demonstrates how to use the workmanager plugin.
publish_to: 'none'
environment:
- sdk: ">=3.2.0 <4.0.0"
+ sdk: ">=3.5.0 <4.0.0"
dependencies:
path_provider: ^2.1.5
@@ -11,14 +11,15 @@ dependencies:
permission_handler: ^11.3.1
flutter:
sdk: flutter
- workmanager:
-
+ workmanager:
+ uuid: ^4.4.2
dev_dependencies:
integration_test:
sdk: flutter
flutter_test:
sdk: flutter
+ flutter_lints: ^6.0.0
flutter:
uses-material-design: true
\ No newline at end of file
diff --git a/melos.yaml b/melos.yaml
index 902e5a03..64dece96 100644
--- a/melos.yaml
+++ b/melos.yaml
@@ -1,10 +1,17 @@
name: workmanager
packages:
- workmanager
+ - workmanager_platform_interface
+ - workmanager_android
+ - workmanager_apple
- example
scripts:
get: melos exec -- dart pub get
+ test:
+ run: melos exec --depends-on="flutter_test" -- "flutter test"
+ description: Run tests for all packages with flutter_test dependency.
+
generate:dart:
run: melos exec -c 1 --depends-on="build_runner" --no-flutter -- "dart run build_runner build --delete-conflicting-outputs"
description: Build all generated files for Dart packages in this project.
diff --git a/workmanager/CHANGELOG.md b/workmanager/CHANGELOG.md
index 824fbf2c..02d7e6a1 100644
--- a/workmanager/CHANGELOG.md
+++ b/workmanager/CHANGELOG.md
@@ -1,3 +1,41 @@
+# 0.8.0
+
+## Major Architecture Changes
+* **BREAKING**: Migrate to federated plugin architecture for better platform extensibility
+* **BREAKING**: Platform-specific implementations moved to separate packages
+* Create `workmanager_platform_interface` for shared platform interface
+* Create `workmanager_android` package with Android WorkManager implementation
+* Create `workmanager_apple` package with iOS BGTaskScheduler implementation
+* Foundation for future macOS support using NSBackgroundActivityScheduler
+
+## Breaking Changes
+* **BREAKING**: Enum values changed from snake_case to camelCase:
+ * `NetworkType` values: `not_required` → `notRequired`, `not_roaming` → `notRoaming`, `metered` → `metered` (unchanged)
+ * `OutOfQuotaPolicy` values: `run_as_non_expedited_work_request` → `runAsNonExpeditedWorkRequest`, `drop_work_request` → `dropWorkRequest`
+* **BREAKING**: Removed JSON serialization for inputData - now uses native Map transfer for better performance and type safety
+
+## New Features
+* Android: Added `isScheduledByUniqueName` method to check if a periodic task is scheduled by its unique name (Android only)
+* Added comprehensive integration tests for better reliability
+
+## Bug Fixes
+* iOS: Fixed `initialDelaySeconds` parameter handling - was previously ignored
+* Android: Fixed NullPointerException when `isInDebugMode` was not properly initialized
+* Fixed inputData type handling across platforms - now properly supports all primitive types and lists
+* iOS: Fixed compilation errors with Map handling
+* iOS: Fixed swapped constraints bug for requiresNetworkConnectivity and requiresExternalPower by @thegriffen (from PR #562)
+* Android: Fixed v2 embedding import in BackgroundWorker by @jogapps (from PR #595)
+
+## Improvements
+* Updated to Flutter 3.32 and flutter_lints 6.0.0
+* Android: Updated target SDK to 35
+* Improved CI/CD with Android emulator caching for faster builds
+* Better error handling and type safety throughout the codebase
+* iOS: Add Privacy Manifest for App Store compliance by @navaronbracke (from PR #555)
+* iOS: Replace print statements with proper os_log for better logging
+* iOS: printScheduledTasks now returns String instead of void by @yarith28 (from PR #585)
+* Android: Fix documentation formatting and typo in BackgroundWorker by @jogapps (from PR #595)
+
# 0.7.0
* **BREAKING**: Minimum Dart SDK bumped to 3.2.0
@@ -238,7 +276,7 @@
```xml
+ package="dev.fluttercommunity.workmanager_example">