From ffe13e61e69063e4f296b735c18f96e058aa8c50 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 15:06:17 +0100 Subject: [PATCH 01/25] feat: create comprehensive docs.page documentation structure - Dramatically shortened README by 90% to focus on quick start - Created usecase-driven documentation with platform specifics integrated - Added comprehensive docs.page configuration with proper navigation - Created detailed platform setup guides (Android/iOS) - Added usecase-driven guides: data sync, file uploads, cleanup, notifications - Restructured documentation to be more actionable and user-focused - README now refers to docs.page for comprehensive documentation --- README.md | 440 +++----------------------- docs.json | 111 ++++++- docs/installation.mdx | 78 +++++ docs/introduction.mdx | 84 +++++ docs/quickstart.mdx | 219 +++++++++++++ docs/setup/android.mdx | 127 ++++++++ docs/setup/ios.mdx | 297 +++++++++++++++++ docs/usecases/data-sync.mdx | 358 +++++++++++++++++++++ docs/usecases/fetch-notifications.mdx | 79 +++++ docs/usecases/periodic-cleanup.mdx | 78 +++++ docs/usecases/upload-files.mdx | 287 +++++++++++++++++ 11 files changed, 1759 insertions(+), 399 deletions(-) create mode 100644 docs/installation.mdx create mode 100644 docs/introduction.mdx create mode 100644 docs/quickstart.mdx create mode 100644 docs/setup/android.mdx create mode 100644 docs/setup/ios.mdx create mode 100644 docs/usecases/data-sync.mdx create mode 100644 docs/usecases/fetch-notifications.mdx create mode 100644 docs/usecases/periodic-cleanup.mdx create mode 100644 docs/usecases/upload-files.mdx diff --git a/README.md b/README.md index ef957757..385effda 100644 --- a/README.md +++ b/README.md @@ -7,426 +7,74 @@ [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/fluttercommunity/flutter_workmanager/test.yml?branch=main&label=tests)](https://github.com/fluttercommunity/flutter_workmanager/actions) [![license](https://img.shields.io/badge/license-MIT-blue.svg)](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. +Execute Dart code in the background, even when your app is closed. Perfect for data sync, file uploads, and periodic maintenance tasks. -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. +## πŸ“– Full Documentation -This is especially useful to run periodic tasks, such as fetching remote data on a regular basis. +**Visit our comprehensive documentation site:** https://docs.workmanager.dev -> This plugin was featured in this [Medium blogpost](https://medium.com/vrt-digital-studio/flutter-workmanager-81e0cfbd6f6e) +## ⚑ Quick Start -## 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. - -- [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? +### 1. Install +```yaml +dependencies: + workmanager: ^0.8.0 +``` -See sample folder for a complete working example. -Before registering any task, the WorkManager plugin must be initialized. +### 2. Platform Setup +- **Android**: Works automatically βœ… +- **iOS**: [5-minute setup required](https://docs.workmanager.dev/setup/ios) +### 3. Initialize & Use ```dart -@pragma('vm:entry-point') // Mandatory if the App is obfuscated or using Flutter 3.1+ +@pragma('vm:entry-point') void callbackDispatcher() { Workmanager().executeTask((task, inputData) { - print("Native called background task: $task"); //simpleTask will be emitted here. + print("Background task: $task"); + // Your background logic here return Future.value(true); }); } void main() { - Workmanager().initialize( - callbackDispatcher, // The top level function, aka callbackDispatcher - isInDebugMode: true // If enabled it will post a notification whenever the task is running. Handy for debugging tasks + Workmanager().initialize(callbackDispatcher); + + // Schedule a task + Workmanager().registerPeriodicTask( + "sync-task", + "data-sync", + frequency: Duration(hours: 1), ); - Workmanager().registerOneOffTask("task-identifier", "simpleTask"); + runApp(MyApp()); } ``` -> The `callbackDispatcher` needs to be either a static function or a top level function to be accessible as a Flutter entry point. - -The workmanager runs on a separate isolate from the main flutter isolate. Ensure to initialize all dependencies inside the `Workmanager().executeTask`. - -##### Debugging tips - -Wrap the code inside your `Workmanager().executeTask` in a `try and catch` in order to catch any exceptions thrown. - -```dart -@pragma('vm:entry-point') -void callbackDispatcher() { - Workmanager().executeTask((task, inputData) async { - - int? totalExecutions; - final _sharedPreference = await SharedPreferences.getInstance(); //Initialize dependency - - try { //add code execution - totalExecutions = _sharedPreference.getInt("totalExecutions"); - _sharedPreference.setInt("totalExecutions", totalExecutions == null ? 1 : totalExecutions+1); - } catch(err) { - Logger().e(err.toString()); // Logger flutter package, prints error on the debug console - throw Exception(err); - } - - return Future.value(true); - }); -} -``` - -Android tasks are identified using their `taskName`. -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. - ---- - -# Work Result +## 🎯 Common Use Cases -The `Workmanager().executeTask(...` block supports 3 possible outcomes: +| Use Case | Documentation | +|----------|---------------| +| **Sync data from API** | [Data Sync Guide](https://docs.workmanager.dev/usecases/data-sync) | +| **Upload files in background** | [File Upload Guide](https://docs.workmanager.dev/usecases/upload-files) | +| **Clean up old data** | [Cleanup Guide](https://docs.workmanager.dev/usecases/periodic-cleanup) | +| **Fetch notifications** | [Notifications Guide](https://docs.workmanager.dev/usecases/fetch-notifications) | -1. `Future.value(true)`: The task is successful. -2. `Future.value(false)`: The task did not complete successfully and needs to be retried. On Android, the retry is done automatically. On iOS (when using BGTaskScheduler), the retry needs to be scheduled manually. -3. `Future.error(...)`: The task failed. +## πŸ—οΈ Architecture -On Android, the `BackoffPolicy` will configure how `WorkManager` is going to retry the task. +This plugin uses a **federated architecture**: +- `workmanager` - Main package (this one) +- `workmanager_android` - Android implementation +- `workmanager_apple` - iOS/macOS implementation +- `workmanager_platform_interface` - Shared interface -Refer to the example app for a successful, retrying and a failed task. +All packages are automatically included when you add `workmanager` to pubspec.yaml. -# iOS specific setup and note +## πŸ› Issues & Support -Initialize Workmanager only once. -Background app refresh can only be tested on a real device, it cannot be tested on a simulator. +- **Bug reports**: [GitHub Issues](https://github.com/fluttercommunity/flutter_workmanager/issues) +- **Questions**: [GitHub Discussions](https://github.com/fluttercommunity/flutter_workmanager/discussions) +- **Documentation**: https://docs.workmanager.dev -### 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 +## πŸš€ Example App -### 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), // 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: "dev.fluttercommunity.workmanagerExample.iOSBackgroundAppRefresh", frequency: NSNumber(value: 20 * 60)) -``` - -Then schedule the task from your App -```dart -const iOSBackgroundAppRefresh = "dev.fluttercommunity.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 = "dev.fluttercommunity.workmanagerExample.iOSBackgroundProcessingTask"; -Workmanager().registerProcessingTask( - iOSBackgroundProcessingTask, - iOSBackgroundProcessingTask, - initialDelay: Duration(minutes: 2), - constraints: Constraints( - // Connected or metered mark the task as requiring internet - networkType: NetworkType.connected, - // Require external power - requiresCharging: true, - ), -); -``` - -### 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. - -Use `permision_handler` to check for the permission: - -``` dart -final status = await Permission.backgroundRefresh.status; -if (status != PermissionStatus.granted) { - _showNoPermission(context, status); - return; -} -``` - -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. - -Two kinds of background tasks can be registered : - -- **One off task** : runs only once -- **Periodic tasks** : runs indefinitely on a regular basis - -```dart -// One off task registration -Workmanager().registerOneOffTask( - "oneoff-task-identifier", - "simpleTask" -); - -// Periodic task registration -Workmanager().registerPeriodicTask( - "periodic-task-identifier", - "simplePeriodicTask", - // When no frequency is provided the default 15 minutes is set. - // Minimum frequency is 15 min. Android will automatically change your frequency to 15 min if you have configured a lower frequency. - frequency: Duration(hours: 1), -) -``` - -Each task must have an **unique name**; -This allows cancellation of a started task. -The second parameter is the `String` that will be sent to your `callbackDispatcher` function, indicating the task's _type_. - -## Tagging - -You can set the optional `tag` property. -Handy for cancellation by `tag`. -This is different from the unique name in that you can group multiple tasks under one tag. - -```dart -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` - -```dart -Workmanager().registerOneOffTask("1", "simpleTask", existingWorkPolicy: ExistingWorkPolicy.append); -``` - -## Initial Delay - -Indicates how along a task should waitbefore its first run. - -```dart -Workmanager().registerOneOffTask("1", "simpleTask", initialDelay: Duration(seconds: 10)); -``` - -## Constraints - -> Constraints are mapped at best effort to each platform. Android's WorkManager supports most of the specific constraints, whereas iOS tasks are limited. - -- NetworkType - Constrains the type of network required for your work to run. For example, Connected. - The `NetworkType` lists various network conditions. `.connected` & `.metered` will be mapped to [`requiresNetworkConnectivity`](https://developer.apple.com/documentation/backgroundtasks/bgprocessingtaskrequest/3142242-requiresnetworkconnectivity) on iOS. -- RequiresBatteryNotLow (Android only) - When set to true, your work will not run if the device is in low battery mode. - **Enabling the battery saving mode on the android device prevents the job from running** -- RequiresCharging - When set to true, your work will only run when the device is charging. -- RequiresDeviceIdle (Android only) - When set to true, this requires the user’s device to be idle before the work will run. This can be useful for running batched operations that might otherwise have a - negative performance impact on other apps running actively on the user’s device. -- RequiresStorageNotLow (Android only) - When set to true, your work will not run if the user’s storage space on the device is too low. - -```dart -Workmanager().registerOneOffTask( - "1", - "simpleTask", - constraints: Constraints( - networkType: NetworkType.connected, - requiresBatteryNotLow: true, - requiresCharging: true, - requiresDeviceIdle: true, - requiresStorageNotLow: true - ) -); -``` - -### InputData - -Add some input data for your task. Valid value types are: `int`, `bool`, `double`, `String` and their `list` - -```dart - Workmanager().registerOneOffTask( - "1", - "simpleTask", - inputData: { - 'int': 1, - 'bool': true, - 'double': 1.0, - 'string': 'string', - 'array': [1, 2, 3], - }, -); -``` - -## BackoffPolicy - -Indicates the waiting strategy upon task failure. -The default is `BackoffPolicy.exponential`. -You can also specify the delay. - -```dart -Workmanager().registerOneOffTask("1", "simpleTask", backoffPolicy: BackoffPolicy.exponential, backoffPolicyDelay: Duration(seconds: 10)); -``` - -## Cancellation - -A task can be cancelled in different ways : - -### By Tag - -Cancels the task that was previously registered using this **Tag**, if any. - -```dart -Workmanager().cancelByTag("tag"); -``` - -### By Unique Name - -```dart -Workmanager().cancelByUniqueName(""); -``` - -### All - -```dart -Workmanager().cancelAll(); -``` - - -# Building project - -Project was migrated to [Melos](https://pub.dev/packages/melos) so build steps has changed. - -1. Install melos - -``` -dart pub global activate melos -``` - -2. In project root bootstrap - -``` -melos bootstrap -``` - -3. Get packages - -``` -melos run get -``` - -## Code Generation - -This project uses [Pigeon](https://pub.dev/packages/pigeon) for type-safe platform channel communication. If you modify the platform interface: - -**⚠️ IMPORTANT**: Always use melos to regenerate Pigeon files: - -```bash -melos run generate:pigeon -``` - -**DO NOT** run pigeon directly - always use the melos script for consistency. - -## Running the example - -Now you should be able to run example project - -``` -cd example -flutter run -``` +See the [example folder](./example/) for a complete working demo with all task types and platform configurations. \ No newline at end of file diff --git a/docs.json b/docs.json index efff7df7..ba1d6ebc 100644 --- a/docs.json +++ b/docs.json @@ -1,5 +1,110 @@ { - "name": "Workmanager", - "description": "Workmanager" + "name": "Flutter Workmanager", + "$schema": "https://mintlify.com/schema.json", + "logo": { + "dark": "/logo/dark.svg", + "light": "/logo/light.svg" + }, + "favicon": "/favicon.svg", + "colors": { + "primary": "#0D9373", + "light": "#07C983", + "dark": "#0D9373", + "anchors": { + "from": "#0D9373", + "to": "#07C983" + } + }, + "topbarLinks": [ + { + "name": "Support", + "url": "https://github.com/fluttercommunity/flutter_workmanager/issues" + } + ], + "topbarCtaButton": { + "name": "GitHub", + "url": "https://github.com/fluttercommunity/flutter_workmanager" + }, + "tabs": [ + { + "name": "API Reference", + "url": "api-reference" + } + ], + "anchors": [ + { + "name": "Documentation", + "icon": "book-open-cover", + "url": "https://pub.dev/documentation/workmanager/latest/" + }, + { + "name": "Community", + "icon": "github", + "url": "https://github.com/fluttercommunity/flutter_workmanager" + } + ], + "navigation": [ + { + "group": "Get Started", + "pages": [ + "introduction", + "installation", + "quickstart" + ] + }, + { + "group": "Use Cases", + "pages": [ + "usecases/data-sync", + "usecases/periodic-cleanup", + "usecases/upload-files", + "usecases/fetch-notifications", + "usecases/database-maintenance" + ] + }, + { + "group": "Task Management", + "pages": [ + "tasks/scheduling", + "tasks/constraints", + "tasks/cancellation" + ] + }, + { + "group": "Platform Setup", + "pages": [ + "setup/android", + "setup/ios" + ] + }, + { + "group": "Debugging & Testing", + "pages": [ + "debugging/overview", + "debugging/notifications", + "debugging/troubleshooting" + ] + }, + { + "group": "Advanced", + "pages": [ + "advanced/architecture", + "advanced/migration", + "advanced/publishing" + ] + }, + { + "group": "API Reference", + "pages": [ + "api-reference/workmanager", + "api-reference/workmanager-android", + "api-reference/workmanager-apple", + "api-reference/platform-interface" + ] + } + ], + "footerSocials": { + "github": "https://github.com/fluttercommunity/flutter_workmanager", + "discord": "https://discord.gg/rflutterdev" } - \ No newline at end of file +} \ No newline at end of file diff --git a/docs/installation.mdx b/docs/installation.mdx new file mode 100644 index 00000000..9b7e5e8d --- /dev/null +++ b/docs/installation.mdx @@ -0,0 +1,78 @@ +--- +title: Installation +description: "Install Flutter Workmanager and configure your project" +--- + +# Installation + +## Add to pubspec.yaml + +```yaml pubspec.yaml +dependencies: + workmanager: ^0.8.0 +``` + +```bash +flutter pub get +``` + +## Platform Configuration + +Before using background tasks, you need to configure each platform: + + + + 5-minute Android WorkManager setup + + + iOS background processing configuration + + + + +Background tasks **will not work** without proper platform configuration. Complete the setup for your target platforms before proceeding. + + +## Initialize Workmanager + +Add this to your `main.dart`: + +```dart main.dart +import 'package:workmanager/workmanager.dart'; + +@pragma('vm:entry-point') +void callbackDispatcher() { + Workmanager().executeTask((task, inputData) { + // Your background task logic here + return Future.value(true); + }); +} + +void main() { + Workmanager().initialize(callbackDispatcher); + runApp(MyApp()); +} +``` + + +You only need to initialize Workmanager once in your app. The federated architecture automatically handles platform-specific implementations. + + +## Next Steps + +Choose your use case to get started quickly: + + + + Fetch remote data in the background + + + Remove old files and cache data + + + Upload photos and documents in background + + + Fetch new notifications from server + + \ No newline at end of file diff --git a/docs/introduction.mdx b/docs/introduction.mdx new file mode 100644 index 00000000..39f044ab --- /dev/null +++ b/docs/introduction.mdx @@ -0,0 +1,84 @@ +--- +title: Introduction +description: "Flutter WorkManager - Background task execution for Flutter apps" +--- + +# Flutter Workmanager + +[![pub package](https://img.shields.io/pub/v/workmanager.svg)](https://pub.dartlang.org/packages/workmanager) +[![pub points](https://img.shields.io/pub/points/workmanager)](https://pub.dev/packages/workmanager/score) +[![likes](https://img.shields.io/pub/likes/workmanager)](https://pub.dev/packages/workmanager/score) +[![popularity](https://img.shields.io/pub/popularity/workmanager)](https://pub.dev/packages/workmanager/score) +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/fluttercommunity/flutter_workmanager/test.yml?branch=main&label=tests)](https://github.com/fluttercommunity/flutter_workmanager/actions) +[![license](https://img.shields.io/badge/license-MIT-blue.svg)](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. 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: + + + + The main package that provides the unified API + + + The common platform interface + + + Android-specific implementation + + + 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. + +## Key Features + + + + Execute Dart code in the background on both Android and iOS platforms + + + + Support for one-off tasks, periodic tasks, and iOS processing tasks + + + + Configure network, battery, charging, and other constraints for task execution + + + + Built-in debugging with notifications to track background task execution + + + +## Supported Platforms + +| Platform | Support | Notes | +|----------|---------|--------| +| Android | βœ… Full | All WorkManager features supported | +| iOS | βœ… Full | Background Fetch + BGTaskScheduler APIs | +| macOS | 🚧 Planned | Future support through NSBackgroundActivityScheduler | +| Web | ❌ Not supported | Background execution not available | +| Windows/Linux | ❌ Not supported | No background task APIs | + +## Getting Started + +Ready to add background tasks to your Flutter app? Let's get started! + + + Learn how to set up and use Flutter Workmanager in your app + \ No newline at end of file diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx new file mode 100644 index 00000000..1d0b7856 --- /dev/null +++ b/docs/quickstart.mdx @@ -0,0 +1,219 @@ +--- +title: Quick Start +description: "Get started with Flutter Workmanager in minutes" +--- + +# Quick Start + +This guide will help you set up Flutter Workmanager and create your first background task in just a few minutes. + +## Installation + +Add `workmanager` to your `pubspec.yaml`: + +```yaml pubspec.yaml +dependencies: + workmanager: ^0.8.0 +``` + +Then run: + +```bash +flutter pub get +``` + + +The platform-specific packages (`workmanager_android`, `workmanager_apple`) are automatically included through the federated plugin system. You don't need to add them manually. + + +## Platform Setup + +Before using Workmanager, you need to configure platform-specific settings: + + + + Configure Android WorkManager + + + Configure iOS background processing + + + +## Basic Usage + +Here's a complete example to get you started: + +```dart main.dart +import 'package:flutter/material.dart'; +import 'package:workmanager/workmanager.dart'; + +@pragma('vm:entry-point') // Mandatory if the App is obfuscated or using Flutter 3.1+ +void callbackDispatcher() { + Workmanager().executeTask((task, inputData) { + print("Native called background task: $task"); // simpleTask will be emitted here + return Future.value(true); + }); +} + +void main() { + // Initialize the plugin + Workmanager().initialize( + callbackDispatcher, // The top level function, aka callbackDispatcher + isInDebugMode: true // If enabled it will post a notification whenever the task is running + ); + + // Register a one-off task + Workmanager().registerOneOffTask("task-identifier", "simpleTask"); + + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Workmanager Example', + home: MyHomePage(), + ); + } +} + +class MyHomePage extends StatefulWidget { + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Workmanager Example'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () { + Workmanager().registerOneOffTask( + "one-off-task", + "simpleTask", + ); + }, + child: Text('Register One-Off Task'), + ), + SizedBox(height: 20), + ElevatedButton( + onPressed: () { + Workmanager().registerPeriodicTask( + "periodic-task", + "periodicTask", + frequency: Duration(minutes: 15), + ); + }, + child: Text('Register Periodic Task'), + ), + ], + ), + ), + ); + } +} +``` + +## Key Concepts + + + + The `callbackDispatcher` is a top-level function that executes your background tasks. It must be either a static function or a top-level function to be accessible as a Flutter entry point. + + + + - **Android**: Tasks are identified using their `taskName` (second parameter) + - **iOS**: Tasks are identified using their `taskIdentifier` (first parameter) + - **iOS Background Fetch**: Use the constant `Workmanager.iOSBackgroundTask` + + + + The workmanager runs on a separate isolate from the main flutter isolate. Ensure to initialize all dependencies inside the `Workmanager().executeTask`. + + + +## Work Results + +Your background task can return three possible outcomes: + + + + `Future.value(true)` - Task completed successfully + + + `Future.value(false)` - Task needs to be retried + + + `Future.error(...)` - Task failed permanently + + + + +On Android, the `BackoffPolicy` configures how WorkManager retries failed tasks. On iOS with BGTaskScheduler, retries need to be scheduled manually. + + +## Debugging Tips + +### Enable Debug Mode + +When initializing Workmanager, set `isInDebugMode: true` to receive notifications whenever background tasks run: + +```dart +Workmanager().initialize( + callbackDispatcher, + isInDebugMode: true // Shows notifications for debugging +); +``` + +### Add Error Handling + +Wrap your task code in try-catch blocks: + +```dart +@pragma('vm:entry-point') +void callbackDispatcher() { + Workmanager().executeTask((task, inputData) async { + try { + // Your background task code here + print("Executing task: $task"); + + // Example: Save data to SharedPreferences + final prefs = await SharedPreferences.getInstance(); + final count = prefs.getInt('task_count') ?? 0; + await prefs.setInt('task_count', count + 1); + + return Future.value(true); + } catch (err) { + print("Task failed: $err"); + return Future.value(false); + } + }); +} +``` + +## Next Steps + +Now that you have a basic understanding, explore more advanced features: + + + + Learn about different types of background tasks + + + Configure when tasks should run + + + Understand platform-specific behaviors + + + See more detailed examples + + \ No newline at end of file diff --git a/docs/setup/android.mdx b/docs/setup/android.mdx new file mode 100644 index 00000000..be2cfd12 --- /dev/null +++ b/docs/setup/android.mdx @@ -0,0 +1,127 @@ +--- +title: Android Setup +description: "Configure Android WorkManager for background tasks" +--- + +# Android Setup + +Configure your Android app to support background tasks using WorkManager. + +## Prerequisites + +Ensure your `AndroidManifest.xml` has Flutter v2 embedding: + +```xml android/app/src/main/AndroidManifest.xml + +``` + + +If you don't have this, follow the [Flutter v2 embedding upgrade guide](https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects). + + +## That's It! + +Android setup is automatic. The `workmanager_android` package handles all WorkManager configuration internally. + +## Debugging + +Enable debug notifications to see when background tasks run: + +```dart +Workmanager().initialize( + callbackDispatcher, + isInDebugMode: true, // Shows notifications when tasks run +); +``` + +
+ Android debug notifications +
+ +## Testing Background Tasks + +Since Android controls when background tasks run, testing can be challenging: + + + + Use `initialDelay` to test tasks quickly: + ```dart + Workmanager().registerOneOffTask( + "test_task", + "my_task", + initialDelay: Duration(seconds: 10), // Runs in 10 seconds + ); + ``` + + + + On debug builds, you can use ADB to force task execution: + ```bash + adb shell cmd jobscheduler run -f + ``` + + + + Check if tasks are scheduled: + ```bash + adb shell dumpsys jobscheduler | grep + ``` + + + +## Battery Optimization + +Modern Android devices aggressively optimize battery usage. Users may need to: + +1. **Disable battery optimization** for your app in Settings +2. **Allow background activity** in app-specific settings +3. **Keep the app in recent apps** (don't swipe away) + + +You cannot programmatically disable battery optimization. Users must do this manually in device settings. + + +## Common Issues + + + + **Possible causes:** + - Battery optimization is enabled for your app + - Device is in Doze mode + - App has been idle for too long + - Device storage is low + + **Solutions:** + - Ask users to disable battery optimization + - Use appropriate constraints (network, charging, etc.) + - Encourage regular app usage + + + + **Android respects minimum intervals:** + - Periodic tasks: 15-minute minimum + - One-off tasks: Can run immediately but respect constraints + + **Use constraints to optimize:** + ```dart + constraints: Constraints( + networkType: NetworkType.connected, + requiresBatteryNotLow: true, + requiresCharging: false, + ) + ``` + + + +## Next Steps + + + + Configure iOS background processing + + + Start with common use cases + + \ No newline at end of file diff --git a/docs/setup/ios.mdx b/docs/setup/ios.mdx new file mode 100644 index 00000000..143b8c60 --- /dev/null +++ b/docs/setup/ios.mdx @@ -0,0 +1,297 @@ +--- +title: iOS Setup +description: "Configure iOS background processing with BGTaskScheduler" +--- + +# iOS Setup + +Configure your iOS app to support background tasks using BGTaskScheduler and Background Fetch APIs. + +## Step 1: Enable Background Modes + +In Xcode, select your target and go to **Signing & Capabilities**: + +1. Click **+ Capability** +2. Add **Background Modes** +3. Check **Background processing** and **Background fetch** + +
+ iOS Background Modes +
+ +This adds to your `Info.plist`: +```xml Info.plist +UIBackgroundModes + + processing + fetch + +``` + +## Step 2: Register Task Identifiers + +Add your task identifiers to `Info.plist`: + +```xml Info.plist +BGTaskSchedulerPermittedIdentifiers + + dev.yourapp.data_sync + dev.yourapp.file_upload + + +``` + +## Step 3: Configure AppDelegate.swift + +```swift ios/Runner/AppDelegate.swift +import Flutter +import UIKit +import workmanager + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + + // Register background tasks + WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "dev.yourapp.data_sync") + + // For periodic tasks (iOS 13+) + WorkmanagerPlugin.registerPeriodicTask( + withIdentifier: "dev.yourapp.periodic_sync", + frequency: NSNumber(value: 20 * 60) // 20 minutes in seconds + ) + + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} +``` + +## Task Types and Setup + + + + **Use for**: Immediate background work (uploads, quick sync) + + **Characteristics:** + - Starts immediately when scheduled + - Limited to ~30 seconds execution time + - No additional setup needed in AppDelegate + + ```dart + Workmanager().registerOneOffTask( + "upload_task", + "file_upload", + constraints: Constraints(networkType: NetworkType.connected), + ); + ``` + + + + **Use for**: Regular data sync, maintenance tasks + + **Setup required in AppDelegate.swift:** + ```swift + WorkmanagerPlugin.registerPeriodicTask( + withIdentifier: "dev.yourapp.sync_task", + frequency: NSNumber(value: 60 * 60) // 1 hour + ) + ``` + + **Info.plist:** + ```xml + BGTaskSchedulerPermittedIdentifiers + + dev.yourapp.sync_task + + ``` + + **Schedule in Dart:** + ```dart + Workmanager().registerPeriodicTask( + "dev.yourapp.sync_task", + "periodic_sync", + frequency: Duration(hours: 1), // Ignored on iOS - set in AppDelegate + ); + ``` + + + + **Use for**: Long-running tasks (large uploads, heavy processing) + + **Setup in AppDelegate.swift:** + ```swift + WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "dev.yourapp.heavy_task") + ``` + + **Characteristics:** + - Can run for minutes + - Only when device is idle and charging + - iOS may defer execution significantly + + ```dart + Workmanager().registerProcessingTask( + "dev.yourapp.heavy_task", + "heavy_processing", + constraints: Constraints( + requiresCharging: true, + networkType: NetworkType.connected, + ), + ); + ``` + + + +## iOS Limitations & Best Practices + + +iOS background processing has strict limitations that don't exist on Android: + + +### Execution Time Limits +- **One-off tasks**: ~30 seconds +- **Periodic tasks**: ~30 seconds +- **Processing tasks**: Several minutes (when device idle + charging) + +### Scheduling Restrictions +- iOS learns user patterns and schedules accordingly +- Tasks may not run if user hasn't used app recently +- Background App Refresh must be enabled +- No guaranteed execution time + +### Best Practices + + + + ```dart + Future _quickSync() async { + final startTime = DateTime.now(); + + try { + // Your sync logic here + await syncData(); + + // Check if we're running out of time + final elapsed = DateTime.now().difference(startTime); + if (elapsed.inSeconds > 25) { + print('Task taking too long, stopping early'); + return false; // Retry later + } + + return true; + } catch (e) { + return false; + } + } + ``` + + + + ```dart + Future _handleTaskExpiration() async { + final completer = Completer(); + + // Start your work + final workFuture = performWork(); + + // Set a timeout slightly less than iOS limit + final timeoutFuture = Future.delayed( + Duration(seconds: 25), + () => false + ); + + try { + final result = await Future.any([workFuture, timeoutFuture]); + return result; + } catch (e) { + return false; + } + } + ``` + + + + ```dart + Future _incrementalSync() async { + final prefs = await SharedPreferences.getInstance(); + final lastSyncPoint = prefs.getString('last_sync_point') ?? '0'; + + // Only sync what's new since last successful sync + final response = await api.getSince(lastSyncPoint); + + if (response.isSuccess) { + await prefs.setString('last_sync_point', response.nextSyncPoint); + return true; + } + + return false; + } + ``` + + + +## Testing on iOS + +### Debug in Simulator +Background tasks **cannot** be tested in iOS Simulator. Use a physical device. + +### Debug Commands +Use Xcode debugger console to trigger tasks: + +```objc +// Trigger background app refresh +e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"dev.yourapp.sync_task"] + +// Trigger processing task +e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"dev.yourapp.heavy_task"] +``` + +### Monitor Background Execution +```dart +// Print scheduled tasks (iOS 13+ only) +if (Platform.isIOS) { + final tasks = await Workmanager().printScheduledTasks(); + print('Scheduled tasks: $tasks'); +} +``` + +## Troubleshooting + + + + **Check:** + - Background App Refresh is enabled in iOS Settings + - Task identifiers match between Info.plist and AppDelegate.swift + - App has been used recently (iOS learning algorithm) + - Device has been idle/charging (for processing tasks) + + + + **Common causes:** + - User disabled Background App Refresh + - App hasn't been used in several days + - iOS battery optimization kicked in + - Task is taking too long and being terminated + + + + **Make sure:** + - `import workmanager` is added to AppDelegate.swift + - iOS deployment target is 10.0+ in Podfile + - Xcode version is 10.3+ (Swift 4.2+) + + + +## Next Steps + + + + See platform-specific examples + + + Learn to debug background tasks + + \ No newline at end of file diff --git a/docs/usecases/data-sync.mdx b/docs/usecases/data-sync.mdx new file mode 100644 index 00000000..b2faeaa2 --- /dev/null +++ b/docs/usecases/data-sync.mdx @@ -0,0 +1,358 @@ +--- +title: "Sync Data Regularly" +description: "Automatically fetch and sync data from your API in the background" +--- + +# Background Data Synchronization + +Keep your app's data fresh by automatically syncing with your backend API, even when the app is closed. + +## When to Use This + +- News apps fetching latest articles +- Social media apps syncing posts and messages +- E-commerce apps updating product catalogs +- Weather apps refreshing forecasts +- Any app that needs fresh data from an API + +## Complete Implementation + +### 1. Set Up the Background Task + +```dart lib/services/data_sync_service.dart +import 'package:workmanager/workmanager.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; + +class DataSyncService { + static const String syncTaskName = "data_sync_task"; + static const String syncTaskId = "periodic_data_sync"; + + static void initialize() { + Workmanager().initialize(callbackDispatcher); + } + + static void scheduleSync() { + Workmanager().registerPeriodicTask( + syncTaskId, + syncTaskName, + frequency: Duration(hours: 1), // Sync every hour + constraints: Constraints( + networkType: NetworkType.connected, // Require internet + requiresBatteryNotLow: true, // Don't drain battery + ), + ); + } + + static void cancelSync() { + Workmanager().cancelByUniqueName(syncTaskId); + } +} + +@pragma('vm:entry-point') +void callbackDispatcher() { + Workmanager().executeTask((task, inputData) async { + try { + switch (task) { + case DataSyncService.syncTaskName: + return await _performDataSync(); + default: + return Future.value(false); + } + } catch (e) { + print('Background sync failed: $e'); + return Future.value(false); // Retry on next opportunity + } + }); +} + +Future _performDataSync() async { + try { + // Fetch data from your API + final response = await http.get( + Uri.parse('https://api.yourapp.com/data'), + headers: {'Authorization': 'Bearer YOUR_TOKEN'}, + ).timeout(Duration(seconds: 30)); + + if (response.statusCode == 200) { + final data = json.decode(response.body); + + // Store in local database (SQLite, Hive, etc.) + await _storeDataLocally(data); + + print('Data sync completed successfully'); + return true; + } else { + print('API returned ${response.statusCode}'); + return false; // Retry later + } + } catch (e) { + print('Sync error: $e'); + return false; // Retry later + } +} + +Future _storeDataLocally(Map data) async { + // Example using SharedPreferences for simple data + final prefs = await SharedPreferences.getInstance(); + await prefs.setString('last_sync_data', json.encode(data)); + await prefs.setString('last_sync_time', DateTime.now().toIso8601String()); + + // For complex data, use SQLite, Hive, or other local storage +} +``` + +### 2. Initialize in Your App + +```dart main.dart +import 'package:flutter/material.dart'; +import 'services/data_sync_service.dart'; + +void main() { + DataSyncService.initialize(); + runApp(MyApp()); +} + +class MyApp extends StatefulWidget { + @override + _MyAppState createState() => _MyAppState(); +} + +class _MyAppState extends State { + @override + void initState() { + super.initState(); + // Start background sync when app launches + DataSyncService.scheduleSync(); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Data Sync App', + home: DataSyncHomePage(), + ); + } +} +``` + +### 3. Show Sync Status to Users + +```dart lib/widgets/sync_status_widget.dart +class SyncStatusWidget extends StatefulWidget { + @override + _SyncStatusWidgetState createState() => _SyncStatusWidgetState(); +} + +class _SyncStatusWidgetState extends State { + String _lastSyncTime = "Never"; + + @override + void initState() { + super.initState(); + _loadLastSyncTime(); + } + + Future _loadLastSyncTime() async { + final prefs = await SharedPreferences.getInstance(); + final syncTime = prefs.getString('last_sync_time'); + if (syncTime != null) { + final dateTime = DateTime.parse(syncTime); + setState(() { + _lastSyncTime = timeago.format(dateTime); + }); + } + } + + @override + Widget build(BuildContext context) { + return Card( + child: ListTile( + leading: Icon(Icons.sync), + title: Text('Background Sync'), + subtitle: Text('Last sync: $_lastSyncTime'), + trailing: IconButton( + icon: Icon(Icons.refresh), + onPressed: () { + // Trigger immediate sync (optional) + _performManualSync(); + }, + ), + ), + ); + } + + void _performManualSync() { + // Schedule an immediate one-off sync + Workmanager().registerOneOffTask( + "manual_sync_${DateTime.now().millisecondsSinceEpoch}", + DataSyncService.syncTaskName, + constraints: Constraints(networkType: NetworkType.connected), + ); + } +} +``` + +## Platform-Specific Considerations + + + + **Android automatically handles**: + - Task scheduling and retry logic + - Battery optimization (Doze mode) + - App standby mode restrictions + - Network changes and availability + + **Best practices**: + ```dart + // Use appropriate constraints + Workmanager().registerPeriodicTask( + "data_sync", + "sync_task", + frequency: Duration(hours: 1), + constraints: Constraints( + networkType: NetworkType.connected, + requiresBatteryNotLow: true, // Respect battery optimization + requiresCharging: false, // Don't wait for charging + ), + ); + ``` + + **Minimum frequency**: 15 minutes (Android WorkManager limitation) + + + + **iOS has additional restrictions**: + - Background App Refresh must be enabled + - Tasks may be limited to 30 seconds + - iOS learns usage patterns and schedules accordingly + - No guaranteed execution time + + **Setup required in AppDelegate.swift**: + ```swift + import workmanager + + // In application(_:didFinishLaunchingWithOptions:) + WorkmanagerPlugin.registerPeriodicTask( + withIdentifier: "data_sync_task", + frequency: NSNumber(value: 60 * 60) // 1 hour in seconds + ) + ``` + + **Info.plist configuration**: + ```xml + BGTaskSchedulerPermittedIdentifiers + + data_sync_task + + ``` + + **Best practices**: + - Keep sync operations under 30 seconds + - Use incremental sync (only fetch changes) + - Handle task expiration gracefully + + + +## Testing Your Implementation + +### Debug Mode +```dart +Workmanager().initialize( + callbackDispatcher, + isInDebugMode: true, // Shows notifications when tasks run +); +``` + +### Manual Testing +```dart +// Test your sync logic immediately +ElevatedButton( + onPressed: () { + Workmanager().registerOneOffTask( + "test_sync", + DataSyncService.syncTaskName, + initialDelay: Duration(seconds: 5), + ); + }, + child: Text('Test Sync'), +) +``` + +### Monitoring +```dart +Future _checkSyncHealth() async { + final prefs = await SharedPreferences.getInstance(); + final lastSync = prefs.getString('last_sync_time'); + + if (lastSync != null) { + final syncTime = DateTime.parse(lastSync); + final hoursSinceSync = DateTime.now().difference(syncTime).inHours; + + if (hoursSinceSync > 6) { + // Alert: Sync might not be working + _showSyncWarning(); + } + } +} +``` + +## Common Issues & Solutions + + + + **Possible causes**: + - Missing platform setup (Android/iOS configuration) + - App battery optimization preventing background tasks + - No network connectivity when task tries to run + - App hasn't been used recently (iOS restriction) + + **Solutions**: + - Verify platform setup is complete + - Ask users to disable battery optimization for your app + - Use network constraints appropriately + - Encourage regular app usage + + + + **Implement incremental sync**: + ```dart + Future _performIncrementalSync() async { + final prefs = await SharedPreferences.getInstance(); + final lastSyncTime = prefs.getString('last_sync_time') ?? ''; + + final response = await http.get( + Uri.parse('https://api.yourapp.com/data?since=$lastSyncTime'), + ); + + // Only sync changed data + if (response.statusCode == 200) { + final changes = json.decode(response.body); + await _applyChanges(changes); + return true; + } + return false; + } + ``` + + + + **Optimize your sync logic**: + - Use appropriate frequency (not too often) + - Add battery constraints + - Implement smart syncing (only when data actually changed) + - Use compression for large payloads + - Cache API responses appropriately + + + +## Related Use Cases + + + + Upload user content in the background + + + Check for new notifications from your server + + \ No newline at end of file diff --git a/docs/usecases/fetch-notifications.mdx b/docs/usecases/fetch-notifications.mdx new file mode 100644 index 00000000..cbc7019e --- /dev/null +++ b/docs/usecases/fetch-notifications.mdx @@ -0,0 +1,79 @@ +--- +title: "Fetch Notifications" +description: "Check for new notifications and messages from your server in the background" +--- + +# Background Notification Fetching + +Automatically check for new notifications, messages, and updates from your server, even when the app is closed. + +## When to Use This + +- Social media apps checking for new messages +- Email apps fetching new mail +- News apps checking for breaking news +- Chat applications checking for messages +- Any app that needs real-time updates + +## Quick Implementation + +```dart +@pragma('vm:entry-point') +void callbackDispatcher() { + Workmanager().executeTask((task, inputData) async { + if (task == "fetch_notifications") { + return await checkForNotifications(); + } + return Future.value(false); + }); +} + +Future checkForNotifications() async { + try { + final response = await http.get( + Uri.parse('https://api.yourapp.com/notifications/check'), + headers: {'Authorization': 'Bearer YOUR_TOKEN'}, + ); + + if (response.statusCode == 200) { + final data = json.decode(response.body); + if (data['hasNew'] == true) { + await _showLocalNotification(data['message']); + } + return true; + } + return false; + } catch (e) { + return false; + } +} + +void scheduleNotificationCheck() { + Workmanager().registerPeriodicTask( + "notification_check", + "fetch_notifications", + frequency: Duration(minutes: 30), + constraints: Constraints( + networkType: NetworkType.connected, + ), + ); +} +``` + +## Platform Considerations + + + + - Can check frequently (minimum 15 minutes) + - Works reliably in background + - Can show local notifications immediately + - Respects battery optimization settings + + + + - Limited execution time (~30 seconds) + - May not run if app unused recently + - iOS learns user patterns for scheduling + - Background App Refresh must be enabled + + \ No newline at end of file diff --git a/docs/usecases/periodic-cleanup.mdx b/docs/usecases/periodic-cleanup.mdx new file mode 100644 index 00000000..0b696cb7 --- /dev/null +++ b/docs/usecases/periodic-cleanup.mdx @@ -0,0 +1,78 @@ +--- +title: "Periodic Cleanup" +description: "Automatically clean up old files, cache data, and maintain app performance" +--- + +# Periodic Data Cleanup + +Keep your app performing well by automatically cleaning up old files, cached data, and temporary content in the background. + +## When to Use This + +- Clear old cached images and files +- Remove expired data from local databases +- Clean up temporary downloads +- Maintain optimal app storage usage +- Remove old log files + +## Quick Implementation + +```dart +@pragma('vm:entry-point') +void callbackDispatcher() { + Workmanager().executeTask((task, inputData) async { + if (task == "cleanup_task") { + return await performCleanup(); + } + return Future.value(false); + }); +} + +Future performCleanup() async { + try { + // Clean old cache files + await _cleanupCache(); + + // Remove expired database entries + await _cleanupDatabase(); + + // Clear temporary files + await _cleanupTempFiles(); + + return true; + } catch (e) { + print('Cleanup failed: $e'); + return false; + } +} + +void scheduleCleanup() { + Workmanager().registerPeriodicTask( + "cleanup_task", + "cleanup_task", + frequency: Duration(days: 1), // Daily cleanup + constraints: Constraints( + requiresBatteryNotLow: true, + requiresDeviceIdle: true, // Run when device not busy + ), + ); +} +``` + +## Platform Behavior + + + + - Runs reliably in the background + - Respects device idle constraints + - Can clean up large amounts of data + - Minimum frequency: 15 minutes + + + + - Limited to 30 seconds execution time + - Use incremental cleanup approach + - May be deferred if device is busy + - Focus on quick cleanup operations + + \ No newline at end of file diff --git a/docs/usecases/upload-files.mdx b/docs/usecases/upload-files.mdx new file mode 100644 index 00000000..8d8ed530 --- /dev/null +++ b/docs/usecases/upload-files.mdx @@ -0,0 +1,287 @@ +--- +title: "Upload Files in Background" +description: "Upload photos, documents, and other files when network conditions are optimal" +--- + +# Background File Uploads + +Upload user files in the background, even when the app is closed, ensuring reliable delivery when network conditions are good. + +## When to Use This + +- Photo backup apps +- Document sharing apps +- Social media with image/video uploads +- Cloud storage synchronization +- Large file transfers that shouldn't block the UI + +## Complete Implementation + +### 1. Queue Files for Upload + +```dart lib/services/upload_service.dart +import 'package:workmanager/workmanager.dart'; +import 'package:http/http.dart' as http; +import 'dart:io'; + +class UploadService { + static const String uploadTaskName = "file_upload_task"; + + static void initialize() { + Workmanager().initialize(callbackDispatcher); + } + + static Future queueFileUpload(String filePath, Map metadata) async { + final taskId = "upload_${DateTime.now().millisecondsSinceEpoch}"; + + Workmanager().registerOneOffTask( + taskId, + uploadTaskName, + inputData: { + 'filePath': filePath, + 'metadata': metadata, + 'taskId': taskId, + }, + constraints: Constraints( + networkType: NetworkType.connected, + requiresBatteryNotLow: true, + ), + ); + } +} + +@pragma('vm:entry-point') +void callbackDispatcher() { + Workmanager().executeTask((task, inputData) async { + if (task == UploadService.uploadTaskName) { + return await _uploadFile(inputData!); + } + return Future.value(false); + }); +} + +Future _uploadFile(Map inputData) async { + try { + final filePath = inputData['filePath'] as String; + final metadata = inputData['metadata'] as Map; + final taskId = inputData['taskId'] as String; + + final file = File(filePath); + if (!await file.exists()) { + print('File not found: $filePath'); + return true; // Don't retry if file doesn't exist + } + + // Create multipart request + final request = http.MultipartRequest( + 'POST', + Uri.parse('https://api.yourapp.com/upload'), + ); + + request.headers['Authorization'] = 'Bearer YOUR_TOKEN'; + request.fields.addAll(metadata.map((k, v) => MapEntry(k, v.toString()))); + + final multipartFile = await http.MultipartFile.fromPath( + 'file', + filePath, + ); + request.files.add(multipartFile); + + final response = await request.send().timeout(Duration(minutes: 5)); + + if (response.statusCode == 200) { + print('Upload successful: $taskId'); + await _markUploadComplete(filePath, taskId); + return true; + } else { + print('Upload failed with status: ${response.statusCode}'); + return false; // Retry later + } + } catch (e) { + print('Upload error: $e'); + return false; // Retry later + } +} + +Future _markUploadComplete(String filePath, String taskId) async { + final prefs = await SharedPreferences.getInstance(); + final completed = prefs.getStringList('completed_uploads') ?? []; + completed.add(taskId); + await prefs.setStringList('completed_uploads', completed); +} +``` + +### 2. UI Integration + +```dart lib/widgets/file_upload_widget.dart +class FileUploadWidget extends StatefulWidget { + @override + _FileUploadWidgetState createState() => _FileUploadWidgetState(); +} + +class _FileUploadWidgetState extends State { + List _pendingUploads = []; + + @override + void initState() { + super.initState(); + _loadPendingUploads(); + } + + Future _selectAndQueueFile() async { + final result = await FilePicker.platform.pickFiles(); + if (result != null) { + final file = result.files.single; + final filePath = file.path!; + + // Queue for background upload + await UploadService.queueFileUpload(filePath, { + 'filename': file.name, + 'size': file.size, + 'type': file.extension, + 'userId': 'current_user_id', + }); + + setState(() { + _pendingUploads.add(file.name); + }); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('File queued for upload: ${file.name}')), + ); + } + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + ElevatedButton.icon( + onPressed: _selectAndQueueFile, + icon: Icon(Icons.cloud_upload), + label: Text('Select File to Upload'), + ), + if (_pendingUploads.isNotEmpty) ...[ + SizedBox(height: 16), + Text('Pending Uploads:', style: Theme.of(context).textTheme.subtitle1), + ...(_pendingUploads.map((filename) => ListTile( + leading: Icon(Icons.schedule), + title: Text(filename), + subtitle: Text('Waiting for background upload...'), + ))), + ], + ], + ); + } +} +``` + +## Platform-Specific Behavior + + + + **Automatic handling:** + - Large file uploads continue even if app is killed + - Respects data saver mode and metered networks + - Retries with exponential backoff on failure + + **Optimize for Android:** + ```dart + Workmanager().registerOneOffTask( + taskId, + uploadTaskName, + inputData: inputData, + constraints: Constraints( + networkType: NetworkType.unmetered, // Use WiFi when possible + requiresCharging: true, // For large files + requiresDeviceIdle: false, // Upload even when device in use + ), + backoffPolicy: BackoffPolicy.exponential, + backoffPolicyDelay: Duration(minutes: 1), + ); + ``` + + + + **iOS limitations:** + - 30-second execution limit for most tasks + - Use BGProcessingTask for longer uploads + - May be deferred until device is idle and charging + + **For large files, use processing tasks:** + ```dart + // Register in AppDelegate.swift + WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "large_file_upload") + + // Schedule in Dart + Workmanager().registerProcessingTask( + taskId, + uploadTaskName, + constraints: Constraints( + networkType: NetworkType.connected, + requiresCharging: true, // iOS prefers charging for processing tasks + ), + ); + ``` + + + +## Advanced Features + +### Progress Tracking +```dart +Future _uploadFileWithProgress(Map inputData) async { + final file = File(inputData['filePath']); + final fileSize = await file.length(); + int uploadedBytes = 0; + + final request = http.StreamedRequest('POST', Uri.parse('https://api.yourapp.com/upload')); + + final stream = file.openRead(); + stream.listen( + (chunk) { + uploadedBytes += chunk.length; + final progress = (uploadedBytes / fileSize * 100).round(); + // Save progress to SharedPreferences for UI updates + _saveUploadProgress(inputData['taskId'], progress); + }, + ); + + request.contentLength = fileSize; + stream.pipe(request); + + final response = await request.send(); + return response.statusCode == 200; +} +``` + +### Retry Logic +```dart +Future _uploadWithRetry(Map inputData) async { + const maxRetries = 3; + int attempts = inputData['attempts'] ?? 0; + + final success = await _uploadFile(inputData); + + if (!success && attempts < maxRetries) { + // Schedule retry with delay + await Future.delayed(Duration(seconds: math.pow(2, attempts).toInt())); + + inputData['attempts'] = attempts + 1; + return await _uploadWithRetry(inputData); + } + + return success; +} +``` + +## Related Use Cases + + + + Synchronize data with your backend + + + Clean up and optimize local data + + \ No newline at end of file From f37b8dce684a2181a1c59d30217f542ba949e85d Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 16:38:46 +0100 Subject: [PATCH 02/25] fix: convert docs.json to docs.page format and populate index.mdx - Fixed docs.json from Mintlify format to simple docs.page format - Populated index.mdx with comprehensive content as main landing page - Added missing database-maintenance use case page - Simplified navigation structure for docs.page compatibility - Should resolve empty docs.page issue --- docs.json | 108 +------------------------ docs/index.mdx | 95 ++++++++++++++++++++++ docs/usecases/database-maintenance.mdx | 71 ++++++++++++++++ 3 files changed, 167 insertions(+), 107 deletions(-) create mode 100644 docs/usecases/database-maintenance.mdx diff --git a/docs.json b/docs.json index ba1d6ebc..de851689 100644 --- a/docs.json +++ b/docs.json @@ -1,110 +1,4 @@ { "name": "Flutter Workmanager", - "$schema": "https://mintlify.com/schema.json", - "logo": { - "dark": "/logo/dark.svg", - "light": "/logo/light.svg" - }, - "favicon": "/favicon.svg", - "colors": { - "primary": "#0D9373", - "light": "#07C983", - "dark": "#0D9373", - "anchors": { - "from": "#0D9373", - "to": "#07C983" - } - }, - "topbarLinks": [ - { - "name": "Support", - "url": "https://github.com/fluttercommunity/flutter_workmanager/issues" - } - ], - "topbarCtaButton": { - "name": "GitHub", - "url": "https://github.com/fluttercommunity/flutter_workmanager" - }, - "tabs": [ - { - "name": "API Reference", - "url": "api-reference" - } - ], - "anchors": [ - { - "name": "Documentation", - "icon": "book-open-cover", - "url": "https://pub.dev/documentation/workmanager/latest/" - }, - { - "name": "Community", - "icon": "github", - "url": "https://github.com/fluttercommunity/flutter_workmanager" - } - ], - "navigation": [ - { - "group": "Get Started", - "pages": [ - "introduction", - "installation", - "quickstart" - ] - }, - { - "group": "Use Cases", - "pages": [ - "usecases/data-sync", - "usecases/periodic-cleanup", - "usecases/upload-files", - "usecases/fetch-notifications", - "usecases/database-maintenance" - ] - }, - { - "group": "Task Management", - "pages": [ - "tasks/scheduling", - "tasks/constraints", - "tasks/cancellation" - ] - }, - { - "group": "Platform Setup", - "pages": [ - "setup/android", - "setup/ios" - ] - }, - { - "group": "Debugging & Testing", - "pages": [ - "debugging/overview", - "debugging/notifications", - "debugging/troubleshooting" - ] - }, - { - "group": "Advanced", - "pages": [ - "advanced/architecture", - "advanced/migration", - "advanced/publishing" - ] - }, - { - "group": "API Reference", - "pages": [ - "api-reference/workmanager", - "api-reference/workmanager-android", - "api-reference/workmanager-apple", - "api-reference/platform-interface" - ] - } - ], - "footerSocials": { - "github": "https://github.com/fluttercommunity/flutter_workmanager", - "discord": "https://discord.gg/rflutterdev" - } + "description": "Background task execution for Flutter apps" } \ No newline at end of file diff --git a/docs/index.mdx b/docs/index.mdx index 9de66d9f..7286bda2 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -1 +1,96 @@ +--- +title: Flutter Workmanager +description: Background task execution for Flutter apps +--- + # Flutter Workmanager + +[![pub package](https://img.shields.io/pub/v/workmanager.svg)](https://pub.dartlang.org/packages/workmanager) +[![pub points](https://img.shields.io/pub/points/workmanager)](https://pub.dev/packages/workmanager/score) +[![likes](https://img.shields.io/pub/likes/workmanager)](https://pub.dev/packages/workmanager/score) +[![popularity](https://img.shields.io/pub/popularity/workmanager)](https://pub.dev/packages/workmanager/score) +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/fluttercommunity/flutter_workmanager/test.yml?branch=main&label=tests)](https://github.com/fluttercommunity/flutter_workmanager/actions) +[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/fluttercommunity/flutter_workmanager/blob/main/LICENSE) + +Execute Dart code in the background, even when your app is closed. Perfect for data sync, file uploads, and periodic maintenance tasks. + +## Quick Start + +### 1. Install +```yaml +dependencies: + workmanager: ^0.8.0 +``` + +### 2. Platform Setup +- **Android**: Works automatically βœ… +- **iOS**: [5-minute setup required](setup/ios) + +### 3. Initialize & Use +```dart +@pragma('vm:entry-point') +void callbackDispatcher() { + Workmanager().executeTask((task, inputData) { + print("Background task: $task"); + // Your background logic here + return Future.value(true); + }); +} + +void main() { + Workmanager().initialize(callbackDispatcher); + + // Schedule a task + Workmanager().registerPeriodicTask( + "sync-task", + "data-sync", + frequency: Duration(hours: 1), + ); + + runApp(MyApp()); +} +``` + +## Common Use Cases + +| Use Case | Documentation | +|----------|---------------| +| **Sync data from API** | [Data Sync Guide](usecases/data-sync) | +| **Upload files in background** | [File Upload Guide](usecases/upload-files) | +| **Clean up old data** | [Cleanup Guide](usecases/periodic-cleanup) | +| **Fetch notifications** | [Notifications Guide](usecases/fetch-notifications) | + +## Architecture + +This plugin uses a **federated architecture**: +- `workmanager` - Main package +- `workmanager_android` - Android implementation +- `workmanager_apple` - iOS/macOS implementation +- `workmanager_platform_interface` - Shared interface + +All packages are automatically included when you add `workmanager` to pubspec.yaml. + +## Key Features + +- **Background Task Execution**: Execute Dart code in the background on both Android and iOS platforms +- **Multiple Task Types**: Support for one-off tasks, periodic tasks, and iOS processing tasks +- **Platform Constraints**: Configure network, battery, charging, and other constraints for task execution +- **Debug Support**: Built-in debugging with notifications to track background task execution + +## Supported Platforms + +| Platform | Support | Notes | +|----------|---------|--------| +| Android | βœ… Full | All WorkManager features supported | +| iOS | βœ… Full | Background Fetch + BGTaskScheduler APIs | +| macOS | 🚧 Planned | Future support through NSBackgroundActivityScheduler | +| Web | ❌ Not supported | Background execution not available | +| Windows/Linux | ❌ Not supported | No background task APIs | + +## Navigation + +- **[Installation](installation)** - Install and configure the plugin +- **[Quick Start](quickstart)** - Get up and running quickly +- **[Platform Setup](setup/android)** - Configure Android and iOS +- **[Use Cases](usecases/data-sync)** - Real-world implementation examples +- **[Example App](https://github.com/fluttercommunity/flutter_workmanager/tree/main/example)** - Complete working demo diff --git a/docs/usecases/database-maintenance.mdx b/docs/usecases/database-maintenance.mdx new file mode 100644 index 00000000..2a65c297 --- /dev/null +++ b/docs/usecases/database-maintenance.mdx @@ -0,0 +1,71 @@ +--- +title: "Database Maintenance" +description: "Perform database cleanup, optimization, and maintenance tasks in the background" +--- + +# Database Maintenance + +Automatically maintain your app's local database by cleaning up old records, optimizing indexes, and performing routine maintenance tasks in the background. + +## When to Use This + +- Remove old/expired database records +- Optimize database indexes and vacuum SQLite +- Archive old data to reduce database size +- Clean up orphaned records and relationships +- Compress and defragment database files + +## Quick Implementation + +```dart +@pragma('vm:entry-point') +void callbackDispatcher() { + Workmanager().executeTask((task, inputData) async { + if (task == "db_maintenance") { + return await performDatabaseMaintenance(); + } + return Future.value(false); + }); +} + +Future performDatabaseMaintenance() async { + try { + final db = await DatabaseHelper.getDatabase(); + + // Remove old records (older than 30 days) + await db.delete( + 'logs', + where: 'created_at < ?', + whereArgs: [DateTime.now().subtract(Duration(days: 30)).millisecondsSinceEpoch], + ); + + // Vacuum database to reclaim space + await db.execute('VACUUM'); + + // Update statistics + await db.execute('ANALYZE'); + + return true; + } catch (e) { + print('Database maintenance failed: $e'); + return false; + } +} + +void scheduleDatabaseMaintenance() { + Workmanager().registerPeriodicTask( + "db_maintenance", + "db_maintenance", + frequency: Duration(days: 7), // Weekly maintenance + constraints: Constraints( + requiresDeviceIdle: true, + requiresBatteryNotLow: true, + ), + ); +} +``` + +## Platform Considerations + +**Android**: Can perform comprehensive maintenance operations +**iOS**: Limited to 30 seconds - focus on quick cleanup operations \ No newline at end of file From d53ec07ab3075cd4d3c920e3e0bd7a477fbc330f Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 16:50:48 +0100 Subject: [PATCH 03/25] fix: resolve docs.page layout and navigation issues - Fixed badges layout: converted to horizontal HTML img tags instead of vertical markdown - Removed invalid score badge as requested - Fixed use case links by using relative paths (usecases/data-sync) - Consolidated installation, setup, and quickstart into single comprehensive quickstart page - Removed duplicate setup pages (android.mdx, ios.mdx) to reduce clutter - Fixed example README - now shows actual features and usage instead of generic Flutter text - Streamlined navigation structure for better user experience - Front page now focused on intro/features/use case overview only --- docs/index.mdx | 93 ++++--------- docs/installation.mdx | 78 ----------- docs/introduction.mdx | 84 ------------ docs/quickstart.mdx | 256 ++++++++++++++++------------------- docs/setup/android.mdx | 127 ------------------ docs/setup/ios.mdx | 297 ----------------------------------------- example/README.md | 67 ++++++++-- 7 files changed, 195 insertions(+), 807 deletions(-) delete mode 100644 docs/installation.mdx delete mode 100644 docs/introduction.mdx delete mode 100644 docs/setup/android.mdx delete mode 100644 docs/setup/ios.mdx diff --git a/docs/index.mdx b/docs/index.mdx index 7286bda2..d2d42169 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -5,75 +5,18 @@ description: Background task execution for Flutter apps # Flutter Workmanager -[![pub package](https://img.shields.io/pub/v/workmanager.svg)](https://pub.dartlang.org/packages/workmanager) -[![pub points](https://img.shields.io/pub/points/workmanager)](https://pub.dev/packages/workmanager/score) -[![likes](https://img.shields.io/pub/likes/workmanager)](https://pub.dev/packages/workmanager/score) -[![popularity](https://img.shields.io/pub/popularity/workmanager)](https://pub.dev/packages/workmanager/score) -[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/fluttercommunity/flutter_workmanager/test.yml?branch=main&label=tests)](https://github.com/fluttercommunity/flutter_workmanager/actions) -[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/fluttercommunity/flutter_workmanager/blob/main/LICENSE) +

+pub package +tests +license +

Execute Dart code in the background, even when your app is closed. Perfect for data sync, file uploads, and periodic maintenance tasks. -## Quick Start - -### 1. Install -```yaml -dependencies: - workmanager: ^0.8.0 -``` - -### 2. Platform Setup -- **Android**: Works automatically βœ… -- **iOS**: [5-minute setup required](setup/ios) - -### 3. Initialize & Use -```dart -@pragma('vm:entry-point') -void callbackDispatcher() { - Workmanager().executeTask((task, inputData) { - print("Background task: $task"); - // Your background logic here - return Future.value(true); - }); -} - -void main() { - Workmanager().initialize(callbackDispatcher); - - // Schedule a task - Workmanager().registerPeriodicTask( - "sync-task", - "data-sync", - frequency: Duration(hours: 1), - ); - - runApp(MyApp()); -} -``` - -## Common Use Cases - -| Use Case | Documentation | -|----------|---------------| -| **Sync data from API** | [Data Sync Guide](usecases/data-sync) | -| **Upload files in background** | [File Upload Guide](usecases/upload-files) | -| **Clean up old data** | [Cleanup Guide](usecases/periodic-cleanup) | -| **Fetch notifications** | [Notifications Guide](usecases/fetch-notifications) | - -## Architecture - -This plugin uses a **federated architecture**: -- `workmanager` - Main package -- `workmanager_android` - Android implementation -- `workmanager_apple` - iOS/macOS implementation -- `workmanager_platform_interface` - Shared interface - -All packages are automatically included when you add `workmanager` to pubspec.yaml. - ## Key Features - **Background Task Execution**: Execute Dart code in the background on both Android and iOS platforms -- **Multiple Task Types**: Support for one-off tasks, periodic tasks, and iOS processing tasks +- **Multiple Task Types**: Support for one-off tasks, periodic tasks, and iOS processing tasks - **Platform Constraints**: Configure network, battery, charging, and other constraints for task execution - **Debug Support**: Built-in debugging with notifications to track background task execution @@ -87,10 +30,22 @@ All packages are automatically included when you add `workmanager` to pubspec.ya | Web | ❌ Not supported | Background execution not available | | Windows/Linux | ❌ Not supported | No background task APIs | -## Navigation +## Common Use Cases + +| Use Case | Description | +|----------|-------------| +| **[Sync data from API](usecases/data-sync)** | Automatically fetch and sync data from your backend API | +| **[Upload files in background](usecases/upload-files)** | Upload photos, documents when network conditions are optimal | +| **[Clean up old data](usecases/periodic-cleanup)** | Remove old files, cache data, and maintain app performance | +| **[Fetch notifications](usecases/fetch-notifications)** | Check for new notifications and messages from your server | +| **[Database maintenance](usecases/database-maintenance)** | Perform database cleanup and optimization tasks | -- **[Installation](installation)** - Install and configure the plugin -- **[Quick Start](quickstart)** - Get up and running quickly -- **[Platform Setup](setup/android)** - Configure Android and iOS -- **[Use Cases](usecases/data-sync)** - Real-world implementation examples -- **[Example App](https://github.com/fluttercommunity/flutter_workmanager/tree/main/example)** - Complete working demo +## Architecture + +This plugin uses a **federated architecture**: +- `workmanager` - Main package that provides the unified API +- `workmanager_android` - Android implementation using WorkManager +- `workmanager_apple` - iOS/macOS implementation using BGTaskScheduler +- `workmanager_platform_interface` - Shared interface for platform implementations + +All packages are automatically included when you add `workmanager` to pubspec.yaml. diff --git a/docs/installation.mdx b/docs/installation.mdx deleted file mode 100644 index 9b7e5e8d..00000000 --- a/docs/installation.mdx +++ /dev/null @@ -1,78 +0,0 @@ ---- -title: Installation -description: "Install Flutter Workmanager and configure your project" ---- - -# Installation - -## Add to pubspec.yaml - -```yaml pubspec.yaml -dependencies: - workmanager: ^0.8.0 -``` - -```bash -flutter pub get -``` - -## Platform Configuration - -Before using background tasks, you need to configure each platform: - - - - 5-minute Android WorkManager setup - - - iOS background processing configuration - - - - -Background tasks **will not work** without proper platform configuration. Complete the setup for your target platforms before proceeding. - - -## Initialize Workmanager - -Add this to your `main.dart`: - -```dart main.dart -import 'package:workmanager/workmanager.dart'; - -@pragma('vm:entry-point') -void callbackDispatcher() { - Workmanager().executeTask((task, inputData) { - // Your background task logic here - return Future.value(true); - }); -} - -void main() { - Workmanager().initialize(callbackDispatcher); - runApp(MyApp()); -} -``` - - -You only need to initialize Workmanager once in your app. The federated architecture automatically handles platform-specific implementations. - - -## Next Steps - -Choose your use case to get started quickly: - - - - Fetch remote data in the background - - - Remove old files and cache data - - - Upload photos and documents in background - - - Fetch new notifications from server - - \ No newline at end of file diff --git a/docs/introduction.mdx b/docs/introduction.mdx deleted file mode 100644 index 39f044ab..00000000 --- a/docs/introduction.mdx +++ /dev/null @@ -1,84 +0,0 @@ ---- -title: Introduction -description: "Flutter WorkManager - Background task execution for Flutter apps" ---- - -# Flutter Workmanager - -[![pub package](https://img.shields.io/pub/v/workmanager.svg)](https://pub.dartlang.org/packages/workmanager) -[![pub points](https://img.shields.io/pub/points/workmanager)](https://pub.dev/packages/workmanager/score) -[![likes](https://img.shields.io/pub/likes/workmanager)](https://pub.dev/packages/workmanager/score) -[![popularity](https://img.shields.io/pub/popularity/workmanager)](https://pub.dev/packages/workmanager/score) -[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/fluttercommunity/flutter_workmanager/test.yml?branch=main&label=tests)](https://github.com/fluttercommunity/flutter_workmanager/actions) -[![license](https://img.shields.io/badge/license-MIT-blue.svg)](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. 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: - - - - The main package that provides the unified API - - - The common platform interface - - - Android-specific implementation - - - 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. - -## Key Features - - - - Execute Dart code in the background on both Android and iOS platforms - - - - Support for one-off tasks, periodic tasks, and iOS processing tasks - - - - Configure network, battery, charging, and other constraints for task execution - - - - Built-in debugging with notifications to track background task execution - - - -## Supported Platforms - -| Platform | Support | Notes | -|----------|---------|--------| -| Android | βœ… Full | All WorkManager features supported | -| iOS | βœ… Full | Background Fetch + BGTaskScheduler APIs | -| macOS | 🚧 Planned | Future support through NSBackgroundActivityScheduler | -| Web | ❌ Not supported | Background execution not available | -| Windows/Linux | ❌ Not supported | No background task APIs | - -## Getting Started - -Ready to add background tasks to your Flutter app? Let's get started! - - - Learn how to set up and use Flutter Workmanager in your app - \ No newline at end of file diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index 1d0b7856..1c45b77a 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -1,120 +1,139 @@ --- title: Quick Start -description: "Get started with Flutter Workmanager in minutes" +description: Get started with Flutter Workmanager in minutes --- # Quick Start -This guide will help you set up Flutter Workmanager and create your first background task in just a few minutes. +Get Flutter Workmanager up and running in your app quickly. ## Installation Add `workmanager` to your `pubspec.yaml`: -```yaml pubspec.yaml +```yaml dependencies: workmanager: ^0.8.0 ``` Then run: - ```bash flutter pub get ``` - -The platform-specific packages (`workmanager_android`, `workmanager_apple`) are automatically included through the federated plugin system. You don't need to add them manually. - +> The platform-specific packages are automatically included through the federated plugin system. ## Platform Setup -Before using Workmanager, you need to configure platform-specific settings: +### Android +Android works automatically - no additional setup required! βœ… - - - Configure Android WorkManager - - - Configure iOS background processing - - +### iOS +iOS requires a 5-minute setup in Xcode: -## Basic Usage +1. **Enable Background Modes** in Xcode target capabilities: + - Background processing βœ… + - Background fetch βœ… + +2. **Add to Info.plist**: +```xml +UIBackgroundModes + + processing + fetch + + +BGTaskSchedulerPermittedIdentifiers + + your.task.identifier + +``` -Here's a complete example to get you started: +3. **Configure AppDelegate.swift**: +```swift +import workmanager + +// In application(_:didFinishLaunchingWithOptions:) +WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "your.task.identifier") +``` + +## Basic Usage -```dart main.dart -import 'package:flutter/material.dart'; -import 'package:workmanager/workmanager.dart'; +### 1. Create Background Task Handler -@pragma('vm:entry-point') // Mandatory if the App is obfuscated or using Flutter 3.1+ +```dart +@pragma('vm:entry-point') // Required for release builds void callbackDispatcher() { - Workmanager().executeTask((task, inputData) { - print("Native called background task: $task"); // simpleTask will be emitted here - return Future.value(true); + Workmanager().executeTask((task, inputData) async { + print("Background task: $task"); + + // Your background logic here + switch (task) { + case "data_sync": + await syncDataWithServer(); + break; + case "cleanup": + await cleanupOldFiles(); + break; + } + + return Future.value(true); // Task completed successfully }); } +``` +### 2. Initialize in main() + +```dart void main() { - // Initialize the plugin + // Initialize Workmanager Workmanager().initialize( - callbackDispatcher, // The top level function, aka callbackDispatcher - isInDebugMode: true // If enabled it will post a notification whenever the task is running + callbackDispatcher, + isInDebugMode: true, // Shows notifications when tasks run ); - // Register a one-off task - Workmanager().registerOneOffTask("task-identifier", "simpleTask"); - runApp(MyApp()); } +``` +### 3. Schedule Tasks + +```dart class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - title: 'Workmanager Example', - home: MyHomePage(), - ); - } -} - -class MyHomePage extends StatefulWidget { - @override - _MyHomePageState createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('Workmanager Example'), - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () { - Workmanager().registerOneOffTask( - "one-off-task", - "simpleTask", - ); - }, - child: Text('Register One-Off Task'), - ), - SizedBox(height: 20), - ElevatedButton( - onPressed: () { - Workmanager().registerPeriodicTask( - "periodic-task", - "periodicTask", - frequency: Duration(minutes: 15), - ); - }, - child: Text('Register Periodic Task'), - ), - ], + home: Scaffold( + appBar: AppBar(title: Text('Workmanager Demo')), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () { + // Schedule a one-time task + Workmanager().registerOneOffTask( + "sync-task", + "data_sync", + initialDelay: Duration(seconds: 10), + ); + }, + child: Text('Sync Data (One-time)'), + ), + SizedBox(height: 20), + ElevatedButton( + onPressed: () { + // Schedule a periodic task + Workmanager().registerPeriodicTask( + "cleanup-task", + "cleanup", + frequency: Duration(hours: 24), + ); + }, + child: Text('Daily Cleanup (Periodic)'), + ), + ], + ), ), ), ); @@ -122,78 +141,45 @@ class _MyHomePageState extends State { } ``` -## Key Concepts - - - - The `callbackDispatcher` is a top-level function that executes your background tasks. It must be either a static function or a top-level function to be accessible as a Flutter entry point. - - - - - **Android**: Tasks are identified using their `taskName` (second parameter) - - **iOS**: Tasks are identified using their `taskIdentifier` (first parameter) - - **iOS Background Fetch**: Use the constant `Workmanager.iOSBackgroundTask` - - - - The workmanager runs on a separate isolate from the main flutter isolate. Ensure to initialize all dependencies inside the `Workmanager().executeTask`. - - +## Task Results -## Work Results +Your background tasks can return: -Your background task can return three possible outcomes: +- `Future.value(true)` - βœ… Task successful +- `Future.value(false)` - πŸ”„ Task should be retried +- `Future.error(...)` - ❌ Task failed - - - `Future.value(true)` - Task completed successfully - - - `Future.value(false)` - Task needs to be retried - - - `Future.error(...)` - Task failed permanently - - +## Key Points - -On Android, the `BackoffPolicy` configures how WorkManager retries failed tasks. On iOS with BGTaskScheduler, retries need to be scheduled manually. - +- **Callback Dispatcher**: Must be a top-level function (not inside a class) +- **Separate Isolate**: Background tasks run in isolation - initialize dependencies inside the task +- **Platform Differences**: + - Android: Reliable background execution, 15-minute minimum frequency + - iOS: 30-second limit, execution depends on user patterns and device state -## Debugging Tips +## Debugging -### Enable Debug Mode - -When initializing Workmanager, set `isInDebugMode: true` to receive notifications whenever background tasks run: +Enable debug mode to see notifications when tasks run: ```dart Workmanager().initialize( callbackDispatcher, - isInDebugMode: true // Shows notifications for debugging + isInDebugMode: true, // Shows debug notifications ); ``` -### Add Error Handling - -Wrap your task code in try-catch blocks: +Add error handling: ```dart @pragma('vm:entry-point') void callbackDispatcher() { Workmanager().executeTask((task, inputData) async { try { - // Your background task code here - print("Executing task: $task"); - - // Example: Save data to SharedPreferences - final prefs = await SharedPreferences.getInstance(); - final count = prefs.getInt('task_count') ?? 0; - await prefs.setInt('task_count', count + 1); - + // Your task logic return Future.value(true); - } catch (err) { - print("Task failed: $err"); - return Future.value(false); + } catch (e) { + print('Task failed: $e'); + return Future.value(false); // Retry } }); } @@ -201,19 +187,5 @@ void callbackDispatcher() { ## Next Steps -Now that you have a basic understanding, explore more advanced features: - - - - Learn about different types of background tasks - - - Configure when tasks should run - - - Understand platform-specific behaviors - - - See more detailed examples - - \ No newline at end of file +- **[Use Cases](usecases/data-sync)** - See real-world examples +- **[Example App](https://github.com/fluttercommunity/flutter_workmanager/tree/main/example)** - Complete working demo \ No newline at end of file diff --git a/docs/setup/android.mdx b/docs/setup/android.mdx deleted file mode 100644 index be2cfd12..00000000 --- a/docs/setup/android.mdx +++ /dev/null @@ -1,127 +0,0 @@ ---- -title: Android Setup -description: "Configure Android WorkManager for background tasks" ---- - -# Android Setup - -Configure your Android app to support background tasks using WorkManager. - -## Prerequisites - -Ensure your `AndroidManifest.xml` has Flutter v2 embedding: - -```xml android/app/src/main/AndroidManifest.xml - -``` - - -If you don't have this, follow the [Flutter v2 embedding upgrade guide](https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects). - - -## That's It! - -Android setup is automatic. The `workmanager_android` package handles all WorkManager configuration internally. - -## Debugging - -Enable debug notifications to see when background tasks run: - -```dart -Workmanager().initialize( - callbackDispatcher, - isInDebugMode: true, // Shows notifications when tasks run -); -``` - -
- Android debug notifications -
- -## Testing Background Tasks - -Since Android controls when background tasks run, testing can be challenging: - - - - Use `initialDelay` to test tasks quickly: - ```dart - Workmanager().registerOneOffTask( - "test_task", - "my_task", - initialDelay: Duration(seconds: 10), // Runs in 10 seconds - ); - ``` - - - - On debug builds, you can use ADB to force task execution: - ```bash - adb shell cmd jobscheduler run -f - ``` - - - - Check if tasks are scheduled: - ```bash - adb shell dumpsys jobscheduler | grep - ``` - - - -## Battery Optimization - -Modern Android devices aggressively optimize battery usage. Users may need to: - -1. **Disable battery optimization** for your app in Settings -2. **Allow background activity** in app-specific settings -3. **Keep the app in recent apps** (don't swipe away) - - -You cannot programmatically disable battery optimization. Users must do this manually in device settings. - - -## Common Issues - - - - **Possible causes:** - - Battery optimization is enabled for your app - - Device is in Doze mode - - App has been idle for too long - - Device storage is low - - **Solutions:** - - Ask users to disable battery optimization - - Use appropriate constraints (network, charging, etc.) - - Encourage regular app usage - - - - **Android respects minimum intervals:** - - Periodic tasks: 15-minute minimum - - One-off tasks: Can run immediately but respect constraints - - **Use constraints to optimize:** - ```dart - constraints: Constraints( - networkType: NetworkType.connected, - requiresBatteryNotLow: true, - requiresCharging: false, - ) - ``` - - - -## Next Steps - - - - Configure iOS background processing - - - Start with common use cases - - \ No newline at end of file diff --git a/docs/setup/ios.mdx b/docs/setup/ios.mdx deleted file mode 100644 index 143b8c60..00000000 --- a/docs/setup/ios.mdx +++ /dev/null @@ -1,297 +0,0 @@ ---- -title: iOS Setup -description: "Configure iOS background processing with BGTaskScheduler" ---- - -# iOS Setup - -Configure your iOS app to support background tasks using BGTaskScheduler and Background Fetch APIs. - -## Step 1: Enable Background Modes - -In Xcode, select your target and go to **Signing & Capabilities**: - -1. Click **+ Capability** -2. Add **Background Modes** -3. Check **Background processing** and **Background fetch** - -
- iOS Background Modes -
- -This adds to your `Info.plist`: -```xml Info.plist -UIBackgroundModes - - processing - fetch - -``` - -## Step 2: Register Task Identifiers - -Add your task identifiers to `Info.plist`: - -```xml Info.plist -BGTaskSchedulerPermittedIdentifiers - - dev.yourapp.data_sync - dev.yourapp.file_upload - - -``` - -## Step 3: Configure AppDelegate.swift - -```swift ios/Runner/AppDelegate.swift -import Flutter -import UIKit -import workmanager - -@UIApplicationMain -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - - // Register background tasks - WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "dev.yourapp.data_sync") - - // For periodic tasks (iOS 13+) - WorkmanagerPlugin.registerPeriodicTask( - withIdentifier: "dev.yourapp.periodic_sync", - frequency: NSNumber(value: 20 * 60) // 20 minutes in seconds - ) - - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } -} -``` - -## Task Types and Setup - - - - **Use for**: Immediate background work (uploads, quick sync) - - **Characteristics:** - - Starts immediately when scheduled - - Limited to ~30 seconds execution time - - No additional setup needed in AppDelegate - - ```dart - Workmanager().registerOneOffTask( - "upload_task", - "file_upload", - constraints: Constraints(networkType: NetworkType.connected), - ); - ``` - - - - **Use for**: Regular data sync, maintenance tasks - - **Setup required in AppDelegate.swift:** - ```swift - WorkmanagerPlugin.registerPeriodicTask( - withIdentifier: "dev.yourapp.sync_task", - frequency: NSNumber(value: 60 * 60) // 1 hour - ) - ``` - - **Info.plist:** - ```xml - BGTaskSchedulerPermittedIdentifiers - - dev.yourapp.sync_task - - ``` - - **Schedule in Dart:** - ```dart - Workmanager().registerPeriodicTask( - "dev.yourapp.sync_task", - "periodic_sync", - frequency: Duration(hours: 1), // Ignored on iOS - set in AppDelegate - ); - ``` - - - - **Use for**: Long-running tasks (large uploads, heavy processing) - - **Setup in AppDelegate.swift:** - ```swift - WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "dev.yourapp.heavy_task") - ``` - - **Characteristics:** - - Can run for minutes - - Only when device is idle and charging - - iOS may defer execution significantly - - ```dart - Workmanager().registerProcessingTask( - "dev.yourapp.heavy_task", - "heavy_processing", - constraints: Constraints( - requiresCharging: true, - networkType: NetworkType.connected, - ), - ); - ``` - - - -## iOS Limitations & Best Practices - - -iOS background processing has strict limitations that don't exist on Android: - - -### Execution Time Limits -- **One-off tasks**: ~30 seconds -- **Periodic tasks**: ~30 seconds -- **Processing tasks**: Several minutes (when device idle + charging) - -### Scheduling Restrictions -- iOS learns user patterns and schedules accordingly -- Tasks may not run if user hasn't used app recently -- Background App Refresh must be enabled -- No guaranteed execution time - -### Best Practices - - - - ```dart - Future _quickSync() async { - final startTime = DateTime.now(); - - try { - // Your sync logic here - await syncData(); - - // Check if we're running out of time - final elapsed = DateTime.now().difference(startTime); - if (elapsed.inSeconds > 25) { - print('Task taking too long, stopping early'); - return false; // Retry later - } - - return true; - } catch (e) { - return false; - } - } - ``` - - - - ```dart - Future _handleTaskExpiration() async { - final completer = Completer(); - - // Start your work - final workFuture = performWork(); - - // Set a timeout slightly less than iOS limit - final timeoutFuture = Future.delayed( - Duration(seconds: 25), - () => false - ); - - try { - final result = await Future.any([workFuture, timeoutFuture]); - return result; - } catch (e) { - return false; - } - } - ``` - - - - ```dart - Future _incrementalSync() async { - final prefs = await SharedPreferences.getInstance(); - final lastSyncPoint = prefs.getString('last_sync_point') ?? '0'; - - // Only sync what's new since last successful sync - final response = await api.getSince(lastSyncPoint); - - if (response.isSuccess) { - await prefs.setString('last_sync_point', response.nextSyncPoint); - return true; - } - - return false; - } - ``` - - - -## Testing on iOS - -### Debug in Simulator -Background tasks **cannot** be tested in iOS Simulator. Use a physical device. - -### Debug Commands -Use Xcode debugger console to trigger tasks: - -```objc -// Trigger background app refresh -e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"dev.yourapp.sync_task"] - -// Trigger processing task -e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"dev.yourapp.heavy_task"] -``` - -### Monitor Background Execution -```dart -// Print scheduled tasks (iOS 13+ only) -if (Platform.isIOS) { - final tasks = await Workmanager().printScheduledTasks(); - print('Scheduled tasks: $tasks'); -} -``` - -## Troubleshooting - - - - **Check:** - - Background App Refresh is enabled in iOS Settings - - Task identifiers match between Info.plist and AppDelegate.swift - - App has been used recently (iOS learning algorithm) - - Device has been idle/charging (for processing tasks) - - - - **Common causes:** - - User disabled Background App Refresh - - App hasn't been used in several days - - iOS battery optimization kicked in - - Task is taking too long and being terminated - - - - **Make sure:** - - `import workmanager` is added to AppDelegate.swift - - iOS deployment target is 10.0+ in Podfile - - Xcode version is 10.3+ (Swift 4.2+) - - - -## Next Steps - - - - See platform-specific examples - - - Learn to debug background tasks - - \ No newline at end of file diff --git a/example/README.md b/example/README.md index 6a9fca8e..e313df84 100644 --- a/example/README.md +++ b/example/README.md @@ -1,16 +1,63 @@ -# workmanager_example +# Flutter Workmanager Example -Demonstrates how to use the workmanager plugin. +Complete working demo showing all Flutter Workmanager features and task types. -## Getting Started +## Features Demonstrated -This project is a starting point for a Flutter application. +- **One-off tasks**: Immediate background execution +- **Periodic tasks**: Scheduled recurring background work +- **Processing tasks**: Long-running iOS background tasks +- **Task constraints**: Network, battery, and device state requirements +- **Debug notifications**: Visual feedback when tasks execute +- **Error handling**: Proper task success/failure/retry logic +- **Platform differences**: Android vs iOS background execution -A few resources to get you started if this is your first Flutter project: +## Quick Start -- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) +1. **Clone and run**: + ```bash + git clone https://github.com/fluttercommunity/flutter_workmanager.git + cd flutter_workmanager/example + flutter run + ``` -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +2. **Platform setup**: + - **Android**: Works immediately βœ… + - **iOS**: Follow the iOS setup in `ios/Runner/AppDelegate.swift` and `ios/Runner/Info.plist` + +3. **Test background tasks**: + - Tap buttons to schedule different task types + - Put app in background to see tasks execute + - Check debug notifications to verify execution + +## Example Tasks + +The demo includes practical examples: + +- **Simulated API sync**: Fetches data and stores locally +- **File cleanup**: Removes old cached files +- **Periodic maintenance**: Regular app maintenance tasks +- **Long processing**: iOS-specific long-running tasks + +## Key Files + +- `lib/main.dart` - Main app with task scheduling UI +- `lib/callback_dispatcher.dart` - Background task execution logic +- `ios/Runner/AppDelegate.swift` - iOS background task registration +- `ios/Runner/Info.plist` - iOS background modes configuration + +## Testing Background Tasks + +**Android**: +- Tasks run reliably in background +- Enable debug mode to see notifications +- Use `adb shell dumpsys jobscheduler` to inspect scheduled tasks + +**iOS**: +- Test on physical device (not simulator) +- Enable Background App Refresh in Settings +- Use Xcode debugger commands to trigger tasks immediately + +## Documentation + +For detailed guides and real-world use cases, visit: **https://docs.workmanager.dev** From 1dcb6acdd60a87cae4e175e233c7d59351f41124 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 17:50:04 +0100 Subject: [PATCH 04/25] fix: final docs.page improvements and debugging guide - Removed badges from docs.page index (cleaner look) - Fixed all domain references from hypothetical subdomain to actual docs.page URLs - Added comprehensive debugging page with Android job scheduler and iOS console debugging - Updated sidebar navigation to include debugging section - Made GitHub README links clearer with arrow indicators - Enhanced example README documentation link --- README.md | 16 +-- docs.json | 37 ++++++- docs/debugging.mdx | 266 +++++++++++++++++++++++++++++++++++++++++++++ docs/index.mdx | 16 ++- example/README.md | 2 +- 5 files changed, 321 insertions(+), 16 deletions(-) create mode 100644 docs/debugging.mdx diff --git a/README.md b/README.md index 385effda..0c6238c2 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Execute Dart code in the background, even when your app is closed. Perfect for d ## πŸ“– Full Documentation -**Visit our comprehensive documentation site:** https://docs.workmanager.dev +**[Visit our comprehensive documentation β†’](https://docs.page/fluttercommunity/flutter_workmanager)** ## ⚑ Quick Start @@ -54,10 +54,10 @@ void main() { | Use Case | Documentation | |----------|---------------| -| **Sync data from API** | [Data Sync Guide](https://docs.workmanager.dev/usecases/data-sync) | -| **Upload files in background** | [File Upload Guide](https://docs.workmanager.dev/usecases/upload-files) | -| **Clean up old data** | [Cleanup Guide](https://docs.workmanager.dev/usecases/periodic-cleanup) | -| **Fetch notifications** | [Notifications Guide](https://docs.workmanager.dev/usecases/fetch-notifications) | +| **Sync data from API** | [Data Sync Guide β†’](https://docs.page/fluttercommunity/flutter_workmanager/usecases/data-sync) | +| **Upload files in background** | [File Upload Guide β†’](https://docs.page/fluttercommunity/flutter_workmanager/usecases/upload-files) | +| **Clean up old data** | [Cleanup Guide β†’](https://docs.page/fluttercommunity/flutter_workmanager/usecases/periodic-cleanup) | +| **Fetch notifications** | [Notifications Guide β†’](https://docs.page/fluttercommunity/flutter_workmanager/usecases/fetch-notifications) | ## πŸ—οΈ Architecture @@ -71,9 +71,9 @@ All packages are automatically included when you add `workmanager` to pubspec.ya ## πŸ› Issues & Support -- **Bug reports**: [GitHub Issues](https://github.com/fluttercommunity/flutter_workmanager/issues) -- **Questions**: [GitHub Discussions](https://github.com/fluttercommunity/flutter_workmanager/discussions) -- **Documentation**: https://docs.workmanager.dev +- **Bug reports**: [GitHub Issues β†’](https://github.com/fluttercommunity/flutter_workmanager/issues) +- **Questions**: [GitHub Discussions β†’](https://github.com/fluttercommunity/flutter_workmanager/discussions) +- **Documentation**: [docs.page/fluttercommunity/flutter_workmanager β†’](https://docs.page/fluttercommunity/flutter_workmanager) ## πŸš€ Example App diff --git a/docs.json b/docs.json index de851689..fde120e8 100644 --- a/docs.json +++ b/docs.json @@ -1,4 +1,39 @@ { "name": "Flutter Workmanager", - "description": "Background task execution for Flutter apps" + "description": "Background task execution for Flutter apps", + "sidebar": [ + { + "text": "Get Started", + "link": "/quickstart" + }, + { + "text": "Use Cases", + "items": [ + { + "text": "Sync Data from API", + "link": "/usecases/data-sync" + }, + { + "text": "Upload Files", + "link": "/usecases/upload-files" + }, + { + "text": "Periodic Cleanup", + "link": "/usecases/periodic-cleanup" + }, + { + "text": "Fetch Notifications", + "link": "/usecases/fetch-notifications" + }, + { + "text": "Database Maintenance", + "link": "/usecases/database-maintenance" + } + ] + }, + { + "text": "Debugging", + "link": "/debugging" + } + ] } \ No newline at end of file diff --git a/docs/debugging.mdx b/docs/debugging.mdx new file mode 100644 index 00000000..3583f1bf --- /dev/null +++ b/docs/debugging.mdx @@ -0,0 +1,266 @@ +--- +title: Debugging Background Tasks +description: Debug and troubleshoot background tasks on Android and iOS +--- + +# Debugging Background Tasks + +Background tasks can be tricky to debug since they run when your app is closed. Here's how to effectively debug and troubleshoot them on both platforms. + +## Enable Debug Mode + +Always start by enabling debug notifications: + +```dart +Workmanager().initialize( + callbackDispatcher, + isInDebugMode: true, // Shows notifications when tasks execute +); +``` + +This shows system notifications whenever background tasks run, making it easy to verify execution. + +## Android Debugging + +### Job Scheduler Inspection + +Use ADB to inspect Android's job scheduler: + +```bash +# View all scheduled jobs for your app +adb shell dumpsys jobscheduler | grep your.package.name + +# View detailed job info +adb shell dumpsys jobscheduler your.package.name + +# Force run a specific job (debug builds only) +adb shell cmd jobscheduler run -f your.package.name JOB_ID +``` + +### Monitor Job Execution + +```bash +# Monitor job scheduler logs +adb logcat | grep WorkManager + +# Monitor your app's background execution +adb logcat | grep "your.package.name" +``` + +### Common Android Issues + +**Tasks not running:** +- Check battery optimization settings +- Verify app is not in "App Standby" mode +- Ensure device isn't in Doze mode +- Check if constraints are too restrictive + +**Tasks running too often:** +- Android enforces minimum 15-minute intervals for periodic tasks +- Use appropriate constraints to limit execution + +### Debug Commands + +```bash +# Check if your app is whitelisted from battery optimization +adb shell dumpsys deviceidle whitelist + +# Check battery optimization status +adb shell settings get global battery_saver_constants + +# Force device into idle mode (testing) +adb shell dumpsys deviceidle force-idle +``` + +## iOS Debugging + +### Console Logging + +iOS background tasks have limited execution time. Add detailed logging: + +```dart +@pragma('vm:entry-point') +void callbackDispatcher() { + Workmanager().executeTask((task, inputData) async { + print('[iOS BG] Task started: $task at ${DateTime.now()}'); + + try { + // Your task logic + final result = await performTask(); + print('[iOS BG] Task completed successfully'); + return true; + } catch (e) { + print('[iOS BG] Task failed: $e'); + return false; + } + }); +} +``` + +### Xcode Debugging + +Use Xcode's console to trigger background tasks manually: + +```objc +// Trigger background app refresh +e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"your.task.id"] + +// Trigger processing task +e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"your.processing.task.id"] + +// Force background fetch (legacy) +e -l objc -- (void)[[UIApplication sharedApplication] _simulateApplicationDidEnterBackground] +``` + +### Monitor Scheduled Tasks + +Check what tasks iOS has scheduled: + +```dart +// iOS 13+ only +if (Platform.isIOS) { + final tasks = await Workmanager().printScheduledTasks(); + print('Scheduled tasks: $tasks'); +} +``` + +This prints output like: +``` +[BGTaskScheduler] Task Identifier: your.task.id earliestBeginDate: 2023.10.10 PM 11:10:12 +[BGTaskScheduler] There are no scheduled tasks +``` + +### Common iOS Issues + +**Tasks never run:** +- Background App Refresh disabled in Settings +- App hasn't been used recently (iOS learning algorithm) +- Task identifiers don't match between Info.plist and AppDelegate +- Missing BGTaskSchedulerPermittedIdentifiers in Info.plist + +**Tasks stop working:** +- iOS battery optimization kicked in +- App removed from recent apps too often +- User disabled background refresh +- Task taking longer than 30 seconds + +**Tasks run but don't complete:** +- Hitting 30-second execution limit +- Network requests timing out +- Heavy processing blocking the thread + +## General Debugging Tips + +### Add Comprehensive Logging + +```dart +@pragma('vm:entry-point') +void callbackDispatcher() { + Workmanager().executeTask((task, inputData) async { + final startTime = DateTime.now(); + print('πŸš€ Task started: $task'); + print('πŸ“Š Input data: $inputData'); + + try { + // Your task implementation + final result = await performTask(task, inputData); + + final duration = DateTime.now().difference(startTime); + print('βœ… Task completed in ${duration.inSeconds}s'); + + return result; + } catch (e, stackTrace) { + final duration = DateTime.now().difference(startTime); + print('❌ Task failed after ${duration.inSeconds}s: $e'); + print('πŸ“‹ Stack trace: $stackTrace'); + + return false; // Retry + } + }); +} +``` + +### Test Task Logic Separately + +Create a way to test your background logic from the UI: + +```dart +// Add a debug button to test task logic +ElevatedButton( + onPressed: () async { + // Test the same logic that runs in background + final result = await performTask('test_task', {'debug': true}); + print('Test result: $result'); + }, + child: Text('Test Task Logic'), +) +``` + +### Monitor Task Health + +Track when tasks last ran successfully: + +```dart +Future saveTaskExecutionTime(String taskName) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString('${taskName}_last_run', DateTime.now().toIso8601String()); +} + +Future isTaskHealthy(String taskName, Duration maxAge) async { + final prefs = await SharedPreferences.getInstance(); + final lastRunString = prefs.getString('${taskName}_last_run'); + + if (lastRunString == null) return false; + + final lastRun = DateTime.parse(lastRunString); + final age = DateTime.now().difference(lastRun); + + return age < maxAge; +} +``` + +## Platform-Specific Testing + +### Android Testing Workflow + +1. **Enable debug mode** and install app +2. **Schedule task** and verify it appears in job scheduler +3. **Put device to sleep** and wait for execution +4. **Check debug notifications** to confirm execution +5. **Use ADB commands** to force execution if needed + +### iOS Testing Workflow + +1. **Test on physical device** (simulator doesn't support background tasks) +2. **Enable Background App Refresh** in iOS Settings +3. **Use Xcode debugger** to trigger tasks immediately +4. **Monitor Xcode console** for logging output +5. **Check iOS Settings > Battery** for background activity + +## Troubleshooting Checklist + +**Task not scheduling:** +- [ ] Workmanager initialized in main() +- [ ] Task names are unique +- [ ] Platform setup completed (iOS Info.plist, AppDelegate) + +**Task not executing:** +- [ ] Debug notifications enabled +- [ ] Battery optimization disabled (Android) +- [ ] Background App Refresh enabled (iOS) +- [ ] App used recently (iOS learning algorithm) +- [ ] Constraints not too restrictive + +**Task executing but failing:** +- [ ] Added try-catch error handling +- [ ] Dependencies initialized in background isolate +- [ ] Network timeouts configured appropriately +- [ ] iOS 30-second limit respected + +**Performance issues:** +- [ ] Task logic optimized for background execution +- [ ] Heavy processing split into smaller chunks +- [ ] Database operations use appropriate batch sizes +- [ ] Network requests have reasonable timeouts + +Remember: Background task execution is controlled by the operating system and is never guaranteed. Always design your app to work gracefully when background tasks don't run as expected. \ No newline at end of file diff --git a/docs/index.mdx b/docs/index.mdx index d2d42169..e93e52c8 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -5,12 +5,6 @@ description: Background task execution for Flutter apps # Flutter Workmanager -

-pub package -tests -license -

- Execute Dart code in the background, even when your app is closed. Perfect for data sync, file uploads, and periodic maintenance tasks. ## Key Features @@ -49,3 +43,13 @@ This plugin uses a **federated architecture**: - `workmanager_platform_interface` - Shared interface for platform implementations All packages are automatically included when you add `workmanager` to pubspec.yaml. + +## Get Started + +Ready to add background tasks to your Flutter app? + +**[β†’ Quick Start Guide](quickstart)** - Get up and running in minutes + +## Example Project + +See a complete working demo: **[Example App β†’](https://github.com/fluttercommunity/flutter_workmanager/tree/main/example)** diff --git a/example/README.md b/example/README.md index e313df84..d667478e 100644 --- a/example/README.md +++ b/example/README.md @@ -60,4 +60,4 @@ The demo includes practical examples: ## Documentation -For detailed guides and real-world use cases, visit: **https://docs.workmanager.dev** +For detailed guides and real-world use cases, visit: **[docs.page/fluttercommunity/flutter_workmanager β†’](https://docs.page/fluttercommunity/flutter_workmanager)** From 4557f8d695f21e14a7d9786e766cd795cde4e568 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 18:29:26 +0100 Subject: [PATCH 05/25] fix: remove unsupported docs.page syntax from use case pages - Converted , , components to standard markdown - Fixed 500 errors on use case pages by using platform-compatible syntax - Changed relative links to work properly with docs.page routing - All use case pages should now load without errors --- docs/usecases/data-sync.mdx | 209 ++++++++++++-------------- docs/usecases/fetch-notifications.mdx | 26 ++-- docs/usecases/periodic-cleanup.mdx | 26 ++-- docs/usecases/upload-files.mdx | 100 ++++++------ 4 files changed, 164 insertions(+), 197 deletions(-) diff --git a/docs/usecases/data-sync.mdx b/docs/usecases/data-sync.mdx index b2faeaa2..4401f5a8 100644 --- a/docs/usecases/data-sync.mdx +++ b/docs/usecases/data-sync.mdx @@ -195,64 +195,60 @@ class _SyncStatusWidgetState extends State { ## Platform-Specific Considerations - - - **Android automatically handles**: - - Task scheduling and retry logic - - Battery optimization (Doze mode) - - App standby mode restrictions - - Network changes and availability - - **Best practices**: - ```dart - // Use appropriate constraints - Workmanager().registerPeriodicTask( - "data_sync", - "sync_task", - frequency: Duration(hours: 1), - constraints: Constraints( - networkType: NetworkType.connected, - requiresBatteryNotLow: true, // Respect battery optimization - requiresCharging: false, // Don't wait for charging - ), - ); - ``` - - **Minimum frequency**: 15 minutes (Android WorkManager limitation) - - - - **iOS has additional restrictions**: - - Background App Refresh must be enabled - - Tasks may be limited to 30 seconds - - iOS learns usage patterns and schedules accordingly - - No guaranteed execution time - - **Setup required in AppDelegate.swift**: - ```swift - import workmanager - - // In application(_:didFinishLaunchingWithOptions:) - WorkmanagerPlugin.registerPeriodicTask( - withIdentifier: "data_sync_task", - frequency: NSNumber(value: 60 * 60) // 1 hour in seconds - ) - ``` - - **Info.plist configuration**: - ```xml - BGTaskSchedulerPermittedIdentifiers - - data_sync_task - - ``` - - **Best practices**: - - Keep sync operations under 30 seconds - - Use incremental sync (only fetch changes) - - Handle task expiration gracefully - - +### Android +**Android automatically handles**: +- Task scheduling and retry logic +- Battery optimization (Doze mode) +- App standby mode restrictions +- Network changes and availability + +**Best practices**: +```dart +// Use appropriate constraints +Workmanager().registerPeriodicTask( + "data_sync", + "sync_task", + frequency: Duration(hours: 1), + constraints: Constraints( + networkType: NetworkType.connected, + requiresBatteryNotLow: true, // Respect battery optimization + requiresCharging: false, // Don't wait for charging + ), +); +``` + +**Minimum frequency**: 15 minutes (Android WorkManager limitation) + +### iOS +**iOS has additional restrictions**: +- Background App Refresh must be enabled +- Tasks may be limited to 30 seconds +- iOS learns usage patterns and schedules accordingly +- No guaranteed execution time + +**Setup required in AppDelegate.swift**: +```swift +import workmanager + +// In application(_:didFinishLaunchingWithOptions:) +WorkmanagerPlugin.registerPeriodicTask( + withIdentifier: "data_sync_task", + frequency: NSNumber(value: 60 * 60) // 1 hour in seconds +) +``` + +**Info.plist configuration**: +```xml +BGTaskSchedulerPermittedIdentifiers + + data_sync_task + +``` + +**Best practices**: +- Keep sync operations under 30 seconds +- Use incremental sync (only fetch changes) +- Handle task expiration gracefully ## Testing Your Implementation @@ -299,60 +295,49 @@ Future _checkSyncHealth() async { ## Common Issues & Solutions - - - **Possible causes**: - - Missing platform setup (Android/iOS configuration) - - App battery optimization preventing background tasks - - No network connectivity when task tries to run - - App hasn't been used recently (iOS restriction) - - **Solutions**: - - Verify platform setup is complete - - Ask users to disable battery optimization for your app - - Use network constraints appropriately - - Encourage regular app usage - - - - **Implement incremental sync**: - ```dart - Future _performIncrementalSync() async { - final prefs = await SharedPreferences.getInstance(); - final lastSyncTime = prefs.getString('last_sync_time') ?? ''; - - final response = await http.get( - Uri.parse('https://api.yourapp.com/data?since=$lastSyncTime'), - ); - - // Only sync changed data - if (response.statusCode == 200) { - final changes = json.decode(response.body); - await _applyChanges(changes); - return true; - } - return false; - } - ``` - - - - **Optimize your sync logic**: - - Use appropriate frequency (not too often) - - Add battery constraints - - Implement smart syncing (only when data actually changed) - - Use compression for large payloads - - Cache API responses appropriately - - +### Sync Not Running +**Possible causes**: +- Missing platform setup (Android/iOS configuration) +- App battery optimization preventing background tasks +- No network connectivity when task tries to run +- App hasn't been used recently (iOS restriction) + +**Solutions**: +- Verify platform setup is complete +- Ask users to disable battery optimization for your app +- Use network constraints appropriately +- Encourage regular app usage + +### Partial Data Sync +**Implement incremental sync**: +```dart +Future _performIncrementalSync() async { + final prefs = await SharedPreferences.getInstance(); + final lastSyncTime = prefs.getString('last_sync_time') ?? ''; + + final response = await http.get( + Uri.parse('https://api.yourapp.com/data?since=$lastSyncTime'), + ); + + // Only sync changed data + if (response.statusCode == 200) { + final changes = json.decode(response.body); + await _applyChanges(changes); + return true; + } + return false; +} +``` + +### High Battery Usage +**Optimize your sync logic**: +- Use appropriate frequency (not too often) +- Add battery constraints +- Implement smart syncing (only when data actually changed) +- Use compression for large payloads +- Cache API responses appropriately ## Related Use Cases - - - Upload user content in the background - - - Check for new notifications from your server - - \ No newline at end of file +- **[Upload Files](upload-files)** - Upload user content in the background +- **[Fetch Notifications](fetch-notifications)** - Check for new notifications from your server \ No newline at end of file diff --git a/docs/usecases/fetch-notifications.mdx b/docs/usecases/fetch-notifications.mdx index cbc7019e..2619cab4 100644 --- a/docs/usecases/fetch-notifications.mdx +++ b/docs/usecases/fetch-notifications.mdx @@ -62,18 +62,14 @@ void scheduleNotificationCheck() { ## Platform Considerations - - - - Can check frequently (minimum 15 minutes) - - Works reliably in background - - Can show local notifications immediately - - Respects battery optimization settings - - - - - Limited execution time (~30 seconds) - - May not run if app unused recently - - iOS learns user patterns for scheduling - - Background App Refresh must be enabled - - \ No newline at end of file +### Android +- Can check frequently (minimum 15 minutes) +- Works reliably in background +- Can show local notifications immediately +- Respects battery optimization settings + +### iOS +- Limited execution time (~30 seconds) +- May not run if app unused recently +- iOS learns user patterns for scheduling +- Background App Refresh must be enabled \ No newline at end of file diff --git a/docs/usecases/periodic-cleanup.mdx b/docs/usecases/periodic-cleanup.mdx index 0b696cb7..d9f877ff 100644 --- a/docs/usecases/periodic-cleanup.mdx +++ b/docs/usecases/periodic-cleanup.mdx @@ -61,18 +61,14 @@ void scheduleCleanup() { ## Platform Behavior - - - - Runs reliably in the background - - Respects device idle constraints - - Can clean up large amounts of data - - Minimum frequency: 15 minutes - - - - - Limited to 30 seconds execution time - - Use incremental cleanup approach - - May be deferred if device is busy - - Focus on quick cleanup operations - - \ No newline at end of file +### Android +- Runs reliably in the background +- Respects device idle constraints +- Can clean up large amounts of data +- Minimum frequency: 15 minutes + +### iOS +- Limited to 30 seconds execution time +- Use incremental cleanup approach +- May be deferred if device is busy +- Focus on quick cleanup operations \ No newline at end of file diff --git a/docs/usecases/upload-files.mdx b/docs/usecases/upload-files.mdx index 8d8ed530..2ccf9f6d 100644 --- a/docs/usecases/upload-files.mdx +++ b/docs/usecases/upload-files.mdx @@ -178,53 +178,49 @@ class _FileUploadWidgetState extends State { ## Platform-Specific Behavior - - - **Automatic handling:** - - Large file uploads continue even if app is killed - - Respects data saver mode and metered networks - - Retries with exponential backoff on failure - - **Optimize for Android:** - ```dart - Workmanager().registerOneOffTask( - taskId, - uploadTaskName, - inputData: inputData, - constraints: Constraints( - networkType: NetworkType.unmetered, // Use WiFi when possible - requiresCharging: true, // For large files - requiresDeviceIdle: false, // Upload even when device in use - ), - backoffPolicy: BackoffPolicy.exponential, - backoffPolicyDelay: Duration(minutes: 1), - ); - ``` - - - - **iOS limitations:** - - 30-second execution limit for most tasks - - Use BGProcessingTask for longer uploads - - May be deferred until device is idle and charging - - **For large files, use processing tasks:** - ```dart - // Register in AppDelegate.swift - WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "large_file_upload") - - // Schedule in Dart - Workmanager().registerProcessingTask( - taskId, - uploadTaskName, - constraints: Constraints( - networkType: NetworkType.connected, - requiresCharging: true, // iOS prefers charging for processing tasks - ), - ); - ``` - - +### Android +**Automatic handling:** +- Large file uploads continue even if app is killed +- Respects data saver mode and metered networks +- Retries with exponential backoff on failure + +**Optimize for Android:** +```dart +Workmanager().registerOneOffTask( + taskId, + uploadTaskName, + inputData: inputData, + constraints: Constraints( + networkType: NetworkType.unmetered, // Use WiFi when possible + requiresCharging: true, // For large files + requiresDeviceIdle: false, // Upload even when device in use + ), + backoffPolicy: BackoffPolicy.exponential, + backoffPolicyDelay: Duration(minutes: 1), +); +``` + +### iOS +**iOS limitations:** +- 30-second execution limit for most tasks +- Use BGProcessingTask for longer uploads +- May be deferred until device is idle and charging + +**For large files, use processing tasks:** +```dart +// Register in AppDelegate.swift +WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "large_file_upload") + +// Schedule in Dart +Workmanager().registerProcessingTask( + taskId, + uploadTaskName, + constraints: Constraints( + networkType: NetworkType.connected, + requiresCharging: true, // iOS prefers charging for processing tasks + ), +); +``` ## Advanced Features @@ -277,11 +273,5 @@ Future _uploadWithRetry(Map inputData) async { ## Related Use Cases - - - Synchronize data with your backend - - - Clean up and optimize local data - - \ No newline at end of file +- **[Data Sync](data-sync)** - Synchronize data with your backend +- **[Database Maintenance](database-maintenance)** - Clean up and optimize local data \ No newline at end of file From 12d0f2afbd13c140b7623943daf14a6d1694db4b Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 18:30:19 +0100 Subject: [PATCH 06/25] fix: remove quotes from frontmatter in use case pages - Fixed YAML frontmatter by removing quotes around title and description - This should resolve 500 errors on use case pages --- docs/usecases/data-sync.mdx | 4 ++-- docs/usecases/database-maintenance.mdx | 4 ++-- docs/usecases/fetch-notifications.mdx | 4 ++-- docs/usecases/periodic-cleanup.mdx | 4 ++-- docs/usecases/upload-files.mdx | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/usecases/data-sync.mdx b/docs/usecases/data-sync.mdx index 4401f5a8..f0d87862 100644 --- a/docs/usecases/data-sync.mdx +++ b/docs/usecases/data-sync.mdx @@ -1,6 +1,6 @@ --- -title: "Sync Data Regularly" -description: "Automatically fetch and sync data from your API in the background" +title: Sync Data Regularly +description: Automatically fetch and sync data from your API in the background --- # Background Data Synchronization diff --git a/docs/usecases/database-maintenance.mdx b/docs/usecases/database-maintenance.mdx index 2a65c297..93a3ca32 100644 --- a/docs/usecases/database-maintenance.mdx +++ b/docs/usecases/database-maintenance.mdx @@ -1,6 +1,6 @@ --- -title: "Database Maintenance" -description: "Perform database cleanup, optimization, and maintenance tasks in the background" +title: Database Maintenance +description: Perform database cleanup, optimization, and maintenance tasks in the background --- # Database Maintenance diff --git a/docs/usecases/fetch-notifications.mdx b/docs/usecases/fetch-notifications.mdx index 2619cab4..40837e78 100644 --- a/docs/usecases/fetch-notifications.mdx +++ b/docs/usecases/fetch-notifications.mdx @@ -1,6 +1,6 @@ --- -title: "Fetch Notifications" -description: "Check for new notifications and messages from your server in the background" +title: Fetch Notifications +description: Check for new notifications and messages from your server in the background --- # Background Notification Fetching diff --git a/docs/usecases/periodic-cleanup.mdx b/docs/usecases/periodic-cleanup.mdx index d9f877ff..73ba0b27 100644 --- a/docs/usecases/periodic-cleanup.mdx +++ b/docs/usecases/periodic-cleanup.mdx @@ -1,6 +1,6 @@ --- -title: "Periodic Cleanup" -description: "Automatically clean up old files, cache data, and maintain app performance" +title: Periodic Cleanup +description: Automatically clean up old files, cache data, and maintain app performance --- # Periodic Data Cleanup diff --git a/docs/usecases/upload-files.mdx b/docs/usecases/upload-files.mdx index 2ccf9f6d..c31d1ff2 100644 --- a/docs/usecases/upload-files.mdx +++ b/docs/usecases/upload-files.mdx @@ -1,6 +1,6 @@ --- -title: "Upload Files in Background" -description: "Upload photos, documents, and other files when network conditions are optimal" +title: Upload Files in Background +description: Upload photos, documents, and other files when network conditions are optimal --- # Background File Uploads From f552d32c83221e065ef3ae59e6c7fde16b0e199a Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 18:32:26 +0100 Subject: [PATCH 07/25] fix: simplify problematic use case pages to resolve 500 errors - Simplified upload-files page by removing FilePicker dependency - Simplified fetch-notifications page by removing http/json imports - Focused on core Workmanager functionality rather than external dependencies --- docs/usecases/fetch-notifications.mdx | 24 ++++++++--------- docs/usecases/upload-files.mdx | 39 +++++++++++++-------------- 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/docs/usecases/fetch-notifications.mdx b/docs/usecases/fetch-notifications.mdx index 40837e78..e653afb6 100644 --- a/docs/usecases/fetch-notifications.mdx +++ b/docs/usecases/fetch-notifications.mdx @@ -30,20 +30,18 @@ void callbackDispatcher() { Future checkForNotifications() async { try { - final response = await http.get( - Uri.parse('https://api.yourapp.com/notifications/check'), - headers: {'Authorization': 'Bearer YOUR_TOKEN'}, - ); - - if (response.statusCode == 200) { - final data = json.decode(response.body); - if (data['hasNew'] == true) { - await _showLocalNotification(data['message']); - } - return true; - } - return false; + // Check your API for new notifications + print('Checking for new notifications...'); + + // Simulate API call + await Future.delayed(Duration(seconds: 2)); + + // Process any new notifications found + // Show local notification if needed + + return true; // Success } catch (e) { + print('Notification check failed: $e'); return false; } } diff --git a/docs/usecases/upload-files.mdx b/docs/usecases/upload-files.mdx index c31d1ff2..55338085 100644 --- a/docs/usecases/upload-files.mdx +++ b/docs/usecases/upload-files.mdx @@ -129,27 +129,24 @@ class _FileUploadWidgetState extends State { } Future _selectAndQueueFile() async { - final result = await FilePicker.platform.pickFiles(); - if (result != null) { - final file = result.files.single; - final filePath = file.path!; - - // Queue for background upload - await UploadService.queueFileUpload(filePath, { - 'filename': file.name, - 'size': file.size, - 'type': file.extension, - 'userId': 'current_user_id', - }); - - setState(() { - _pendingUploads.add(file.name); - }); - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('File queued for upload: ${file.name}')), - ); - } + // Use your preferred file picker implementation + final filePath = '/path/to/selected/file.jpg'; + + // Queue for background upload + await UploadService.queueFileUpload(filePath, { + 'filename': 'photo.jpg', + 'size': 1024000, + 'type': 'jpg', + 'userId': 'current_user_id', + }); + + setState(() { + _pendingUploads.add('photo.jpg'); + }); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('File queued for upload')), + ); } @override From b504688587ffa09fb0657f05053010af4b1af1fe Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 19:17:21 +0100 Subject: [PATCH 08/25] fix: improve mobile experience and code quality - Added _sidebar.md for mobile menu navigation - Fixed repetitive page titles (all use cases now marked as examples) - Made code examples mobile-friendly with shorter lines - Fixed invalid SQLite VACUUM command (changed to PRAGMA optimize) - Shortened variable names and function calls for better mobile display - Improved debugging commands for mobile viewing --- docs.json | 37 +------------------------- docs/_sidebar.md | 8 ++++++ docs/debugging.mdx | 29 ++++++++++---------- docs/usecases/data-sync.mdx | 37 +++++++++++++------------- docs/usecases/database-maintenance.mdx | 23 ++++++++-------- docs/usecases/fetch-notifications.mdx | 6 ++--- docs/usecases/periodic-cleanup.mdx | 6 ++--- docs/usecases/upload-files.mdx | 6 ++--- 8 files changed, 62 insertions(+), 90 deletions(-) create mode 100644 docs/_sidebar.md diff --git a/docs.json b/docs.json index fde120e8..de851689 100644 --- a/docs.json +++ b/docs.json @@ -1,39 +1,4 @@ { "name": "Flutter Workmanager", - "description": "Background task execution for Flutter apps", - "sidebar": [ - { - "text": "Get Started", - "link": "/quickstart" - }, - { - "text": "Use Cases", - "items": [ - { - "text": "Sync Data from API", - "link": "/usecases/data-sync" - }, - { - "text": "Upload Files", - "link": "/usecases/upload-files" - }, - { - "text": "Periodic Cleanup", - "link": "/usecases/periodic-cleanup" - }, - { - "text": "Fetch Notifications", - "link": "/usecases/fetch-notifications" - }, - { - "text": "Database Maintenance", - "link": "/usecases/database-maintenance" - } - ] - }, - { - "text": "Debugging", - "link": "/debugging" - } - ] + "description": "Background task execution for Flutter apps" } \ No newline at end of file diff --git a/docs/_sidebar.md b/docs/_sidebar.md new file mode 100644 index 00000000..41ec6195 --- /dev/null +++ b/docs/_sidebar.md @@ -0,0 +1,8 @@ +- [Get Started](quickstart) +- **Examples** + - [Data Sync](usecases/data-sync) + - [File Upload](usecases/upload-files) + - [Cleanup Tasks](usecases/periodic-cleanup) + - [Notifications](usecases/fetch-notifications) + - [Database Tasks](usecases/database-maintenance) +- [Debugging](debugging) \ No newline at end of file diff --git a/docs/debugging.mdx b/docs/debugging.mdx index 3583f1bf..c8338ad0 100644 --- a/docs/debugging.mdx +++ b/docs/debugging.mdx @@ -27,24 +27,24 @@ This shows system notifications whenever background tasks run, making it easy to Use ADB to inspect Android's job scheduler: ```bash -# View all scheduled jobs for your app -adb shell dumpsys jobscheduler | grep your.package.name +# View scheduled jobs +adb shell dumpsys jobscheduler | grep yourapp -# View detailed job info -adb shell dumpsys jobscheduler your.package.name +# View detailed job info +adb shell dumpsys jobscheduler yourapp -# Force run a specific job (debug builds only) -adb shell cmd jobscheduler run -f your.package.name JOB_ID +# Force run job (debug only) +adb shell cmd jobscheduler run -f yourapp JOB_ID ``` ### Monitor Job Execution ```bash -# Monitor job scheduler logs +# Monitor WorkManager logs adb logcat | grep WorkManager -# Monitor your app's background execution -adb logcat | grep "your.package.name" +# Monitor app background execution +adb logcat | grep "yourapp" ``` ### Common Android Issues @@ -102,14 +102,13 @@ void callbackDispatcher() { Use Xcode's console to trigger background tasks manually: ```objc -// Trigger background app refresh -e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"your.task.id"] +// Trigger background refresh +e -l objc -- (void)[[BGTaskScheduler sharedScheduler] + _simulateLaunchForTaskWithIdentifier:@"task.id"] // Trigger processing task -e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"your.processing.task.id"] - -// Force background fetch (legacy) -e -l objc -- (void)[[UIApplication sharedApplication] _simulateApplicationDidEnterBackground] +e -l objc -- (void)[[BGTaskScheduler sharedScheduler] + _simulateLaunchForTaskWithIdentifier:@"process.id"] ``` ### Monitor Scheduled Tasks diff --git a/docs/usecases/data-sync.mdx b/docs/usecases/data-sync.mdx index f0d87862..cc3c3005 100644 --- a/docs/usecases/data-sync.mdx +++ b/docs/usecases/data-sync.mdx @@ -1,9 +1,9 @@ --- -title: Sync Data Regularly -description: Automatically fetch and sync data from your API in the background +title: Example - Data Sync +description: Example implementation for syncing API data in background --- -# Background Data Synchronization +# Example: API Data Sync Keep your app's data fresh by automatically syncing with your backend API, even when the app is closed. @@ -68,37 +68,36 @@ void callbackDispatcher() { Future _performDataSync() async { try { - // Fetch data from your API + // Fetch data from API + final uri = Uri.parse('https://api.yourapp.com/data'); final response = await http.get( - Uri.parse('https://api.yourapp.com/data'), - headers: {'Authorization': 'Bearer YOUR_TOKEN'}, + uri, + headers: {'Authorization': 'Bearer TOKEN'}, ).timeout(Duration(seconds: 30)); if (response.statusCode == 200) { final data = json.decode(response.body); - - // Store in local database (SQLite, Hive, etc.) await _storeDataLocally(data); - - print('Data sync completed successfully'); return true; } else { - print('API returned ${response.statusCode}'); + print('API error: ${response.statusCode}'); return false; // Retry later } } catch (e) { - print('Sync error: $e'); - return false; // Retry later + print('Sync failed: $e'); + return false; } } -Future _storeDataLocally(Map data) async { - // Example using SharedPreferences for simple data +Future _storeDataLocally( + Map data +) async { final prefs = await SharedPreferences.getInstance(); - await prefs.setString('last_sync_data', json.encode(data)); - await prefs.setString('last_sync_time', DateTime.now().toIso8601String()); - - // For complex data, use SQLite, Hive, or other local storage + await prefs.setString('sync_data', json.encode(data)); + await prefs.setString( + 'sync_time', + DateTime.now().toIso8601String() + ); } ``` diff --git a/docs/usecases/database-maintenance.mdx b/docs/usecases/database-maintenance.mdx index 93a3ca32..f96e3c3d 100644 --- a/docs/usecases/database-maintenance.mdx +++ b/docs/usecases/database-maintenance.mdx @@ -1,9 +1,9 @@ --- -title: Database Maintenance -description: Perform database cleanup, optimization, and maintenance tasks in the background +title: Example - Database Tasks +description: Example implementation for database maintenance tasks --- -# Database Maintenance +# Example: Database Maintenance Automatically maintain your app's local database by cleaning up old records, optimizing indexes, and performing routine maintenance tasks in the background. @@ -32,22 +32,23 @@ Future performDatabaseMaintenance() async { try { final db = await DatabaseHelper.getDatabase(); - // Remove old records (older than 30 days) + // Remove old records (30+ days) + final cutoff = DateTime.now() + .subtract(Duration(days: 30)) + .millisecondsSinceEpoch; + await db.delete( 'logs', where: 'created_at < ?', - whereArgs: [DateTime.now().subtract(Duration(days: 30)).millisecondsSinceEpoch], + whereArgs: [cutoff], ); - // Vacuum database to reclaim space - await db.execute('VACUUM'); - - // Update statistics - await db.execute('ANALYZE'); + // Optimize database (SQLite) + await db.execute('PRAGMA optimize'); return true; } catch (e) { - print('Database maintenance failed: $e'); + print('DB maintenance failed: $e'); return false; } } diff --git a/docs/usecases/fetch-notifications.mdx b/docs/usecases/fetch-notifications.mdx index e653afb6..025ac887 100644 --- a/docs/usecases/fetch-notifications.mdx +++ b/docs/usecases/fetch-notifications.mdx @@ -1,9 +1,9 @@ --- -title: Fetch Notifications -description: Check for new notifications and messages from your server in the background +title: Example - Notifications +description: Example implementation for background notification checking --- -# Background Notification Fetching +# Example: Notification Checking Automatically check for new notifications, messages, and updates from your server, even when the app is closed. diff --git a/docs/usecases/periodic-cleanup.mdx b/docs/usecases/periodic-cleanup.mdx index 73ba0b27..d6370b5f 100644 --- a/docs/usecases/periodic-cleanup.mdx +++ b/docs/usecases/periodic-cleanup.mdx @@ -1,9 +1,9 @@ --- -title: Periodic Cleanup -description: Automatically clean up old files, cache data, and maintain app performance +title: Example - Cleanup Tasks +description: Example implementation for periodic cleanup tasks --- -# Periodic Data Cleanup +# Example: Cleanup Tasks Keep your app performing well by automatically cleaning up old files, cached data, and temporary content in the background. diff --git a/docs/usecases/upload-files.mdx b/docs/usecases/upload-files.mdx index 55338085..96dc32e9 100644 --- a/docs/usecases/upload-files.mdx +++ b/docs/usecases/upload-files.mdx @@ -1,9 +1,9 @@ --- -title: Upload Files in Background -description: Upload photos, documents, and other files when network conditions are optimal +title: Example - File Upload +description: Example implementation for background file uploads --- -# Background File Uploads +# Example: Background File Upload Upload user files in the background, even when the app is closed, ensuring reliable delivery when network conditions are good. From 80811a4116025a86e64c8cbf3ffb93315d27b607 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 19:18:39 +0100 Subject: [PATCH 09/25] refactor: further mobile optimization of code examples - Shortened method names and variable declarations - Split long function calls across multiple lines appropriately - Removed verbose comments that take up mobile screen space - Made code more scannable on small screens --- docs/quickstart.mdx | 16 ++++++++-------- docs/usecases/upload-files.mdx | 19 +++++++++---------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index 1c45b77a..8b8941d4 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -53,8 +53,10 @@ iOS requires a 5-minute setup in Xcode: ```swift import workmanager -// In application(_:didFinishLaunchingWithOptions:) -WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "your.task.identifier") +// In application didFinishLaunching +WorkmanagerPlugin.registerBGProcessingTask( + withIdentifier: "your.task.identifier" +) ``` ## Basic Usage @@ -62,22 +64,21 @@ WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "your.task.identifier ### 1. Create Background Task Handler ```dart -@pragma('vm:entry-point') // Required for release builds +@pragma('vm:entry-point') void callbackDispatcher() { Workmanager().executeTask((task, inputData) async { print("Background task: $task"); - // Your background logic here switch (task) { case "data_sync": await syncDataWithServer(); break; - case "cleanup": + case "cleanup": await cleanupOldFiles(); break; } - return Future.value(true); // Task completed successfully + return Future.value(true); }); } ``` @@ -86,10 +87,9 @@ void callbackDispatcher() { ```dart void main() { - // Initialize Workmanager Workmanager().initialize( callbackDispatcher, - isInDebugMode: true, // Shows notifications when tasks run + isInDebugMode: true, ); runApp(MyApp()); diff --git a/docs/usecases/upload-files.mdx b/docs/usecases/upload-files.mdx index 96dc32e9..c8333410 100644 --- a/docs/usecases/upload-files.mdx +++ b/docs/usecases/upload-files.mdx @@ -73,19 +73,18 @@ Future _uploadFile(Map inputData) async { } // Create multipart request - final request = http.MultipartRequest( - 'POST', - Uri.parse('https://api.yourapp.com/upload'), - ); + final uri = Uri.parse('https://api.yourapp.com/upload'); + final request = http.MultipartRequest('POST', uri); - request.headers['Authorization'] = 'Bearer YOUR_TOKEN'; - request.fields.addAll(metadata.map((k, v) => MapEntry(k, v.toString()))); + request.headers['Authorization'] = 'Bearer TOKEN'; + request.fields.addAll( + metadata.map((k, v) => MapEntry(k, v.toString())) + ); - final multipartFile = await http.MultipartFile.fromPath( - 'file', - filePath, + final file = await http.MultipartFile.fromPath( + 'file', filePath ); - request.files.add(multipartFile); + request.files.add(file); final response = await request.send().timeout(Duration(minutes: 5)); From 6312c91fd255f77cf01495315d702b694e4b9826 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 19:28:18 +0100 Subject: [PATCH 10/25] fix: resolve mobile menu and duplicate titles - Fixed _sidebar.md format with asterisks and leading slashes for docs.page compatibility - Removed duplicate H1 headings from all pages (frontmatter title is sufficient) - Mobile menu should now display properly with navigation items - Cleaner page titles without repetition --- docs/_sidebar.md | 17 +++++++++-------- docs/debugging.mdx | 2 -- docs/index.mdx | 2 -- docs/quickstart.mdx | 2 -- docs/usecases/data-sync.mdx | 4 +--- docs/usecases/database-maintenance.mdx | 4 +--- docs/usecases/fetch-notifications.mdx | 4 +--- docs/usecases/periodic-cleanup.mdx | 4 +--- docs/usecases/upload-files.mdx | 4 +--- 9 files changed, 14 insertions(+), 29 deletions(-) diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 41ec6195..1ddbc40a 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -1,8 +1,9 @@ -- [Get Started](quickstart) -- **Examples** - - [Data Sync](usecases/data-sync) - - [File Upload](usecases/upload-files) - - [Cleanup Tasks](usecases/periodic-cleanup) - - [Notifications](usecases/fetch-notifications) - - [Database Tasks](usecases/database-maintenance) -- [Debugging](debugging) \ No newline at end of file +* [Home](/) +* [Get Started](/quickstart) +* **Examples** + * [Data Sync](/usecases/data-sync) + * [File Upload](/usecases/upload-files) + * [Cleanup Tasks](/usecases/periodic-cleanup) + * [Notifications](/usecases/fetch-notifications) + * [Database Tasks](/usecases/database-maintenance) +* [Debugging](/debugging) \ No newline at end of file diff --git a/docs/debugging.mdx b/docs/debugging.mdx index c8338ad0..dd4dcff4 100644 --- a/docs/debugging.mdx +++ b/docs/debugging.mdx @@ -3,8 +3,6 @@ title: Debugging Background Tasks description: Debug and troubleshoot background tasks on Android and iOS --- -# Debugging Background Tasks - Background tasks can be tricky to debug since they run when your app is closed. Here's how to effectively debug and troubleshoot them on both platforms. ## Enable Debug Mode diff --git a/docs/index.mdx b/docs/index.mdx index e93e52c8..f0acd266 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -3,8 +3,6 @@ title: Flutter Workmanager description: Background task execution for Flutter apps --- -# Flutter Workmanager - Execute Dart code in the background, even when your app is closed. Perfect for data sync, file uploads, and periodic maintenance tasks. ## Key Features diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index 8b8941d4..4b53ebd1 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -3,8 +3,6 @@ title: Quick Start description: Get started with Flutter Workmanager in minutes --- -# Quick Start - Get Flutter Workmanager up and running in your app quickly. ## Installation diff --git a/docs/usecases/data-sync.mdx b/docs/usecases/data-sync.mdx index cc3c3005..452fc165 100644 --- a/docs/usecases/data-sync.mdx +++ b/docs/usecases/data-sync.mdx @@ -1,10 +1,8 @@ --- -title: Example - Data Sync +title: Data Sync Example description: Example implementation for syncing API data in background --- -# Example: API Data Sync - Keep your app's data fresh by automatically syncing with your backend API, even when the app is closed. ## When to Use This diff --git a/docs/usecases/database-maintenance.mdx b/docs/usecases/database-maintenance.mdx index f96e3c3d..44ba1352 100644 --- a/docs/usecases/database-maintenance.mdx +++ b/docs/usecases/database-maintenance.mdx @@ -1,10 +1,8 @@ --- -title: Example - Database Tasks +title: Database Tasks Example description: Example implementation for database maintenance tasks --- -# Example: Database Maintenance - Automatically maintain your app's local database by cleaning up old records, optimizing indexes, and performing routine maintenance tasks in the background. ## When to Use This diff --git a/docs/usecases/fetch-notifications.mdx b/docs/usecases/fetch-notifications.mdx index 025ac887..62a2bc37 100644 --- a/docs/usecases/fetch-notifications.mdx +++ b/docs/usecases/fetch-notifications.mdx @@ -1,10 +1,8 @@ --- -title: Example - Notifications +title: Notifications Example description: Example implementation for background notification checking --- -# Example: Notification Checking - Automatically check for new notifications, messages, and updates from your server, even when the app is closed. ## When to Use This diff --git a/docs/usecases/periodic-cleanup.mdx b/docs/usecases/periodic-cleanup.mdx index d6370b5f..8c4e1762 100644 --- a/docs/usecases/periodic-cleanup.mdx +++ b/docs/usecases/periodic-cleanup.mdx @@ -1,10 +1,8 @@ --- -title: Example - Cleanup Tasks +title: Cleanup Tasks Example description: Example implementation for periodic cleanup tasks --- -# Example: Cleanup Tasks - Keep your app performing well by automatically cleaning up old files, cached data, and temporary content in the background. ## When to Use This diff --git a/docs/usecases/upload-files.mdx b/docs/usecases/upload-files.mdx index c8333410..0498b94a 100644 --- a/docs/usecases/upload-files.mdx +++ b/docs/usecases/upload-files.mdx @@ -1,10 +1,8 @@ --- -title: Example - File Upload +title: File Upload Example description: Example implementation for background file uploads --- -# Example: Background File Upload - Upload user files in the background, even when the app is closed, ensuring reliable delivery when network conditions are good. ## When to Use This From 3139e5ca1fa319e4431e346144e29378bb390748 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 19:53:24 +0100 Subject: [PATCH 11/25] fix: add multiple sidebar configurations for docs.page compatibility - Added sidebar.json for JSON-based navigation - Updated docs.json with navigation array - Trying multiple approaches to ensure mobile menu displays --- docs.json | 17 ++++++++++++++++- docs/sidebar.json | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 docs/sidebar.json diff --git a/docs.json b/docs.json index de851689..3dadc0d6 100644 --- a/docs.json +++ b/docs.json @@ -1,4 +1,19 @@ { "name": "Flutter Workmanager", - "description": "Background task execution for Flutter apps" + "description": "Background task execution for Flutter apps", + "navigation": [ + { "text": "Home", "link": "/" }, + { "text": "Get Started", "link": "/quickstart" }, + { + "text": "Examples", + "items": [ + { "text": "Data Sync", "link": "/usecases/data-sync" }, + { "text": "File Upload", "link": "/usecases/upload-files" }, + { "text": "Cleanup Tasks", "link": "/usecases/periodic-cleanup" }, + { "text": "Notifications", "link": "/usecases/fetch-notifications" }, + { "text": "Database Tasks", "link": "/usecases/database-maintenance" } + ] + }, + { "text": "Debugging", "link": "/debugging" } + ] } \ No newline at end of file diff --git a/docs/sidebar.json b/docs/sidebar.json new file mode 100644 index 00000000..2e4d3f7d --- /dev/null +++ b/docs/sidebar.json @@ -0,0 +1,39 @@ +[ + { + "text": "Home", + "link": "/" + }, + { + "text": "Get Started", + "link": "/quickstart" + }, + { + "text": "Examples", + "children": [ + { + "text": "Data Sync", + "link": "/usecases/data-sync" + }, + { + "text": "File Upload", + "link": "/usecases/upload-files" + }, + { + "text": "Cleanup Tasks", + "link": "/usecases/periodic-cleanup" + }, + { + "text": "Notifications", + "link": "/usecases/fetch-notifications" + }, + { + "text": "Database Tasks", + "link": "/usecases/database-maintenance" + } + ] + }, + { + "text": "Debugging", + "link": "/debugging" + } +] \ No newline at end of file From ee52fb0b3a98c237c878bfd22ed4f9a30a520387 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 22:17:33 +0100 Subject: [PATCH 12/25] fix: remove conflicting sidebar files and use correct docs.page format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove _sidebar.md and sidebar.json that were causing conflicts - Update docs.json with proper docs.page sidebar structure using title/href - Fix mobile menu configuration for docs.page - Ensure all pages use correct frontmatter without duplicate H1 headings πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- docs.json | 46 +++++++++++++++++++++++++++++++++++----------- docs/_sidebar.md | 9 --------- docs/sidebar.json | 39 --------------------------------------- 3 files changed, 35 insertions(+), 59 deletions(-) delete mode 100644 docs/_sidebar.md delete mode 100644 docs/sidebar.json diff --git a/docs.json b/docs.json index 3dadc0d6..7a3c5c62 100644 --- a/docs.json +++ b/docs.json @@ -1,19 +1,43 @@ { "name": "Flutter Workmanager", "description": "Background task execution for Flutter apps", - "navigation": [ - { "text": "Home", "link": "/" }, - { "text": "Get Started", "link": "/quickstart" }, + "sidebar": [ { - "text": "Examples", - "items": [ - { "text": "Data Sync", "link": "/usecases/data-sync" }, - { "text": "File Upload", "link": "/usecases/upload-files" }, - { "text": "Cleanup Tasks", "link": "/usecases/periodic-cleanup" }, - { "text": "Notifications", "link": "/usecases/fetch-notifications" }, - { "text": "Database Tasks", "link": "/usecases/database-maintenance" } + "title": "Home", + "href": "/" + }, + { + "title": "Get Started", + "href": "/quickstart" + }, + { + "group": "Examples", + "pages": [ + { + "title": "Data Sync", + "href": "/usecases/data-sync" + }, + { + "title": "File Upload", + "href": "/usecases/upload-files" + }, + { + "title": "Cleanup Tasks", + "href": "/usecases/periodic-cleanup" + }, + { + "title": "Notifications", + "href": "/usecases/fetch-notifications" + }, + { + "title": "Database Tasks", + "href": "/usecases/database-maintenance" + } ] }, - { "text": "Debugging", "link": "/debugging" } + { + "title": "Debugging", + "href": "/debugging" + } ] } \ No newline at end of file diff --git a/docs/_sidebar.md b/docs/_sidebar.md deleted file mode 100644 index 1ddbc40a..00000000 --- a/docs/_sidebar.md +++ /dev/null @@ -1,9 +0,0 @@ -* [Home](/) -* [Get Started](/quickstart) -* **Examples** - * [Data Sync](/usecases/data-sync) - * [File Upload](/usecases/upload-files) - * [Cleanup Tasks](/usecases/periodic-cleanup) - * [Notifications](/usecases/fetch-notifications) - * [Database Tasks](/usecases/database-maintenance) -* [Debugging](/debugging) \ No newline at end of file diff --git a/docs/sidebar.json b/docs/sidebar.json deleted file mode 100644 index 2e4d3f7d..00000000 --- a/docs/sidebar.json +++ /dev/null @@ -1,39 +0,0 @@ -[ - { - "text": "Home", - "link": "/" - }, - { - "text": "Get Started", - "link": "/quickstart" - }, - { - "text": "Examples", - "children": [ - { - "text": "Data Sync", - "link": "/usecases/data-sync" - }, - { - "text": "File Upload", - "link": "/usecases/upload-files" - }, - { - "text": "Cleanup Tasks", - "link": "/usecases/periodic-cleanup" - }, - { - "text": "Notifications", - "link": "/usecases/fetch-notifications" - }, - { - "text": "Database Tasks", - "link": "/usecases/database-maintenance" - } - ] - }, - { - "text": "Debugging", - "link": "/debugging" - } -] \ No newline at end of file From db4da8dcf640b109278da50a887f593d078dbd58 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 22:19:41 +0100 Subject: [PATCH 13/25] fix: use proper docs.page sidebar format with tab and group structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add 'tab: root' property to all sidebar groups - Organize navigation into Getting Started, Examples, and Reference groups - Follow official docs.page sidebar configuration format - Should fix mobile menu visibility issue πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- docs.json | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/docs.json b/docs.json index 7a3c5c62..33fd9ef1 100644 --- a/docs.json +++ b/docs.json @@ -3,15 +3,22 @@ "description": "Background task execution for Flutter apps", "sidebar": [ { - "title": "Home", - "href": "/" - }, - { - "title": "Get Started", - "href": "/quickstart" + "group": "Getting Started", + "tab": "root", + "pages": [ + { + "title": "Overview", + "href": "/" + }, + { + "title": "Quick Start", + "href": "/quickstart" + } + ] }, { "group": "Examples", + "tab": "root", "pages": [ { "title": "Data Sync", @@ -36,8 +43,14 @@ ] }, { - "title": "Debugging", - "href": "/debugging" + "group": "Reference", + "tab": "root", + "pages": [ + { + "title": "Debugging", + "href": "/debugging" + } + ] } ] } \ No newline at end of file From aaff2a98585cacbdcf2b5c410311297df1e941e7 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 22:27:06 +0100 Subject: [PATCH 14/25] feat: improve documentation UX and content quality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add dark blue theme with higher contrast (#1e3a8a primary) - Remove redundant cross-links between use case pages (handled by sidebar) - Eliminate repetitive intro paragraph from quickstart page - Add Xcode Help link for Background Modes configuration - Add explanatory callout for iOS task identifier requirements - Improve user guidance and reduce confusion πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- docs.json | 4 ++++ docs/quickstart.mdx | 6 +++--- docs/usecases/data-sync.mdx | 4 ---- docs/usecases/upload-files.mdx | 4 ---- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/docs.json b/docs.json index 33fd9ef1..8fdb5d0e 100644 --- a/docs.json +++ b/docs.json @@ -1,6 +1,10 @@ { "name": "Flutter Workmanager", "description": "Background task execution for Flutter apps", + "theme": { + "primaryColor": "#1e3a8a", + "accentColor": "#3b82f6" + }, "sidebar": [ { "group": "Getting Started", diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index 4b53ebd1..777af78c 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -3,8 +3,6 @@ title: Quick Start description: Get started with Flutter Workmanager in minutes --- -Get Flutter Workmanager up and running in your app quickly. - ## Installation Add `workmanager` to your `pubspec.yaml`: @@ -29,7 +27,7 @@ Android works automatically - no additional setup required! βœ… ### iOS iOS requires a 5-minute setup in Xcode: -1. **Enable Background Modes** in Xcode target capabilities: +1. **Enable Background Modes** in Xcode target capabilities ([Xcode Help](https://help.apple.com/xcode/mac/current/#/devbfa1532c4)): - Background processing βœ… - Background fetch βœ… @@ -57,6 +55,8 @@ WorkmanagerPlugin.registerBGProcessingTask( ) ``` +> **Why this configuration is needed:** iOS requires explicit registration of background task identifiers for security and system resource management. The task identifier in Info.plist tells iOS which background tasks your app can schedule, while the AppDelegate registration connects the identifier to the actual task handler. This prevents apps from scheduling unauthorized background work. + ## Basic Usage ### 1. Create Background Task Handler diff --git a/docs/usecases/data-sync.mdx b/docs/usecases/data-sync.mdx index 452fc165..7024096c 100644 --- a/docs/usecases/data-sync.mdx +++ b/docs/usecases/data-sync.mdx @@ -334,7 +334,3 @@ Future _performIncrementalSync() async { - Use compression for large payloads - Cache API responses appropriately -## Related Use Cases - -- **[Upload Files](upload-files)** - Upload user content in the background -- **[Fetch Notifications](fetch-notifications)** - Check for new notifications from your server \ No newline at end of file diff --git a/docs/usecases/upload-files.mdx b/docs/usecases/upload-files.mdx index 0498b94a..635f64fe 100644 --- a/docs/usecases/upload-files.mdx +++ b/docs/usecases/upload-files.mdx @@ -265,7 +265,3 @@ Future _uploadWithRetry(Map inputData) async { } ``` -## Related Use Cases - -- **[Data Sync](data-sync)** - Synchronize data with your backend -- **[Database Maintenance](database-maintenance)** - Clean up and optimize local data \ No newline at end of file From f8e5c418f164d24d7bedb2b976027afc257a3551 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 22:36:36 +0100 Subject: [PATCH 15/25] feat: enhance quickstart with clear iOS API choice and proper callouts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix callouts to use proper docs.page syntax (Info, Success components) - Improve color theme contrast for better light theme readability - Restructure iOS setup with clear decision tree: Periodic vs Processing tasks - Default to Background Fetch API (registerPeriodicTask) for common use cases - Add BGTaskScheduler option for complex operations requiring longer execution - Add Xcode 'Perform Fetch' debugging option to Xcode debugging section - Provide clear guidance on which API to choose based on use case πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- docs.json | 5 +++-- docs/debugging.mdx | 2 +- docs/quickstart.mdx | 46 ++++++++++++++++++++++++++++++++++++++------- 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/docs.json b/docs.json index 8fdb5d0e..70ebd576 100644 --- a/docs.json +++ b/docs.json @@ -2,8 +2,9 @@ "name": "Flutter Workmanager", "description": "Background task execution for Flutter apps", "theme": { - "primaryColor": "#1e3a8a", - "accentColor": "#3b82f6" + "primaryColor": "#1e40af", + "accentColor": "#2563eb", + "textColor": "#1f2937" }, "sidebar": [ { diff --git a/docs/debugging.mdx b/docs/debugging.mdx index dd4dcff4..b681c69a 100644 --- a/docs/debugging.mdx +++ b/docs/debugging.mdx @@ -97,7 +97,7 @@ void callbackDispatcher() { ### Xcode Debugging -Use Xcode's console to trigger background tasks manually: +Use Xcode's console to trigger background tasks manually, or use **Debug β†’ Perform Fetch** from the Xcode run menu: ```objc // Trigger background refresh diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index 777af78c..bca5b458 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -25,37 +25,69 @@ flutter pub get Android works automatically - no additional setup required! βœ… ### iOS -iOS requires a 5-minute setup in Xcode: +iOS requires a 5-minute setup in Xcode. Choose your approach based on your needs: + +#### Option A: Periodic Tasks (Recommended for most use cases) +For regular data sync, notifications, cleanup - uses iOS Background Fetch: 1. **Enable Background Modes** in Xcode target capabilities ([Xcode Help](https://help.apple.com/xcode/mac/current/#/devbfa1532c4)): - - Background processing βœ… - Background fetch βœ… 2. **Add to Info.plist**: ```xml UIBackgroundModes - processing fetch +``` + +3. **Configure AppDelegate.swift**: +```swift +import workmanager_apple + +// In application didFinishLaunching +WorkmanagerPlugin.registerPeriodicTask( + withIdentifier: "com.yourapp.periodic_task", + frequency: NSNumber(value: 15 * 60) // 15 minutes minimum +) +``` + +#### Option B: Processing Tasks (For complex operations) +For file uploads, data processing, longer tasks - uses BGTaskScheduler: + +1. **Enable Background Modes** in Xcode target capabilities ([Xcode Help](https://help.apple.com/xcode/mac/current/#/devbfa1532c4)): + - Background processing βœ… + +2. **Add to Info.plist**: +```xml +UIBackgroundModes + + processing + BGTaskSchedulerPermittedIdentifiers - your.task.identifier + com.yourapp.processing_task ``` 3. **Configure AppDelegate.swift**: ```swift -import workmanager +import workmanager_apple // In application didFinishLaunching WorkmanagerPlugin.registerBGProcessingTask( - withIdentifier: "your.task.identifier" + withIdentifier: "com.yourapp.processing_task" ) ``` -> **Why this configuration is needed:** iOS requires explicit registration of background task identifiers for security and system resource management. The task identifier in Info.plist tells iOS which background tasks your app can schedule, while the AppDelegate registration connects the identifier to the actual task handler. This prevents apps from scheduling unauthorized background work. + +**Why this configuration is needed:** iOS requires explicit registration of background task identifiers for security and system resource management. The task identifier in Info.plist tells iOS which background tasks your app can schedule, while the AppDelegate registration connects the identifier to the actual task handler. This prevents apps from scheduling unauthorized background work. + + + +**Which option to choose?** Use **Option A (Periodic Tasks)** for most use cases like data sync, notifications, and cleanup. Use **Option B (Processing Tasks)** only when you need longer execution time or fine-grained control over task scheduling. + ## Basic Usage From 5f120ea49b5d727911a2f33c130f0a231347b921 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 22:37:01 +0100 Subject: [PATCH 16/25] fix: clarify iOS Background Fetch needs no AppDelegate configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove unnecessary registerPeriodicTask from Background Fetch setup - Clarify that only BGTaskScheduler (Option B) requires AppDelegate registration - Background Fetch (Option A) works automatically with just Info.plist setup - Update info callout to explain difference between the two approaches - Make it clear which setup steps apply to which iOS background API πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- docs/quickstart.mdx | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index bca5b458..8efae035 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -41,16 +41,9 @@ For regular data sync, notifications, cleanup - uses iOS Background Fetch: ``` -3. **Configure AppDelegate.swift**: -```swift -import workmanager_apple - -// In application didFinishLaunching -WorkmanagerPlugin.registerPeriodicTask( - withIdentifier: "com.yourapp.periodic_task", - frequency: NSNumber(value: 15 * 60) // 15 minutes minimum -) -``` +3. **No AppDelegate configuration needed!** βœ… + + The Background Fetch approach works automatically - just schedule tasks from your Dart code. #### Option B: Processing Tasks (For complex operations) For file uploads, data processing, longer tasks - uses BGTaskScheduler: @@ -71,7 +64,7 @@ For file uploads, data processing, longer tasks - uses BGTaskScheduler: ``` -3. **Configure AppDelegate.swift**: +3. **Configure AppDelegate.swift** (required for BGTaskScheduler): ```swift import workmanager_apple @@ -82,7 +75,7 @@ WorkmanagerPlugin.registerBGProcessingTask( ``` -**Why this configuration is needed:** iOS requires explicit registration of background task identifiers for security and system resource management. The task identifier in Info.plist tells iOS which background tasks your app can schedule, while the AppDelegate registration connects the identifier to the actual task handler. This prevents apps from scheduling unauthorized background work. +**Why BGTaskScheduler registration is needed:** iOS requires explicit registration of background task identifiers for security and system resource management. The task identifier in Info.plist tells iOS which background tasks your app can schedule, while the AppDelegate registration connects the identifier to the actual task handler. Background Fetch (Option A) doesn't require this since it uses the simpler, system-managed approach. From 0e3b5b9a9a0c9104b5436225568d720c21440ac9 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 22:37:34 +0100 Subject: [PATCH 17/25] clarify: iOS Background Fetch is system-scheduled once daily MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add warning that iOS Background Fetch runs typically once per day - Explain that scheduling is entirely controlled by iOS based on user patterns - Cannot force immediate execution - designed for non-critical updates - Update decision guidance to reflect daily scheduling limitation - Help users understand Background Fetch vs BGTaskScheduler timing differences πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- docs/quickstart.mdx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index 8efae035..eaf91cb8 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -45,6 +45,10 @@ For regular data sync, notifications, cleanup - uses iOS Background Fetch: The Background Fetch approach works automatically - just schedule tasks from your Dart code. + +**iOS Background Fetch scheduling:** iOS completely controls when Background Fetch runs (typically once per day based on user app usage patterns). You cannot force immediate execution - it's designed for non-critical periodic updates like refreshing content. + + #### Option B: Processing Tasks (For complex operations) For file uploads, data processing, longer tasks - uses BGTaskScheduler: @@ -79,7 +83,7 @@ WorkmanagerPlugin.registerBGProcessingTask( -**Which option to choose?** Use **Option A (Periodic Tasks)** for most use cases like data sync, notifications, and cleanup. Use **Option B (Processing Tasks)** only when you need longer execution time or fine-grained control over task scheduling. +**Which option to choose?** Use **Option A (Background Fetch)** for non-critical updates that can happen once daily (data sync, content refresh). Use **Option B (BGTaskScheduler)** when you need more control over timing, longer execution time, or immediate task scheduling. ## Basic Usage From 7d7331ff709781a5b66faecd2d68b8795166d2f6 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 22:43:49 +0100 Subject: [PATCH 18/25] feat: comprehensive iOS setup and debugging improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Option C: registerPeriodicTask with custom frequency control (15+ min) - Clarify three distinct iOS approaches: Background Fetch vs BGTaskScheduler vs Periodic - Remove debugging code from quickstart, link to dedicated debugging page - Fix confusing Xcode debugging: separate 'Perform Fetch' from BGTaskScheduler commands - Add Apple WWDC and upstream documentation links to Common iOS Issues - Include relevant Apple Support, Developer Documentation, and WWDC session links - Provide comprehensive guidance for iOS background task debugging πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- docs/debugging.mdx | 29 +++++++++++---------- docs/quickstart.mdx | 61 ++++++++++++++++++++++++++------------------- 2 files changed, 51 insertions(+), 39 deletions(-) diff --git a/docs/debugging.mdx b/docs/debugging.mdx index b681c69a..65b66803 100644 --- a/docs/debugging.mdx +++ b/docs/debugging.mdx @@ -97,16 +97,16 @@ void callbackDispatcher() { ### Xcode Debugging -Use Xcode's console to trigger background tasks manually, or use **Debug β†’ Perform Fetch** from the Xcode run menu: +**For Background Fetch tasks:** +Use **Debug β†’ Perform Fetch** from the Xcode run menu while your app is running. -```objc -// Trigger background refresh -e -l objc -- (void)[[BGTaskScheduler sharedScheduler] - _simulateLaunchForTaskWithIdentifier:@"task.id"] +**For BGTaskScheduler tasks (processing/periodic):** +Use Xcode's console to trigger tasks manually: -// Trigger processing task +```objc +// Trigger specific BGTaskScheduler task e -l objc -- (void)[[BGTaskScheduler sharedScheduler] - _simulateLaunchForTaskWithIdentifier:@"process.id"] + _simulateLaunchForTaskWithIdentifier:@"com.yourapp.task.identifier"] ``` ### Monitor Scheduled Tasks @@ -129,22 +129,25 @@ This prints output like: ### Common iOS Issues +**Background App Refresh disabled:** +- Check iOS Settings β†’ General β†’ Background App Refresh +- See [Apple's Background App Refresh Guide](https://support.apple.com/en-us/102425) + **Tasks never run:** -- Background App Refresh disabled in Settings -- App hasn't been used recently (iOS learning algorithm) +- App hasn't been used recently (iOS learning algorithm) - [iOS Power Management](https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/PrioritizeWorkAtTheTaskLevel.html) - Task identifiers don't match between Info.plist and AppDelegate - Missing BGTaskSchedulerPermittedIdentifiers in Info.plist **Tasks stop working:** -- iOS battery optimization kicked in +- iOS battery optimization kicked in - [WWDC 2020: Background execution demystified](https://developer.apple.com/videos/play/wwdc2020/10063/) - App removed from recent apps too often - User disabled background refresh -- Task taking longer than 30 seconds +- Task taking longer than 30 seconds - [BGTaskScheduler Documentation](https://developer.apple.com/documentation/backgroundtasks/bgtaskscheduler) **Tasks run but don't complete:** -- Hitting 30-second execution limit +- Hitting 30-second execution limit - [Background Tasks Best Practices](https://developer.apple.com/documentation/backgroundtasks/bgtask) - Network requests timing out -- Heavy processing blocking the thread +- Heavy processing blocking the thread - [WWDC 2019: Advances in App Background Execution](https://developer.apple.com/videos/play/wwdc2019/707/) ## General Debugging Tips diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index eaf91cb8..26b1dd09 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -82,8 +82,41 @@ WorkmanagerPlugin.registerBGProcessingTask( **Why BGTaskScheduler registration is needed:** iOS requires explicit registration of background task identifiers for security and system resource management. The task identifier in Info.plist tells iOS which background tasks your app can schedule, while the AppDelegate registration connects the identifier to the actual task handler. Background Fetch (Option A) doesn't require this since it uses the simpler, system-managed approach. +#### Option C: Periodic Tasks with Custom Frequency +For periodic tasks with more control than Background Fetch - uses BGTaskScheduler with frequency: + +1. **Enable Background Modes** in Xcode target capabilities ([Xcode Help](https://help.apple.com/xcode/mac/current/#/devbfa1532c4)): + - Background processing βœ… + +2. **Add to Info.plist**: +```xml +UIBackgroundModes + + processing + + +BGTaskSchedulerPermittedIdentifiers + + com.yourapp.periodic_task + +``` + +3. **Configure AppDelegate.swift** (with frequency control): +```swift +import workmanager_apple + +// In application didFinishLaunching +WorkmanagerPlugin.registerPeriodicTask( + withIdentifier: "com.yourapp.periodic_task", + frequency: NSNumber(value: 20 * 60) // 20 minutes (15 min minimum) +) +``` + -**Which option to choose?** Use **Option A (Background Fetch)** for non-critical updates that can happen once daily (data sync, content refresh). Use **Option B (BGTaskScheduler)** when you need more control over timing, longer execution time, or immediate task scheduling. +**Which option to choose?** +- **Option A (Background Fetch)** for non-critical updates that can happen once daily (data sync, content refresh) +- **Option B (BGTaskScheduler)** for one-time tasks, file uploads, or immediate task scheduling +- **Option C (Periodic Tasks)** for regular tasks with custom frequency control (15+ minutes) ## Basic Usage @@ -186,31 +219,7 @@ Your background tasks can return: ## Debugging -Enable debug mode to see notifications when tasks run: - -```dart -Workmanager().initialize( - callbackDispatcher, - isInDebugMode: true, // Shows debug notifications -); -``` - -Add error handling: - -```dart -@pragma('vm:entry-point') -void callbackDispatcher() { - Workmanager().executeTask((task, inputData) async { - try { - // Your task logic - return Future.value(true); - } catch (e) { - print('Task failed: $e'); - return Future.value(false); // Retry - } - }); -} -``` +For comprehensive debugging guidance including platform-specific tools, ADB commands, and Xcode debugging, see the **[Debugging Guide](debugging)**. ## Next Steps From 91f1265ae772092daca03d56d973d63dcd2d7869 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 22:51:27 +0100 Subject: [PATCH 19/25] feat: simplify docs by removing complex use case pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove convoluted use case pages (data-sync, upload-files, etc.) - Eliminate 'Examples' section from navigation - now just Getting Started + Reference - Keep use case descriptions in main page table for overview without links - Focus documentation on essential guides: Overview, Quickstart, Debugging - Point users to example app for complete implementation reference - Cleaner, more focused documentation structure πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- docs.json | 26 -- docs/index.mdx | 10 +- docs/quickstart.mdx | 2 +- docs/usecases/data-sync.mdx | 336 ------------------------- docs/usecases/database-maintenance.mdx | 70 ------ docs/usecases/fetch-notifications.mdx | 71 ------ docs/usecases/periodic-cleanup.mdx | 72 ------ docs/usecases/upload-files.mdx | 267 -------------------- 8 files changed, 6 insertions(+), 848 deletions(-) delete mode 100644 docs/usecases/data-sync.mdx delete mode 100644 docs/usecases/database-maintenance.mdx delete mode 100644 docs/usecases/fetch-notifications.mdx delete mode 100644 docs/usecases/periodic-cleanup.mdx delete mode 100644 docs/usecases/upload-files.mdx diff --git a/docs.json b/docs.json index 70ebd576..6d336062 100644 --- a/docs.json +++ b/docs.json @@ -21,32 +21,6 @@ } ] }, - { - "group": "Examples", - "tab": "root", - "pages": [ - { - "title": "Data Sync", - "href": "/usecases/data-sync" - }, - { - "title": "File Upload", - "href": "/usecases/upload-files" - }, - { - "title": "Cleanup Tasks", - "href": "/usecases/periodic-cleanup" - }, - { - "title": "Notifications", - "href": "/usecases/fetch-notifications" - }, - { - "title": "Database Tasks", - "href": "/usecases/database-maintenance" - } - ] - }, { "group": "Reference", "tab": "root", diff --git a/docs/index.mdx b/docs/index.mdx index f0acd266..70ddd9cb 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -26,11 +26,11 @@ Execute Dart code in the background, even when your app is closed. Perfect for d | Use Case | Description | |----------|-------------| -| **[Sync data from API](usecases/data-sync)** | Automatically fetch and sync data from your backend API | -| **[Upload files in background](usecases/upload-files)** | Upload photos, documents when network conditions are optimal | -| **[Clean up old data](usecases/periodic-cleanup)** | Remove old files, cache data, and maintain app performance | -| **[Fetch notifications](usecases/fetch-notifications)** | Check for new notifications and messages from your server | -| **[Database maintenance](usecases/database-maintenance)** | Perform database cleanup and optimization tasks | +| **Sync data from API** | Automatically fetch and sync data from your backend API | +| **Upload files in background** | Upload photos, documents when network conditions are optimal | +| **Clean up old data** | Remove old files, cache data, and maintain app performance | +| **Fetch notifications** | Check for new notifications and messages from your server | +| **Database maintenance** | Perform database cleanup and optimization tasks | ## Architecture diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index 26b1dd09..f60dbdad 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -223,5 +223,5 @@ For comprehensive debugging guidance including platform-specific tools, ADB comm ## Next Steps -- **[Use Cases](usecases/data-sync)** - See real-world examples +- **[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 \ No newline at end of file diff --git a/docs/usecases/data-sync.mdx b/docs/usecases/data-sync.mdx deleted file mode 100644 index 7024096c..00000000 --- a/docs/usecases/data-sync.mdx +++ /dev/null @@ -1,336 +0,0 @@ ---- -title: Data Sync Example -description: Example implementation for syncing API data in background ---- - -Keep your app's data fresh by automatically syncing with your backend API, even when the app is closed. - -## When to Use This - -- News apps fetching latest articles -- Social media apps syncing posts and messages -- E-commerce apps updating product catalogs -- Weather apps refreshing forecasts -- Any app that needs fresh data from an API - -## Complete Implementation - -### 1. Set Up the Background Task - -```dart lib/services/data_sync_service.dart -import 'package:workmanager/workmanager.dart'; -import 'package:http/http.dart' as http; -import 'dart:convert'; - -class DataSyncService { - static const String syncTaskName = "data_sync_task"; - static const String syncTaskId = "periodic_data_sync"; - - static void initialize() { - Workmanager().initialize(callbackDispatcher); - } - - static void scheduleSync() { - Workmanager().registerPeriodicTask( - syncTaskId, - syncTaskName, - frequency: Duration(hours: 1), // Sync every hour - constraints: Constraints( - networkType: NetworkType.connected, // Require internet - requiresBatteryNotLow: true, // Don't drain battery - ), - ); - } - - static void cancelSync() { - Workmanager().cancelByUniqueName(syncTaskId); - } -} - -@pragma('vm:entry-point') -void callbackDispatcher() { - Workmanager().executeTask((task, inputData) async { - try { - switch (task) { - case DataSyncService.syncTaskName: - return await _performDataSync(); - default: - return Future.value(false); - } - } catch (e) { - print('Background sync failed: $e'); - return Future.value(false); // Retry on next opportunity - } - }); -} - -Future _performDataSync() async { - try { - // Fetch data from API - final uri = Uri.parse('https://api.yourapp.com/data'); - final response = await http.get( - uri, - headers: {'Authorization': 'Bearer TOKEN'}, - ).timeout(Duration(seconds: 30)); - - if (response.statusCode == 200) { - final data = json.decode(response.body); - await _storeDataLocally(data); - return true; - } else { - print('API error: ${response.statusCode}'); - return false; // Retry later - } - } catch (e) { - print('Sync failed: $e'); - return false; - } -} - -Future _storeDataLocally( - Map data -) async { - final prefs = await SharedPreferences.getInstance(); - await prefs.setString('sync_data', json.encode(data)); - await prefs.setString( - 'sync_time', - DateTime.now().toIso8601String() - ); -} -``` - -### 2. Initialize in Your App - -```dart main.dart -import 'package:flutter/material.dart'; -import 'services/data_sync_service.dart'; - -void main() { - DataSyncService.initialize(); - runApp(MyApp()); -} - -class MyApp extends StatefulWidget { - @override - _MyAppState createState() => _MyAppState(); -} - -class _MyAppState extends State { - @override - void initState() { - super.initState(); - // Start background sync when app launches - DataSyncService.scheduleSync(); - } - - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Data Sync App', - home: DataSyncHomePage(), - ); - } -} -``` - -### 3. Show Sync Status to Users - -```dart lib/widgets/sync_status_widget.dart -class SyncStatusWidget extends StatefulWidget { - @override - _SyncStatusWidgetState createState() => _SyncStatusWidgetState(); -} - -class _SyncStatusWidgetState extends State { - String _lastSyncTime = "Never"; - - @override - void initState() { - super.initState(); - _loadLastSyncTime(); - } - - Future _loadLastSyncTime() async { - final prefs = await SharedPreferences.getInstance(); - final syncTime = prefs.getString('last_sync_time'); - if (syncTime != null) { - final dateTime = DateTime.parse(syncTime); - setState(() { - _lastSyncTime = timeago.format(dateTime); - }); - } - } - - @override - Widget build(BuildContext context) { - return Card( - child: ListTile( - leading: Icon(Icons.sync), - title: Text('Background Sync'), - subtitle: Text('Last sync: $_lastSyncTime'), - trailing: IconButton( - icon: Icon(Icons.refresh), - onPressed: () { - // Trigger immediate sync (optional) - _performManualSync(); - }, - ), - ), - ); - } - - void _performManualSync() { - // Schedule an immediate one-off sync - Workmanager().registerOneOffTask( - "manual_sync_${DateTime.now().millisecondsSinceEpoch}", - DataSyncService.syncTaskName, - constraints: Constraints(networkType: NetworkType.connected), - ); - } -} -``` - -## Platform-Specific Considerations - -### Android -**Android automatically handles**: -- Task scheduling and retry logic -- Battery optimization (Doze mode) -- App standby mode restrictions -- Network changes and availability - -**Best practices**: -```dart -// Use appropriate constraints -Workmanager().registerPeriodicTask( - "data_sync", - "sync_task", - frequency: Duration(hours: 1), - constraints: Constraints( - networkType: NetworkType.connected, - requiresBatteryNotLow: true, // Respect battery optimization - requiresCharging: false, // Don't wait for charging - ), -); -``` - -**Minimum frequency**: 15 minutes (Android WorkManager limitation) - -### iOS -**iOS has additional restrictions**: -- Background App Refresh must be enabled -- Tasks may be limited to 30 seconds -- iOS learns usage patterns and schedules accordingly -- No guaranteed execution time - -**Setup required in AppDelegate.swift**: -```swift -import workmanager - -// In application(_:didFinishLaunchingWithOptions:) -WorkmanagerPlugin.registerPeriodicTask( - withIdentifier: "data_sync_task", - frequency: NSNumber(value: 60 * 60) // 1 hour in seconds -) -``` - -**Info.plist configuration**: -```xml -BGTaskSchedulerPermittedIdentifiers - - data_sync_task - -``` - -**Best practices**: -- Keep sync operations under 30 seconds -- Use incremental sync (only fetch changes) -- Handle task expiration gracefully - -## Testing Your Implementation - -### Debug Mode -```dart -Workmanager().initialize( - callbackDispatcher, - isInDebugMode: true, // Shows notifications when tasks run -); -``` - -### Manual Testing -```dart -// Test your sync logic immediately -ElevatedButton( - onPressed: () { - Workmanager().registerOneOffTask( - "test_sync", - DataSyncService.syncTaskName, - initialDelay: Duration(seconds: 5), - ); - }, - child: Text('Test Sync'), -) -``` - -### Monitoring -```dart -Future _checkSyncHealth() async { - final prefs = await SharedPreferences.getInstance(); - final lastSync = prefs.getString('last_sync_time'); - - if (lastSync != null) { - final syncTime = DateTime.parse(lastSync); - final hoursSinceSync = DateTime.now().difference(syncTime).inHours; - - if (hoursSinceSync > 6) { - // Alert: Sync might not be working - _showSyncWarning(); - } - } -} -``` - -## Common Issues & Solutions - -### Sync Not Running -**Possible causes**: -- Missing platform setup (Android/iOS configuration) -- App battery optimization preventing background tasks -- No network connectivity when task tries to run -- App hasn't been used recently (iOS restriction) - -**Solutions**: -- Verify platform setup is complete -- Ask users to disable battery optimization for your app -- Use network constraints appropriately -- Encourage regular app usage - -### Partial Data Sync -**Implement incremental sync**: -```dart -Future _performIncrementalSync() async { - final prefs = await SharedPreferences.getInstance(); - final lastSyncTime = prefs.getString('last_sync_time') ?? ''; - - final response = await http.get( - Uri.parse('https://api.yourapp.com/data?since=$lastSyncTime'), - ); - - // Only sync changed data - if (response.statusCode == 200) { - final changes = json.decode(response.body); - await _applyChanges(changes); - return true; - } - return false; -} -``` - -### High Battery Usage -**Optimize your sync logic**: -- Use appropriate frequency (not too often) -- Add battery constraints -- Implement smart syncing (only when data actually changed) -- Use compression for large payloads -- Cache API responses appropriately - diff --git a/docs/usecases/database-maintenance.mdx b/docs/usecases/database-maintenance.mdx deleted file mode 100644 index 44ba1352..00000000 --- a/docs/usecases/database-maintenance.mdx +++ /dev/null @@ -1,70 +0,0 @@ ---- -title: Database Tasks Example -description: Example implementation for database maintenance tasks ---- - -Automatically maintain your app's local database by cleaning up old records, optimizing indexes, and performing routine maintenance tasks in the background. - -## When to Use This - -- Remove old/expired database records -- Optimize database indexes and vacuum SQLite -- Archive old data to reduce database size -- Clean up orphaned records and relationships -- Compress and defragment database files - -## Quick Implementation - -```dart -@pragma('vm:entry-point') -void callbackDispatcher() { - Workmanager().executeTask((task, inputData) async { - if (task == "db_maintenance") { - return await performDatabaseMaintenance(); - } - return Future.value(false); - }); -} - -Future performDatabaseMaintenance() async { - try { - final db = await DatabaseHelper.getDatabase(); - - // Remove old records (30+ days) - final cutoff = DateTime.now() - .subtract(Duration(days: 30)) - .millisecondsSinceEpoch; - - await db.delete( - 'logs', - where: 'created_at < ?', - whereArgs: [cutoff], - ); - - // Optimize database (SQLite) - await db.execute('PRAGMA optimize'); - - return true; - } catch (e) { - print('DB maintenance failed: $e'); - return false; - } -} - -void scheduleDatabaseMaintenance() { - Workmanager().registerPeriodicTask( - "db_maintenance", - "db_maintenance", - frequency: Duration(days: 7), // Weekly maintenance - constraints: Constraints( - requiresDeviceIdle: true, - requiresBatteryNotLow: true, - ), - ); -} -``` - -## Platform Considerations - -**Android**: Can perform comprehensive maintenance operations -**iOS**: Limited to 30 seconds - focus on quick cleanup operations \ No newline at end of file diff --git a/docs/usecases/fetch-notifications.mdx b/docs/usecases/fetch-notifications.mdx deleted file mode 100644 index 62a2bc37..00000000 --- a/docs/usecases/fetch-notifications.mdx +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: Notifications Example -description: Example implementation for background notification checking ---- - -Automatically check for new notifications, messages, and updates from your server, even when the app is closed. - -## When to Use This - -- Social media apps checking for new messages -- Email apps fetching new mail -- News apps checking for breaking news -- Chat applications checking for messages -- Any app that needs real-time updates - -## Quick Implementation - -```dart -@pragma('vm:entry-point') -void callbackDispatcher() { - Workmanager().executeTask((task, inputData) async { - if (task == "fetch_notifications") { - return await checkForNotifications(); - } - return Future.value(false); - }); -} - -Future checkForNotifications() async { - try { - // Check your API for new notifications - print('Checking for new notifications...'); - - // Simulate API call - await Future.delayed(Duration(seconds: 2)); - - // Process any new notifications found - // Show local notification if needed - - return true; // Success - } catch (e) { - print('Notification check failed: $e'); - return false; - } -} - -void scheduleNotificationCheck() { - Workmanager().registerPeriodicTask( - "notification_check", - "fetch_notifications", - frequency: Duration(minutes: 30), - constraints: Constraints( - networkType: NetworkType.connected, - ), - ); -} -``` - -## Platform Considerations - -### Android -- Can check frequently (minimum 15 minutes) -- Works reliably in background -- Can show local notifications immediately -- Respects battery optimization settings - -### iOS -- Limited execution time (~30 seconds) -- May not run if app unused recently -- iOS learns user patterns for scheduling -- Background App Refresh must be enabled \ No newline at end of file diff --git a/docs/usecases/periodic-cleanup.mdx b/docs/usecases/periodic-cleanup.mdx deleted file mode 100644 index 8c4e1762..00000000 --- a/docs/usecases/periodic-cleanup.mdx +++ /dev/null @@ -1,72 +0,0 @@ ---- -title: Cleanup Tasks Example -description: Example implementation for periodic cleanup tasks ---- - -Keep your app performing well by automatically cleaning up old files, cached data, and temporary content in the background. - -## When to Use This - -- Clear old cached images and files -- Remove expired data from local databases -- Clean up temporary downloads -- Maintain optimal app storage usage -- Remove old log files - -## Quick Implementation - -```dart -@pragma('vm:entry-point') -void callbackDispatcher() { - Workmanager().executeTask((task, inputData) async { - if (task == "cleanup_task") { - return await performCleanup(); - } - return Future.value(false); - }); -} - -Future performCleanup() async { - try { - // Clean old cache files - await _cleanupCache(); - - // Remove expired database entries - await _cleanupDatabase(); - - // Clear temporary files - await _cleanupTempFiles(); - - return true; - } catch (e) { - print('Cleanup failed: $e'); - return false; - } -} - -void scheduleCleanup() { - Workmanager().registerPeriodicTask( - "cleanup_task", - "cleanup_task", - frequency: Duration(days: 1), // Daily cleanup - constraints: Constraints( - requiresBatteryNotLow: true, - requiresDeviceIdle: true, // Run when device not busy - ), - ); -} -``` - -## Platform Behavior - -### Android -- Runs reliably in the background -- Respects device idle constraints -- Can clean up large amounts of data -- Minimum frequency: 15 minutes - -### iOS -- Limited to 30 seconds execution time -- Use incremental cleanup approach -- May be deferred if device is busy -- Focus on quick cleanup operations \ No newline at end of file diff --git a/docs/usecases/upload-files.mdx b/docs/usecases/upload-files.mdx deleted file mode 100644 index 635f64fe..00000000 --- a/docs/usecases/upload-files.mdx +++ /dev/null @@ -1,267 +0,0 @@ ---- -title: File Upload Example -description: Example implementation for background file uploads ---- - -Upload user files in the background, even when the app is closed, ensuring reliable delivery when network conditions are good. - -## When to Use This - -- Photo backup apps -- Document sharing apps -- Social media with image/video uploads -- Cloud storage synchronization -- Large file transfers that shouldn't block the UI - -## Complete Implementation - -### 1. Queue Files for Upload - -```dart lib/services/upload_service.dart -import 'package:workmanager/workmanager.dart'; -import 'package:http/http.dart' as http; -import 'dart:io'; - -class UploadService { - static const String uploadTaskName = "file_upload_task"; - - static void initialize() { - Workmanager().initialize(callbackDispatcher); - } - - static Future queueFileUpload(String filePath, Map metadata) async { - final taskId = "upload_${DateTime.now().millisecondsSinceEpoch}"; - - Workmanager().registerOneOffTask( - taskId, - uploadTaskName, - inputData: { - 'filePath': filePath, - 'metadata': metadata, - 'taskId': taskId, - }, - constraints: Constraints( - networkType: NetworkType.connected, - requiresBatteryNotLow: true, - ), - ); - } -} - -@pragma('vm:entry-point') -void callbackDispatcher() { - Workmanager().executeTask((task, inputData) async { - if (task == UploadService.uploadTaskName) { - return await _uploadFile(inputData!); - } - return Future.value(false); - }); -} - -Future _uploadFile(Map inputData) async { - try { - final filePath = inputData['filePath'] as String; - final metadata = inputData['metadata'] as Map; - final taskId = inputData['taskId'] as String; - - final file = File(filePath); - if (!await file.exists()) { - print('File not found: $filePath'); - return true; // Don't retry if file doesn't exist - } - - // Create multipart request - final uri = Uri.parse('https://api.yourapp.com/upload'); - final request = http.MultipartRequest('POST', uri); - - request.headers['Authorization'] = 'Bearer TOKEN'; - request.fields.addAll( - metadata.map((k, v) => MapEntry(k, v.toString())) - ); - - final file = await http.MultipartFile.fromPath( - 'file', filePath - ); - request.files.add(file); - - final response = await request.send().timeout(Duration(minutes: 5)); - - if (response.statusCode == 200) { - print('Upload successful: $taskId'); - await _markUploadComplete(filePath, taskId); - return true; - } else { - print('Upload failed with status: ${response.statusCode}'); - return false; // Retry later - } - } catch (e) { - print('Upload error: $e'); - return false; // Retry later - } -} - -Future _markUploadComplete(String filePath, String taskId) async { - final prefs = await SharedPreferences.getInstance(); - final completed = prefs.getStringList('completed_uploads') ?? []; - completed.add(taskId); - await prefs.setStringList('completed_uploads', completed); -} -``` - -### 2. UI Integration - -```dart lib/widgets/file_upload_widget.dart -class FileUploadWidget extends StatefulWidget { - @override - _FileUploadWidgetState createState() => _FileUploadWidgetState(); -} - -class _FileUploadWidgetState extends State { - List _pendingUploads = []; - - @override - void initState() { - super.initState(); - _loadPendingUploads(); - } - - Future _selectAndQueueFile() async { - // Use your preferred file picker implementation - final filePath = '/path/to/selected/file.jpg'; - - // Queue for background upload - await UploadService.queueFileUpload(filePath, { - 'filename': 'photo.jpg', - 'size': 1024000, - 'type': 'jpg', - 'userId': 'current_user_id', - }); - - setState(() { - _pendingUploads.add('photo.jpg'); - }); - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('File queued for upload')), - ); - } - - @override - Widget build(BuildContext context) { - return Column( - children: [ - ElevatedButton.icon( - onPressed: _selectAndQueueFile, - icon: Icon(Icons.cloud_upload), - label: Text('Select File to Upload'), - ), - if (_pendingUploads.isNotEmpty) ...[ - SizedBox(height: 16), - Text('Pending Uploads:', style: Theme.of(context).textTheme.subtitle1), - ...(_pendingUploads.map((filename) => ListTile( - leading: Icon(Icons.schedule), - title: Text(filename), - subtitle: Text('Waiting for background upload...'), - ))), - ], - ], - ); - } -} -``` - -## Platform-Specific Behavior - -### Android -**Automatic handling:** -- Large file uploads continue even if app is killed -- Respects data saver mode and metered networks -- Retries with exponential backoff on failure - -**Optimize for Android:** -```dart -Workmanager().registerOneOffTask( - taskId, - uploadTaskName, - inputData: inputData, - constraints: Constraints( - networkType: NetworkType.unmetered, // Use WiFi when possible - requiresCharging: true, // For large files - requiresDeviceIdle: false, // Upload even when device in use - ), - backoffPolicy: BackoffPolicy.exponential, - backoffPolicyDelay: Duration(minutes: 1), -); -``` - -### iOS -**iOS limitations:** -- 30-second execution limit for most tasks -- Use BGProcessingTask for longer uploads -- May be deferred until device is idle and charging - -**For large files, use processing tasks:** -```dart -// Register in AppDelegate.swift -WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "large_file_upload") - -// Schedule in Dart -Workmanager().registerProcessingTask( - taskId, - uploadTaskName, - constraints: Constraints( - networkType: NetworkType.connected, - requiresCharging: true, // iOS prefers charging for processing tasks - ), -); -``` - -## Advanced Features - -### Progress Tracking -```dart -Future _uploadFileWithProgress(Map inputData) async { - final file = File(inputData['filePath']); - final fileSize = await file.length(); - int uploadedBytes = 0; - - final request = http.StreamedRequest('POST', Uri.parse('https://api.yourapp.com/upload')); - - final stream = file.openRead(); - stream.listen( - (chunk) { - uploadedBytes += chunk.length; - final progress = (uploadedBytes / fileSize * 100).round(); - // Save progress to SharedPreferences for UI updates - _saveUploadProgress(inputData['taskId'], progress); - }, - ); - - request.contentLength = fileSize; - stream.pipe(request); - - final response = await request.send(); - return response.statusCode == 200; -} -``` - -### Retry Logic -```dart -Future _uploadWithRetry(Map inputData) async { - const maxRetries = 3; - int attempts = inputData['attempts'] ?? 0; - - final success = await _uploadFile(inputData); - - if (!success && attempts < maxRetries) { - // Schedule retry with delay - await Future.delayed(Duration(seconds: math.pow(2, attempts).toInt())); - - inputData['attempts'] = attempts + 1; - return await _uploadWithRetry(inputData); - } - - return success; -} -``` - From bed680e6cebd466c9438089ba2b14606b970e5c7 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 22:52:50 +0100 Subject: [PATCH 20/25] feat: add API documentation link to overview page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add link to pub.dev API documentation in Get Started section - Provides users easy access to complete Dart API reference - Positioned after Quick Start Guide for natural discovery flow πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- docs/index.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/index.mdx b/docs/index.mdx index 70ddd9cb..8545bd68 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -48,6 +48,8 @@ Ready to add background tasks to your Flutter app? **[β†’ Quick Start Guide](quickstart)** - Get up and running in minutes +**[β†’ API Documentation](https://pub.dev/documentation/workmanager/latest/)** - Complete Dart API reference + ## Example Project See a complete working demo: **[Example App β†’](https://github.com/fluttercommunity/flutter_workmanager/tree/main/example)** From 76264cea2c58a07a06462d442bb51f597a6d402e Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 22:58:59 +0100 Subject: [PATCH 21/25] feat: add missing functionality and streamline project README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Missing Features to Quickstart: - Task constraints (network, battery, storage, charging requirements) - Input data passing and access in background tasks - Task tagging and management (cancel by tag, unique name, all) - iOS requirements (Swift 4.2+, Xcode 10.3+, iOS 10+) Setup File Cleanup: - Remove android_setup.md and ios_setup.md (content now in docs.page) - Essential setup details integrated into quickstart guide README Improvements: - Remove redundant quickstart code (point to docs.page instead) - Fix broken use case links (use cases were removed) - Streamline documentation links to Quick Start, API docs, Debugging - Focus on high-level use case descriptions rather than detailed guides Ensures information parity with pub.dev while maintaining clean structure. πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- ANDROID_SETUP.md | 24 ---- IOS_SETUP.md | 284 -------------------------------------------- README.md | 67 +++-------- docs/quickstart.mdx | 64 ++++++++++ 4 files changed, 82 insertions(+), 357 deletions(-) delete mode 100644 ANDROID_SETUP.md delete mode 100644 IOS_SETUP.md diff --git a/ANDROID_SETUP.md b/ANDROID_SETUP.md deleted file mode 100644 index 6c519a66..00000000 --- a/ANDROID_SETUP.md +++ /dev/null @@ -1,24 +0,0 @@ -# Check your AndroidManifest.xml - -Check if you have the following in your `AndroidManifest.xml` file. - -```xml - -``` - -Ideally you should have this, if not follow the [upgrade guide](https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects). -If for some reason you can't upgrade yet we still support the [older way of embedding](ANDROID_SETUP_V1.md): - -# How to Debug my background job - -Debugging a background task can be difficult, Android decides when is the best time to run. -There is no guaranteed way to enforce a run of a job even in debug mode. - -However to facilitate debugging, the plugin provides an `isInDebugMode` flag when initializing the plugin: `Workmanager().initialize(callbackDispatcher, isInDebugMode: true)` - -Once this flag is enabled you will receive a notification whenever a background task was triggered. -This way you can keep track whether that task ran successfully or not. - - diff --git a/IOS_SETUP.md b/IOS_SETUP.md deleted file mode 100644 index 68a5c448..00000000 --- a/IOS_SETUP.md +++ /dev/null @@ -1,284 +0,0 @@ -# iOS Installation - -## Prerequisites - -This plugin is compatible with **Swift 4.2** and up. Make sure you are using **Xcode 10.3** or higher and have set your minimum deployment target to **iOS 10** or higher by defining a platform version in your podfile: `platform :ios, '10.0'` - - -## Enable BGTaskScheduler - -> ⚠️ 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) - -This will add the **UIBackgroundModes** key to your project's `Info.plist`: - -``` xml -UIBackgroundModes - - processing - - - fetch - -``` - -You **MUST** amend your `AppDelegate.swift` and `Info.plist` file to register your task ID. - -- AppDelegate.swift -``` swift -import workmanager -``` - -``` swift -// In AppDelegate.application method -WorkmanagerPlugin.registerBGProcessingTask(withIdentifier: "task-identifier") - -// Register a periodic task in iOS 13+ -WorkmanagerPlugin.registerPeriodicTask(withIdentifier: "dev.fluttercommunity.workmanagerExample.iOSBackgroundAppRefresh", frequency: NSNumber(value: 20 * 60)) -``` - -- Info.plist -``` xml -BGTaskSchedulerPermittedIdentifiers - - task-identifier - - - dev.fluttercommunity.workmanagerExample.iOSBackgroundAppRefresh - -``` -> ⚠️ 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: - -``` -SystemCapabilities = { - com.apple.BackgroundModes = { - enabled = 1; - }; -}; -``` - -## Testing BGTaskScheduler - -Follow the instructions on https://developer.apple.com/documentation/backgroundtasks/starting_and_terminating_tasks_during_development. - -The exact command to trigger the WorkManager default BG Task is: - -``` -e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"workmanager.background.task"] -``` - -## Enabling Background Fetch - -> ⚠️ 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*: - -![Screenshot of Background Fetch Capabilities tab in Xcode ](.art/ios_background_mode_fetch.png) - -This will add the **UIBackgroundModes** key to your project's `Info.plist`: - -```xml -UIBackgroundModes - - fetch - -``` - -And will set the correct *SystemCapabilities* for your target in the `project.pbxproj` file: - -``` -SystemCapabilities = { - com.apple.BackgroundModes = { - enabled = 1; - }; -}; -``` - -Inside your app's delegate `didFinishLaunchingWithOptions`, set your desired **minimumBackgroundFetchInterval** : - - -```Swift -class AppDelegate:UIResponder,UIApplicationDelegate{ - func application(_ application:UIApplication,didFinishLaunchingWithOptions launchOptions:[UIApplicationLaunchOptionsKey:Any]?)->Bool{ - // Other intialization code… - UIApplication.shared.setMinimumBackgroundFetchInterval(TimeInterval(60*15)) - - return true - } -} -``` - -This ensures that the task is ran at most every 15 minutes. - -> πŸ“ Note: this is a **minimum** time interval, there's no guarantee on how often this will be called. - - - -## Testing Background Fetch - -You can wait for iOS to trigger the `performFetchWithCompletionHandler` but you as a developer have no control over *when* and how often iOS will allow your app to fetch data in the background: - -> When a good opportunity arises, the system wakes or launches your app into the background and calls the app delegate’s `application:performFetchWithCompletionHandler:` method. [...] **Apps that download small amounts of content quickly, and accurately reflect when they had content available to download, are more likely to receive execution time in the future** than apps that take a long time to download their content or that claim content was available but then do not download anything. - - - -> πŸ“ Note: also see relevant discussion in issue #23 - -But in order to test your implementation during development, you can *simulate* a `background fetch` in Xcode; go to `Debug` β†’ `Simulate Background Fetch` - -![Screenshot of iOS Xcode's Debug menu](.art/ios_xcode_debug_menu.png) - -When the WorkManager plugin receives a `background fetch` event, it will start a new **Dart isolate**, using the entrypoint provided by the `initialize` method. - -Here is an example of a Flutter entrypoint called `callbackDispatcher`: - -```dart -@pragma('vm:entry-point') -void callbackDispatcher() { - Workmanager().executeTask((task, inputData) { - switch (task) { - case Workmanager.iOSBackgroundTask: - stderr.writeln("The iOS background fetch was triggered"); - break; - } - bool success = true; - return Future.value(success); - }); -} -``` - -If you then simulate a background fetch in Xcode, you should see `"The iOS background fetch was triggered"` log in Xcode's Console: - -![Screenshot of Xcode console's output](.art/ios_xcode_console.png) - -### Troubleshooting - -If the *Simulate Background Fetch* is greyed out in the *Debug* menu, that means Xcode's debugger is not attached to the current process. Attaching is done for you *automatically* when you run directly from Xcode. - -If you launched your app using the Flutter command line tools or another IDE like IntelliJ IDEA, you will need to attach to the running *Runner* process using the Debug menu in Xcode: - -![Screenshot of Xcode's attach to process menu item](.art/ios_xcode_attach_to_process.jpg) - -> πŸ“ Note that this feature of Xcode is not 100% reliable. For best results, run directly from Xcode - - - -## Debug mode - -To make background work more visible when developing, the WorkManager plugin provides an `isInDebugMode` flag when initializing the plugin: - -```dart -Workmanager().initialize(callbackDispatcher, isInDebugMode: true) -``` - -If `isInDebugMode` is `true`, a local notification will be displayed whenever a background fetch was triggered by iOS. In the example gif below, two background fetches were *simulated* in quick succession. Both completing succesfully after a few seconds in this case: - -![example of iOS debug notification](.art/ios_debug_notifications.gif) - - - -These are the three notification types, **start** work, finished **successfully**, finished with **failure**: - -![example of iOS debug notification](.art/ios_debug_notification_types.png) - -*Success* or *failure* depending on what you return in Dart: - -```dart -bool success = true; -return Future.value(success); -``` - - - -If your app is running in the **foreground**, notification banners are **not shown**. That is default behaviour on iOS. Triggering *Simulate Background Fetch* when running on a **real device** will put the app in the background *first*, before calling the `performFetchWithCompletionHandler`. This doesn't happen in the Simulator. So, make sure to go to the Home screen *before* triggering background fetch. - -> πŸ“ Note: the Home Indicator swipe-up gesture is sometimes tricky to do on a simulator. Use Simulator menu `Hardware` β†’ `Home` or keyboard shortcut ⌘ command + ⇧ shift + H to make your life easier - -### Show notifications in foreground - -Alternatively, if you *do* want the banners to appear while your app is in the foreground you can implement `userNotificationCenter(_:willPresent:withCompletionHandler:)` of `UNUserNotificationCenterDelegate`. - -From the [Apple docs](https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate/1649518-usernotificationcenter): - -> If your **app is in the foreground** when a notification arrives, the shared user **notification center calls this method to deliver the notification directly to your app**. If you implement this method, you can take whatever actions are necessary to process the notification and update your app. When you finish, **call the completionHandler block and specify how you want the system to alert the user**, if at all. - -An easy way to get it working is by implementing it your `AppDelegate` like so: -```swift -class AppDelegate: FlutterAppDelegate { - // ... - override func userNotificationCenter(_ center: UNUserNotificationCenter, - willPresent notification: UNNotification, - withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { - completionHandler(.alert) // shows banner even if app is in foreground - } - -} -``` - -And assigning yourself as the delegate. Preferably as early as possible, in `application:didFinishLaunchingWithOptions:` - -```swift -UNUserNotificationCenter.current().delegate = self -``` - -Now the banners **are** shown when your app is running in the foreground πŸ‘‡ - -![Screenshot of Simulator with app in foreground showing notification banner](.art/ios_notification_foreground.png) - -> πŸ“ Note: decide if implementing the delegate call makes sense for your app. You could make use of [active compilation conditions](https://blog.krzyzanowskim.com/2016/10/10/conditional-swift-testing/) to implement it *only* for the `Debug` configruation, for example - - - -## Registered plugins -Since the provided Flutter entry point is ran in a dedicated **Dart isolate**, the Flutter plugins which may -have been registered AppDelegate's `didFinishLaunchingWithOptions` (or somewhere else) are unavailable, -since they were registered on a different registry. - -In order to know when the Dart isolate has started, the plugin user may make use of the -WorkmanagerPlugin's `setPluginRegistrantCallback` function. For example : - -```Swift -class AppDelegate: FlutterAppDelegate { - /// Registers all pubspec-referenced Flutter plugins in the given registry. - static func registerPlugins(with registry: FlutterPluginRegistry) { - GeneratedPluginRegistrant.register(with: registry) - } - - override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - - // ... Initialization code - - AppDelegate.registerPlugins(with: self) // Register the app's plugins in the context of a normal run - - WorkmanagerPlugin.setPluginRegistrantCallback { registry in - // The following code will be called upon WorkmanagerPlugin's registration. - // Note : all of the app's plugins may not be required in this context ; - // instead of using GeneratedPluginRegistrant.register(with: registry), - // you may want to register only specific plugins. - AppDelegate.registerPlugins(with: registry) - } - } -} -``` - - - -## Useful links - -- [Updating Your App with Background App Refresh](https://developer.apple.com/documentation/uikit/app_and_environment/scenes/preparing_your_ui_to_run_in_the_background/updating_your_app_with_background_app_refresh) - Apple Documentation - -- [UNUserNotificationCenterDelegate](https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate/1649518-usernotificationcenter) documentation diff --git a/README.md b/README.md index 0c6238c2..442235f8 100644 --- a/README.md +++ b/README.md @@ -9,55 +9,24 @@ Execute Dart code in the background, even when your app is closed. Perfect for data sync, file uploads, and periodic maintenance tasks. -## πŸ“– Full Documentation - -**[Visit our comprehensive documentation β†’](https://docs.page/fluttercommunity/flutter_workmanager)** - -## ⚑ Quick Start - -### 1. Install -```yaml -dependencies: - workmanager: ^0.8.0 -``` - -### 2. Platform Setup -- **Android**: Works automatically βœ… -- **iOS**: [5-minute setup required](https://docs.workmanager.dev/setup/ios) - -### 3. Initialize & Use -```dart -@pragma('vm:entry-point') -void callbackDispatcher() { - Workmanager().executeTask((task, inputData) { - print("Background task: $task"); - // Your background logic here - return Future.value(true); - }); -} - -void main() { - Workmanager().initialize(callbackDispatcher); - - // Schedule a task - Workmanager().registerPeriodicTask( - "sync-task", - "data-sync", - frequency: Duration(hours: 1), - ); - - runApp(MyApp()); -} -``` - -## 🎯 Common Use Cases - -| Use Case | Documentation | -|----------|---------------| -| **Sync data from API** | [Data Sync Guide β†’](https://docs.page/fluttercommunity/flutter_workmanager/usecases/data-sync) | -| **Upload files in background** | [File Upload Guide β†’](https://docs.page/fluttercommunity/flutter_workmanager/usecases/upload-files) | -| **Clean up old data** | [Cleanup Guide β†’](https://docs.page/fluttercommunity/flutter_workmanager/usecases/periodic-cleanup) | -| **Fetch notifications** | [Notifications Guide β†’](https://docs.page/fluttercommunity/flutter_workmanager/usecases/fetch-notifications) | +## πŸ“– Documentation + +Get started with background tasks in Flutter: + +**[β†’ Quick Start Guide](https://docs.page/fluttercommunity/flutter_workmanager/quickstart)** - Installation and setup + +**[β†’ API Documentation](https://pub.dev/documentation/workmanager/latest/)** - Complete Dart API reference + +**[β†’ Debugging Guide](https://docs.page/fluttercommunity/flutter_workmanager/debugging)** - Troubleshooting help + +## 🎯 Use Cases + +Background tasks are perfect for: +- **Sync data from API** - Keep your app's data fresh +- **Upload files in background** - Reliable file uploads +- **Clean up old data** - Remove old files and cache +- **Fetch notifications** - Check for new messages +- **Database maintenance** - Optimize and clean databases ## πŸ—οΈ Architecture diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index f60dbdad..46d84234 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -27,6 +27,10 @@ Android works automatically - no additional setup required! βœ… ### iOS iOS requires a 5-minute setup in Xcode. Choose your approach based on your needs: + +**Requirements:** Swift 4.2+, Xcode 10.3+, iOS 10+ minimum deployment target. Set in your Podfile: `platform :ios, '10.0'` + + #### Option A: Periodic Tasks (Recommended for most use cases) For regular data sync, notifications, cleanup - uses iOS Background Fetch: @@ -209,6 +213,66 @@ Your background tasks can return: - `Future.value(false)` - πŸ”„ Task should be retried - `Future.error(...)` - ❌ Task failed +## Task Customization + +### Input Data and Constraints + +```dart +// Schedule task with input data and constraints +Workmanager().registerOneOffTask( + "upload-task", + "file_upload", + initialDelay: Duration(minutes: 5), + inputData: { + 'fileName': 'document.pdf', + 'uploadUrl': 'https://api.example.com/upload', + 'retryCount': 3 + }, + constraints: Constraints( + networkType: NetworkType.connected, + requiresBatteryNotLow: true, + requiresCharging: false, + requiresDeviceIdle: false, + requiresStorageNotLow: true, + ), +); + +// Access input data in your task +@pragma('vm:entry-point') +void callbackDispatcher() { + Workmanager().executeTask((task, inputData) async { + print('Task: $task'); + print('Input: $inputData'); // Access your input data + + String? fileName = inputData?['fileName']; + int retryCount = inputData?['retryCount'] ?? 0; + + // Your task logic using the input data + return Future.value(true); + }); +} +``` + +### Task Tagging and Management + +```dart +// Tag tasks for easier management +Workmanager().registerOneOffTask( + "sync-task-1", + "data_sync", + tag: "data-sync-tasks", +); + +// Cancel tasks by tag +Workmanager().cancelByTag("data-sync-tasks"); + +// Cancel specific task +Workmanager().cancelByUniqueName("sync-task-1"); + +// Cancel all tasks +Workmanager().cancelAll(); +``` + ## Key Points - **Callback Dispatcher**: Must be a top-level function (not inside a class) From 1e2e94b1b0f191768820c64a5115a023b1d07ef2 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 23:00:02 +0100 Subject: [PATCH 22/25] feat: create dedicated Task Customization page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move advanced features from quickstart to separate customization page - Keep quickstart focused on basic setup and usage - Add comprehensive customization guide covering: - Input data passing and access - Task constraints (network, battery, storage) - Task tagging and management - Cancellation methods - Error handling and retries - Best practices and resource management - Add to navigation under Reference section - Update quickstart Next Steps to reference customization page πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- docs.json | 4 + docs/customization.mdx | 282 +++++++++++++++++++++++++++++++++++++++++ docs/quickstart.mdx | 62 +-------- 3 files changed, 288 insertions(+), 60 deletions(-) create mode 100644 docs/customization.mdx diff --git a/docs.json b/docs.json index 6d336062..513b3184 100644 --- a/docs.json +++ b/docs.json @@ -25,6 +25,10 @@ "group": "Reference", "tab": "root", "pages": [ + { + "title": "Task Customization", + "href": "/customization" + }, { "title": "Debugging", "href": "/debugging" diff --git a/docs/customization.mdx b/docs/customization.mdx new file mode 100644 index 00000000..69db3cbc --- /dev/null +++ b/docs/customization.mdx @@ -0,0 +1,282 @@ +--- +title: Task Customization +description: Advanced task configuration with constraints, input data, and management +--- + +Configure background tasks with constraints, input data, and advanced management options. + +## Input Data + +Pass data to your background tasks and access it in the callback: + +```dart +// Schedule task with input data +Workmanager().registerOneOffTask( + "upload-task", + "file_upload", + inputData: { + 'fileName': 'document.pdf', + 'uploadUrl': 'https://api.example.com/upload', + 'retryCount': 3, + 'userId': 12345, + }, +); + +// Access input data in your task +@pragma('vm:entry-point') +void callbackDispatcher() { + Workmanager().executeTask((task, inputData) async { + print('Task: $task'); + print('Input: $inputData'); + + // Extract specific values + String? fileName = inputData?['fileName']; + String? uploadUrl = inputData?['uploadUrl']; + int retryCount = inputData?['retryCount'] ?? 0; + int userId = inputData?['userId'] ?? 0; + + // Use the data in your task logic + await uploadFile(fileName, uploadUrl, userId); + + return Future.value(true); + }); +} +``` + + +**Input Data Types:** You can pass basic JSON-serializable types: `String`, `int`, `double`, `bool`, `List`, `Map`. Complex objects need to be serialized first. + + +## Task Constraints + +Control when tasks should run based on device conditions: + +```dart +Workmanager().registerOneOffTask( + "sync-task", + "data_sync", + constraints: Constraints( + networkType: NetworkType.connected, // Require internet connection + requiresBatteryNotLow: true, // Don't run when battery is low + requiresCharging: false, // Can run when not charging + requiresDeviceIdle: false, // Can run when device is active + requiresStorageNotLow: true, // Don't run when storage is low + ), +); +``` + +### Network Constraints + +```dart +// Different network requirements +NetworkType.connected // Any internet connection +NetworkType.unmetered // WiFi or unlimited data only +NetworkType.not_required // Can run without internet +``` + +### Battery and Charging + +```dart +constraints: Constraints( + requiresBatteryNotLow: true, // Wait for adequate battery + requiresCharging: true, // Only run when plugged in +) +``` + + +**Platform Differences:** Some constraints are Android-only. iOS background tasks have different system-level constraints that cannot be configured directly. + + +## Task Management + +### Tagging Tasks + +Group related tasks with tags for easier management: + +```dart +// Tag multiple related tasks +Workmanager().registerOneOffTask( + "sync-photos", + "photo_sync", + tag: "sync-tasks", +); + +Workmanager().registerOneOffTask( + "sync-documents", + "document_sync", + tag: "sync-tasks", +); + +// Cancel all tasks with a specific tag +Workmanager().cancelByTag("sync-tasks"); +``` + +### Canceling Tasks + +```dart +// Cancel a specific task by unique name +Workmanager().cancelByUniqueName("sync-photos"); + +// Cancel tasks by tag +Workmanager().cancelByTag("sync-tasks"); + +// Cancel all scheduled tasks +Workmanager().cancelAll(); +``` + +### Task Scheduling Options + +```dart +// One-time task with delay +Workmanager().registerOneOffTask( + "delayed-task", + "cleanup", + initialDelay: Duration(minutes: 30), + inputData: {'cleanupType': 'cache'} +); + +// Periodic task with custom frequency +Workmanager().registerPeriodicTask( + "hourly-sync", + "data_sync", + frequency: Duration(hours: 1), // Android: minimum 15 minutes + initialDelay: Duration(minutes: 5), // Wait before first execution + inputData: {'syncType': 'incremental'} +); +``` + +## Advanced Configuration + +### Task Identification + +Use meaningful, unique task names to avoid conflicts: + +```dart +// Good: Specific and unique +Workmanager().registerOneOffTask( + "user-${userId}-photo-upload-${timestamp}", + "upload_task", + inputData: {'userId': userId, 'type': 'photo'} +); + +// Avoid: Generic names that might conflict +Workmanager().registerOneOffTask( + "task1", + "upload", + // ... +); +``` + +### Task Types and Names + +```dart +// Use descriptive task type names in your callback +@pragma('vm:entry-point') +void callbackDispatcher() { + Workmanager().executeTask((task, inputData) async { + switch (task) { + case 'photo_upload': + return await handlePhotoUpload(inputData); + case 'data_sync': + return await handleDataSync(inputData); + case 'cache_cleanup': + return await handleCacheCleanup(inputData); + case 'notification_check': + return await handleNotificationCheck(inputData); + default: + print('Unknown task: $task'); + return Future.value(false); + } + }); +} +``` + +## Error Handling and Retries + +```dart +@pragma('vm:entry-point') +void callbackDispatcher() { + Workmanager().executeTask((task, inputData) async { + int retryCount = inputData?['retryCount'] ?? 0; + + try { + // Your task logic + await performTask(inputData); + return Future.value(true); + + } catch (e) { + print('Task failed: $e'); + + // Decide whether to retry + if (retryCount < 3 && isRetryableError(e)) { + print('Retrying task (attempt ${retryCount + 1})'); + return Future.value(false); // Tell system to retry + } else { + print('Task failed permanently'); + return Future.value(true); // Don't retry + } + } + }); +} + +bool isRetryableError(dynamic error) { + // Network errors, temporary server issues, etc. + return error.toString().contains('network') || + error.toString().contains('timeout'); +} +``` + +## Best Practices + +### Efficient Task Design + +```dart +// Good: Quick, focused tasks +@pragma('vm:entry-point') +void callbackDispatcher() { + Workmanager().executeTask((task, inputData) async { + // Fast operation + await syncCriticalData(); + return Future.value(true); + }); +} + +// Avoid: Long-running operations +@pragma('vm:entry-point') +void callbackDispatcher() { + Workmanager().executeTask((task, inputData) async { + // This might timeout on iOS (30-second limit) + await processLargeDataset(); // ❌ Too slow + return Future.value(true); + }); +} +``` + +### Resource Management + +```dart +@pragma('vm:entry-point') +void callbackDispatcher() { + Workmanager().executeTask((task, inputData) async { + HttpClient? client; + + try { + // Initialize resources in the background isolate + client = HttpClient(); + + // Perform task + await performNetworkOperation(client); + + return Future.value(true); + + } finally { + // Clean up resources + client?.close(); + } + }); +} +``` + + +**Pro Tip:** Always initialize dependencies inside your background task callback since it runs in a separate isolate from your main app. + \ No newline at end of file diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index 46d84234..d3dd7fd8 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -213,65 +213,6 @@ Your background tasks can return: - `Future.value(false)` - πŸ”„ Task should be retried - `Future.error(...)` - ❌ Task failed -## Task Customization - -### Input Data and Constraints - -```dart -// Schedule task with input data and constraints -Workmanager().registerOneOffTask( - "upload-task", - "file_upload", - initialDelay: Duration(minutes: 5), - inputData: { - 'fileName': 'document.pdf', - 'uploadUrl': 'https://api.example.com/upload', - 'retryCount': 3 - }, - constraints: Constraints( - networkType: NetworkType.connected, - requiresBatteryNotLow: true, - requiresCharging: false, - requiresDeviceIdle: false, - requiresStorageNotLow: true, - ), -); - -// Access input data in your task -@pragma('vm:entry-point') -void callbackDispatcher() { - Workmanager().executeTask((task, inputData) async { - print('Task: $task'); - print('Input: $inputData'); // Access your input data - - String? fileName = inputData?['fileName']; - int retryCount = inputData?['retryCount'] ?? 0; - - // Your task logic using the input data - return Future.value(true); - }); -} -``` - -### Task Tagging and Management - -```dart -// Tag tasks for easier management -Workmanager().registerOneOffTask( - "sync-task-1", - "data_sync", - tag: "data-sync-tasks", -); - -// Cancel tasks by tag -Workmanager().cancelByTag("data-sync-tasks"); - -// Cancel specific task -Workmanager().cancelByUniqueName("sync-task-1"); - -// Cancel all tasks -Workmanager().cancelAll(); -``` ## Key Points @@ -287,5 +228,6 @@ For comprehensive debugging guidance including platform-specific tools, ADB comm ## Next Steps -- **[Debugging Guide](debugging)** - Learn how to debug and troubleshoot background tasks +- **[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 \ No newline at end of file From 0b8c56df6f3056c42b9aed61e9e9cfcc745e1187 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 23:02:27 +0100 Subject: [PATCH 23/25] docs: add comprehensive CONTRIBUTING.md for maintainers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Include dart pub publish --dry-run workflow information - Document melos-based development setup and commands - Cover code generation, formatting, and testing procedures - Provide publishing checklist and version management guide - Include platform-specific testing guidelines - Document CI workflows and common troubleshooting - Preserve maintainer knowledge previously in setup docs πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CONTRIBUTING.md | 208 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..686020a1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,208 @@ +# Contributing to Flutter Workmanager + +Thank you for your interest in contributing to Flutter Workmanager! This guide will help you get started. + +## Development Setup + +### Prerequisites +- Flutter SDK (latest stable version) +- Android Studio / Xcode for platform-specific development +- Melos for monorepo management + +### Getting Started + +1. Fork and clone the repository +2. Install melos globally: `dart pub global activate melos` +3. Bootstrap the workspace: `melos bootstrap` +4. Run tests: `melos run test` + +## Project Structure + +This is a federated plugin with the following packages: +- `workmanager/` - Main plugin package +- `workmanager_android/` - Android implementation +- `workmanager_apple/` - iOS/macOS implementation +- `workmanager_platform_interface/` - Shared interface +- `example/` - Demo application + +## Development Workflow + +### Making Changes + +1. Create a feature branch: `git checkout -b feature/your-feature` +2. Make your changes +3. Run formatting: `melos run format` +4. Run analysis: `melos run analyze` +5. Run tests: `melos run test` +6. Test on example app: `cd example && flutter run` + +### Code Generation + +If you modify the Pigeon API definition in `workmanager_platform_interface/pigeons/workmanager_api.dart`: + +```bash +# Regenerate Pigeon files +melos run generate:pigeon +``` + +**Important**: Never manually edit generated `*.g.*` files. + +### Code Formatting + +The project uses specific formatting rules: + +- **Dart**: Use `dart format` (configured to exclude generated files) +- **Kotlin**: Use `ktlint -F .` in root folder +- **Swift**: Use SwiftLint for formatting + +Generated files are automatically excluded from formatting checks. + +## Testing + +### Running Tests + +```bash +# All tests +melos run test + +# Specific package tests +cd workmanager_android && flutter test +cd workmanager_apple && flutter test + +# Native tests +cd example/android && ./gradlew :workmanager_android:test +cd example/ios && xcodebuild test -workspace Runner.xcworkspace -scheme Runner +``` + +### Integration Tests + +```bash +# iOS integration tests +melos run test:drive_ios + +# Android integration tests +melos run test:drive_android +``` + +### Example App Testing + +Always build the example app before completing your changes: + +```bash +cd example +flutter build apk --debug +flutter build ios --debug --no-codesign +``` + +## Platform-Specific Guidelines + +### Android +- Follow Android WorkManager best practices +- Test on various Android API levels +- Ensure background task constraints work properly + +### iOS +- Test both Background Fetch and BGTaskScheduler APIs +- Verify 30-second execution limits are respected +- Test on physical devices (background tasks don't work in simulator) + +## Publishing (Maintainers Only) + +### Pre-publish Checklist + +Before publishing any package, run the dry-run validation: + +```bash +# Validate all packages are ready for publishing +melos run publish:dry-run + +# Or for individual packages: +cd workmanager && dart pub publish --dry-run +cd workmanager_android && dart pub publish --dry-run +cd workmanager_apple && dart pub publish --dry-run +cd workmanager_platform_interface && dart pub publish --dry-run +``` + +This validates that: +- All dependencies are correctly specified +- No uncommitted changes exist +- Package follows pub.dev guidelines +- All required files are included + +### Version Management + +Use melos for coordinated version bumps: + +```bash +# Bump versions across related packages +melos version +``` + +### Publishing Process + +1. Ensure all tests pass: `melos run test` +2. Run dry-run validation: `melos run publish:dry-run` +3. Update CHANGELOGs for all modified packages +4. Create release PR with version bumps +5. After merge, tag release: `git tag v0.x.x` +6. Publish packages: `melos publish --no-dry-run` + +## Documentation + +### Updating Documentation + +- **API docs**: Documented inline in Dart code +- **User guides**: Located in `docs/` directory using docs.page +- **Setup guides**: Integrated into quickstart documentation + +### Documentation Structure + +- `docs/index.mdx` - Overview and features +- `docs/quickstart.mdx` - Installation and basic setup +- `docs/customization.mdx` - Advanced configuration +- `docs/debugging.mdx` - Troubleshooting guide + +### Testing Documentation + +Test documentation changes locally: +1. Push changes to a branch +2. View at: `https://docs.page/fluttercommunity/flutter_workmanager~your-branch` + +## GitHub Actions + +The project uses several CI workflows: + +- **Format** (`.github/workflows/format.yml`): Code formatting checks +- **Analysis** (`.github/workflows/analysis.yml`): Package analysis and dry-run validation +- **Test** (`.github/workflows/test.yml`): Unit tests, native tests, integration tests + +All checks must pass before merging PRs. + +## Common Issues + +### Generated Files + +If you see formatting or analysis errors in generated files: +- Never manually edit `*.g.*` files +- Use `melos run generate:pigeon` to regenerate +- Generated files are excluded from formatting by design + +### CI Failures + +**Package analysis failures**: Usually caused by uncommitted changes or missing dependencies +**Format failures**: Run `melos run format` locally first +**Test failures**: Ensure all tests pass locally with `melos run test` + +## Getting Help + +- **Bug reports**: [GitHub Issues](https://github.com/fluttercommunity/flutter_workmanager/issues) +- **Questions**: [GitHub Discussions](https://github.com/fluttercommunity/flutter_workmanager/discussions) +- **Documentation**: [docs.page](https://docs.page/fluttercommunity/flutter_workmanager) + +## Code of Conduct + +This project follows the [Flutter Community Code of Conduct](https://github.com/fluttercommunity/community/blob/main/CODE_OF_CONDUCT.md). + +## License + +By contributing to Flutter Workmanager, you agree that your contributions will be licensed under the [MIT License](LICENSE). \ No newline at end of file From 98a3ca8b6b6b5d55d775f0725cc0e21a9c8ce73f Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 23:10:17 +0100 Subject: [PATCH 24/25] cleanup: streamline quickstart and README content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quickstart improvements: - Remove outdated iOS requirements (Swift 4.2+, Xcode 10.3+, iOS 10+) - Combine repetitive setup steps and remove checkmark emojis - Merge Xcode capabilities and Info.plist steps into single numbered items - Remove federated plugin system mention (now in CONTRIBUTING.md) - Remove debugging section (handled in Next Steps) README improvements: - Remove federated architecture section (detailed in CONTRIBUTING.md) - Focus on essential user information only Results in cleaner, more focused user documentation. πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 9 --------- docs/quickstart.mdx | 32 ++++++-------------------------- 2 files changed, 6 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 442235f8..a09b5e27 100644 --- a/README.md +++ b/README.md @@ -28,15 +28,6 @@ Background tasks are perfect for: - **Fetch notifications** - Check for new messages - **Database maintenance** - Optimize and clean databases -## πŸ—οΈ Architecture - -This plugin uses a **federated architecture**: -- `workmanager` - Main package (this one) -- `workmanager_android` - Android implementation -- `workmanager_apple` - iOS/macOS implementation -- `workmanager_platform_interface` - Shared interface - -All packages are automatically included when you add `workmanager` to pubspec.yaml. ## πŸ› Issues & Support diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index d3dd7fd8..d57f5c1d 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -17,8 +17,6 @@ Then run: flutter pub get ``` -> The platform-specific packages are automatically included through the federated plugin system. - ## Platform Setup ### Android @@ -27,17 +25,10 @@ Android works automatically - no additional setup required! βœ… ### iOS iOS requires a 5-minute setup in Xcode. Choose your approach based on your needs: - -**Requirements:** Swift 4.2+, Xcode 10.3+, iOS 10+ minimum deployment target. Set in your Podfile: `platform :ios, '10.0'` - - #### Option A: Periodic Tasks (Recommended for most use cases) For regular data sync, notifications, cleanup - uses iOS Background Fetch: -1. **Enable Background Modes** in Xcode target capabilities ([Xcode Help](https://help.apple.com/xcode/mac/current/#/devbfa1532c4)): - - Background fetch βœ… - -2. **Add to Info.plist**: +1. **Enable Background Modes** in Xcode target capabilities ([Xcode Help](https://help.apple.com/xcode/mac/current/#/devbfa1532c4)) and add to Info.plist: ```xml UIBackgroundModes @@ -45,9 +36,7 @@ For regular data sync, notifications, cleanup - uses iOS Background Fetch: ``` -3. **No AppDelegate configuration needed!** βœ… - - The Background Fetch approach works automatically - just schedule tasks from your Dart code. +2. **No AppDelegate configuration needed** - works automatically from Dart code **iOS Background Fetch scheduling:** iOS completely controls when Background Fetch runs (typically once per day based on user app usage patterns). You cannot force immediate execution - it's designed for non-critical periodic updates like refreshing content. @@ -56,10 +45,7 @@ For regular data sync, notifications, cleanup - uses iOS Background Fetch: #### Option B: Processing Tasks (For complex operations) For file uploads, data processing, longer tasks - uses BGTaskScheduler: -1. **Enable Background Modes** in Xcode target capabilities ([Xcode Help](https://help.apple.com/xcode/mac/current/#/devbfa1532c4)): - - Background processing βœ… - -2. **Add to Info.plist**: +1. **Enable Background Modes** in Xcode target capabilities ([Xcode Help](https://help.apple.com/xcode/mac/current/#/devbfa1532c4)) and add to Info.plist: ```xml UIBackgroundModes @@ -72,7 +58,7 @@ For file uploads, data processing, longer tasks - uses BGTaskScheduler: ``` -3. **Configure AppDelegate.swift** (required for BGTaskScheduler): +2. **Configure AppDelegate.swift** (required for BGTaskScheduler): ```swift import workmanager_apple @@ -89,10 +75,7 @@ WorkmanagerPlugin.registerBGProcessingTask( #### Option C: Periodic Tasks with Custom Frequency For periodic tasks with more control than Background Fetch - uses BGTaskScheduler with frequency: -1. **Enable Background Modes** in Xcode target capabilities ([Xcode Help](https://help.apple.com/xcode/mac/current/#/devbfa1532c4)): - - Background processing βœ… - -2. **Add to Info.plist**: +1. **Enable Background Modes** in Xcode target capabilities ([Xcode Help](https://help.apple.com/xcode/mac/current/#/devbfa1532c4)) and add to Info.plist: ```xml UIBackgroundModes @@ -105,7 +88,7 @@ For periodic tasks with more control than Background Fetch - uses BGTaskSchedule ``` -3. **Configure AppDelegate.swift** (with frequency control): +2. **Configure AppDelegate.swift** (with frequency control): ```swift import workmanager_apple @@ -222,9 +205,6 @@ Your background tasks can return: - Android: Reliable background execution, 15-minute minimum frequency - iOS: 30-second limit, execution depends on user patterns and device state -## Debugging - -For comprehensive debugging guidance including platform-specific tools, ADB commands, and Xcode debugging, see the **[Debugging Guide](debugging)**. ## Next Steps From f10f7adaaa99ab6e0c2eebdd9500664002faf0ca Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 29 Jul 2025 23:24:54 +0100 Subject: [PATCH 25/25] feat: improve Basic Usage section and cleanup debugging documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Basic Usage improvements: - Add note that callbackDispatcher must be a top-level function (runs in separate isolate) - Remove print statement from callback example - Add switch/case fallback for iOS task identifiers (Workmanager.iOSBackgroundTask) - Use kDebugMode instead of hardcoded true for isInDebugMode - Add required flutter/foundation.dart import - Replace UI code with simple task scheduling examples (no more StatelessWidget/Scaffold) Debugging documentation cleanup: - Simplify troubleshooting checklist (remove repetitive items covered elsewhere) - Condense from 16 checklist items down to 8 focused items - Add quickstart links to Common iOS Issues for easy double-checking: - Task identifier mismatches β†’ Option B setup - Missing BGTaskSchedulerPermittedIdentifiers β†’ iOS configuration - Background refresh disabled β†’ Option A setup - Link to iOS setup guide in troubleshooting checklist Results in cleaner, more focused documentation with better cross-references. πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- docs/debugging.mdx | 32 +++++++-------------- docs/quickstart.mdx | 70 +++++++++++++++++---------------------------- 2 files changed, 37 insertions(+), 65 deletions(-) diff --git a/docs/debugging.mdx b/docs/debugging.mdx index 65b66803..0b84d772 100644 --- a/docs/debugging.mdx +++ b/docs/debugging.mdx @@ -135,13 +135,13 @@ This prints output like: **Tasks never run:** - App hasn't been used recently (iOS learning algorithm) - [iOS Power Management](https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/PrioritizeWorkAtTheTaskLevel.html) -- Task identifiers don't match between Info.plist and AppDelegate -- Missing BGTaskSchedulerPermittedIdentifiers in Info.plist +- Task identifiers don't match between Info.plist and AppDelegate - [check iOS setup](quickstart#option-b-processing-tasks-for-complex-operations) +- Missing BGTaskSchedulerPermittedIdentifiers in Info.plist - [review iOS configuration](quickstart#ios) **Tasks stop working:** - iOS battery optimization kicked in - [WWDC 2020: Background execution demystified](https://developer.apple.com/videos/play/wwdc2020/10063/) - App removed from recent apps too often -- User disabled background refresh +- User disabled background refresh - [check Background Fetch setup](quickstart#option-a-periodic-tasks-recommended-for-most-use-cases) - Task taking longer than 30 seconds - [BGTaskScheduler Documentation](https://developer.apple.com/documentation/backgroundtasks/bgtaskscheduler) **Tasks run but don't complete:** @@ -239,28 +239,16 @@ Future isTaskHealthy(String taskName, Duration maxAge) async { ## Troubleshooting Checklist -**Task not scheduling:** +**Quick Checks:** - [ ] Workmanager initialized in main() - [ ] Task names are unique -- [ ] Platform setup completed (iOS Info.plist, AppDelegate) +- [ ] Platform setup completed ([iOS setup guide](quickstart#ios)) +- [ ] Debug notifications enabled (`isInDebugMode: kDebugMode`) -**Task not executing:** -- [ ] Debug notifications enabled -- [ ] Battery optimization disabled (Android) -- [ ] Background App Refresh enabled (iOS) -- [ ] App used recently (iOS learning algorithm) -- [ ] Constraints not too restrictive - -**Task executing but failing:** -- [ ] Added try-catch error handling -- [ ] Dependencies initialized in background isolate -- [ ] Network timeouts configured appropriately -- [ ] iOS 30-second limit respected - -**Performance issues:** +**Performance & Reliability:** - [ ] Task logic optimized for background execution -- [ ] Heavy processing split into smaller chunks -- [ ] Database operations use appropriate batch sizes -- [ ] Network requests have reasonable timeouts +- [ ] Dependencies initialized in background isolate +- [ ] Error handling with try-catch blocks +- [ ] iOS 30-second execution limit respected Remember: Background task execution is controlled by the operating system and is never guaranteed. Always design your app to work gracefully when background tasks don't run as expected. \ No newline at end of file diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index d57f5c1d..1489d0cb 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -114,8 +114,6 @@ WorkmanagerPlugin.registerPeriodicTask( @pragma('vm:entry-point') void callbackDispatcher() { Workmanager().executeTask((task, inputData) async { - print("Background task: $task"); - switch (task) { case "data_sync": await syncDataWithServer(); @@ -123,6 +121,13 @@ void callbackDispatcher() { case "cleanup": await cleanupOldFiles(); break; + case Workmanager.iOSBackgroundTask: + // iOS Background Fetch task + await handleBackgroundFetch(); + break; + default: + // Handle unknown task types + break; } return Future.value(true); @@ -130,13 +135,19 @@ void callbackDispatcher() { } ``` + +**Important:** The `callbackDispatcher` must be a top-level function (not inside a class) since it runs in a separate isolate. + + ### 2. Initialize in main() ```dart +import 'package:flutter/foundation.dart'; + void main() { Workmanager().initialize( callbackDispatcher, - isInDebugMode: true, + isInDebugMode: kDebugMode, ); runApp(MyApp()); @@ -146,46 +157,19 @@ void main() { ### 3. Schedule Tasks ```dart -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - appBar: AppBar(title: Text('Workmanager Demo')), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () { - // Schedule a one-time task - Workmanager().registerOneOffTask( - "sync-task", - "data_sync", - initialDelay: Duration(seconds: 10), - ); - }, - child: Text('Sync Data (One-time)'), - ), - SizedBox(height: 20), - ElevatedButton( - onPressed: () { - // Schedule a periodic task - Workmanager().registerPeriodicTask( - "cleanup-task", - "cleanup", - frequency: Duration(hours: 24), - ); - }, - child: Text('Daily Cleanup (Periodic)'), - ), - ], - ), - ), - ), - ); - } -} +// Schedule a one-time task +Workmanager().registerOneOffTask( + "sync-task", + "data_sync", + initialDelay: Duration(seconds: 10), +); + +// Schedule a periodic task +Workmanager().registerPeriodicTask( + "cleanup-task", + "cleanup", + frequency: Duration(hours: 24), +); ``` ## Task Results