A: It depends on the platform and how the app was closed:
Android:
Yes! Tasks are registered with Android WorkManager and survive:
- User force-close (swiping the app away)
- System Purge (OOM kills)
- Phone reboot
Note: For maximum reliability, use
isHeavyTask: true(Foreground Service) for critical tasks to ensure execution even in Doze mode.
iOS:
- System Purge (OOM kill): Yes. If iOS kills the app in the background to reclaim memory, tasks are preserved and will trigger when iOS allocates the next background window.
native_workmanagerexcels here because it uses a native SQLite store to recover state instantly. - User Force-Quit (Swipe up): No. This is a strict Apple platform limitation. If a user manually swipes the app away, iOS cancels all pending background tasks. The system will not wake the app again until the user manually relaunches it. No third-party package can bypass this.
A: The underlying mechanisms are fundamentally different, making native_workmanager vastly more resilient to system memory limits (OOM kills):
-
Zero-Engine Memory Footprint (~2MB vs 50MB+): Traditional packages like
flutter_background_servicekeep a full Flutter Engine running 24/7 inside a persistent service. When Android runs low on RAM, the OOM Killer targets heavy processes first. Once killed, it struggles to restart.native_workmanager's Native Workers run in pure Kotlin/Swift (<50ms startup, ~2MB RAM). Because it is so lightweight, the OS rarely considers it a target for termination. -
Event-Driven vs. Persistent Service: We do not maintain a permanent service.
native_workmanagerrelies on native schedulers (WorkManager/BGTaskScheduler). The Foreground Service (viaisHeavyTask) is only invoked at the exact moment the task runs and is torn down immediately after. -
System-Level State Persistence: If the OS does kill the process mid-execution, the task state is safely stored in the internal
WorkManagerSQLite database. Android will automatically reschedule it once resources free up.
A: It depends on the worker type:
| Worker Type | Memory Usage | Startup Time |
|---|---|---|
| Native Workers | 2-5 MB | <100ms |
| Dart Workers | ~50 MB | 200-500ms |
| Custom Native Workers | Varies (typically 1-10 MB) | <100ms |
Why the difference?
- Native workers execute without starting Flutter engine
- Dart workers need full Flutter engine initialization
A: Technically yes, but not recommended for several reasons:
iOS Limitation:
- Each task in chain must complete within 30 seconds
- iOS may cancel long chains
- Recommendation: Keep chains to 3-5 tasks max on iOS
Android:
- No strict limit, but very long chains can be fragile
- Recommendation: Keep chains to 5-10 tasks max
Better approach:
// Instead of: Task1 → Task2 → ... → Task100
// Use: Periodic task that processes batches
NativeWorkManager.enqueue(
taskId: 'batch-processor',
trigger: TaskTrigger.periodic(Duration(hours: 1)),
worker: DartWorker(callbackId: 'processBatch'),
);A: The chain stops at the failed task:
- Tasks before failure: ✅ Completed successfully
- Failed task: ❌ Marked as failed
- Tasks after failure: ⏸️ Not executed (cancelled)
Example:
TaskA (✅) → TaskB (❌ FAILS) → TaskC (⏸️ Skipped) → TaskD (⏸️ Skipped)
Retry behavior:
- If you configured retry policy, the failed task retries
- Chain continues only if retry succeeds
- If all retries fail, chain stops permanently
A: ~90% API compatible with minor syntax changes required.
Main differences:
- Import:
import 'package:native_workmanager/native_workmanager.dart'; - Initialization:
NativeWorkManager.initialize()vsWorkmanager.initialize() - Enqueue syntax: Different trigger API
- Native workers: New capability not in workmanager
A: No, background tasks are for periodic work, not continuous tracking.
Why?
- Tasks run at intervals (minimum 15 minutes on iOS, 15-30 minutes on Android)
- Tasks have execution time limits (30 seconds on iOS, 10 minutes on Android)
- OS may defer tasks to save battery
For location tracking, use:
geolocatorwith background modesbackground_location- Foreground service on Android
Use native_workmanager for:
- Upload location batches every hour
- Process and sync location logs
- Periodic geofence checks
A: By default, tasks are deferred during Doze mode to save battery. However, you can bypass this using specific constraints.
Should I use allowWhileIdle or isHeavyTask? (Do not use both)
They serve different purposes and combining them is redundant and may cause scheduling conflicts:
-
isHeavyTask: true(Foreground Service)- Android: Promotes your task to a full Foreground Service (requires
ForegroundNotificationConfig). It completely bypasses Doze mode, ignores App Standby quotas, and survives app kills. This is the highest priority execution Android allows. - iOS: Maps to a
BGProcessingTask. - When to use: For critical, long-running tasks (e.g., uploading large files) that MUST finish reliably.
- Trade-off: Shows a visible notification to the user while running.
- Android: Promotes your task to a full Foreground Service (requires
-
allowWhileIdle: true(Expedited Work)- Android: Uses WorkManager's Expedited Work mechanism to run the task even if the device is locked/in Doze mode, without showing a persistent notification.
- iOS: Ignored (no effect).
- When to use: For short, fast tasks (< 1 minute) that you want to trigger silently.
- Trade-off: Strictly regulated by Android's App Standby Buckets (quotas). If you exceed the quota, Android downgrades the task. Also, WorkManager rejects Expedited jobs if combined with constraints like
requiresCharging.
Summary: If you use isHeavyTask: true, the task already bypasses Doze mode. Adding allowWhileIdle: true is redundant and discouraged.
A: This is a strict OS-level battery saving feature, not a plugin bug.
Android (Doze Mode & Manufacturer Optimizations):
When the app is killed and the screen is locked/off, Android enters Doze mode (or manufacturer-specific deep sleep, especially on Xiaomi, Huawei, Oppo, Vivo). In this state, the OS suspends all WorkManager tasks, network access, and WakeLocks to save battery. As soon as the user turns the screen on or unlocks the device, the OS exits Doze mode and all deferred tasks trigger immediately.
To mitigate this:
- Guide users to device Settings and set your app's Battery usage to Unrestricted.
- On aggressive ROMs (Xiaomi/Oppo/Vivo), users must enable Auto-start for your app.
- You can prompt users for the
REQUEST_IGNORE_BATTERY_OPTIMIZATIONSpermission if your app's core functionality requires it.
iOS:
iOS BGTaskScheduler is strictly opportunistic. While iOS can execute tasks while locked, it may heavily defer them if the app was swiped away (killed) or if the device is in Low Power Mode.
A: No, native_workmanager is for flexible background tasks, not exact alarms.
Why?
- iOS doesn't support exact-time background tasks
- Android Doze mode defers tasks anyway
- Background tasks are designed for flexibility
For exact alarms, use:
flutter_local_notificationsandroid_alarm_manager_plus(Android only)- Platform channels to native alarm APIs
Use native_workmanager for:
- Periodic work that can be flexible (±15 minutes is OK)
- Background data sync
- File processing tasks
A: Follow these strategies:
1. Use logging:
worker: DartWorker(
callbackId: 'myTask',
onProgress: (progress) {
print('Task progress: $progress'); // Won't show in release
// Use proper logging instead:
developer.log('Task progress: $progress', name: 'NativeWorkManager');
},
)2. Listen to task events:
NativeWorkManager.events.listen((event) {
print('Task ${event.taskId}: ${event.success ? "✅" : "❌"}');
print('Message: ${event.message}');
});3. Check native logs:
iOS: Use Xcode Console while device is connected
Android: Use adb logcat or Android Studio Logcat
4. Test in foreground first:
// Test task logic in foreground before background
await myTaskLogic(); // Test this works
// Then schedule as background taskA: Direct data passing between tasks is not natively supported. Use shared storage as a workaround:
// Task 1: Save result
await NativeWorkManager.beginWith(
TaskRequest(id: 'task1', worker: DartWorker(callbackId: 'saveData')),
).then(
TaskRequest(id: 'task2', worker: DartWorker(callbackId: 'useData')),
).enqueue();
// In task1 callback:
void saveData() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('task1_result', jsonEncode(result));
}
// In task2 callback:
void useData() async {
final prefs = await SharedPreferences.getInstance();
final data = jsonDecode(prefs.getString('task1_result')!);
// Use data...
}A: Platform-specific minimums:
| Platform | Minimum Interval | Notes |
|---|---|---|
| Android | 15 minutes | WorkManager API limitation |
| iOS | 15 minutes | BGTaskScheduler limitation |
Example:
// ✅ Works (1 hour)
trigger: TaskTrigger.periodic(Duration(hours: 1))
// ⚠️ Will be clamped to 15 minutes
trigger: TaskTrigger.periodic(Duration(minutes: 5))
// ❌ Will fail
trigger: TaskTrigger.periodic(Duration(seconds: 30))A: Only if your tasks use network:
Android (android/app/src/main/AndroidManifest.xml):
<uses-permission android:name="android.permission.INTERNET" />iOS (ios/Runner/Info.plist):
- Network access allowed by default
- If using HTTP (not HTTPS), configure App Transport Security
Permissions for workers:
- HTTP workers: Need INTERNET permission
- File workers: No special permissions
- Crypto workers: No special permissions
A: Yes, but avoid conflicts:
Compatible:
- ✅
flutter_local_notifications- Different use case (notifications vs tasks) - ✅
geolocator- Can coexist (use geolocator for continuous, native_workmanager for periodic) - ✅
shared_preferences- For task data storage
Potential conflicts:
⚠️ workmanager- Same underlying APIs, choose one⚠️ workmanager- Same underlying APIs, choose one
Best practice: Use native_workmanager as your primary background task solution.
A: Use retry policies and constraints:
await NativeWorkManager.enqueue(
taskId: 'critical-sync',
trigger: TaskTrigger.oneTime(),
worker: NativeWorker.httpSync(url: 'https://api.example.com/sync'),
constraints: Constraints(
requiresNetwork: true,
backoffPolicy: BackoffPolicy.exponential, // Exponential backoff
backoffDelayMs: 30000, // Start with 30s delay
maxRetries: 5, // Retry up to 5 times
),
);Retry strategies:
BackoffPolicy.linear: Fixed delay (30s, 30s, 30s, ...)BackoffPolicy.exponential: Growing delay (30s, 60s, 120s, 240s, ...)
Support channels:
- 💬 GitHub Discussions - Ask questions
- 🐛 Issue Tracker - Report bugs
- 📖 Documentation - Comprehensive guides
- 📧 Email: support@brewkits.dev - Direct support
Before asking:
- Check this FAQ
- Read Getting Started Guide
- Review Use Cases for similar scenarios
- Search existing issues
Common reasons:
-
Task not triggered yet
- iOS defers background tasks
- Test by backgrounding app, waiting 30+ seconds
-
30-second limit exceeded
- Tasks must complete in 30 seconds
- Use native workers (faster startup, no engine overhead)
- Split into chains
-
Low Power Mode active
- iOS deprioritizes background tasks
- Add
requiresCharging: trueconstraint
-
BGTaskScheduler not configured
- Check
Info.plisthasBGTaskSchedulerPermittedIdentifiers - See iOS Guide
- Check
Common reasons:
-
Minimum SDK version too low
- Plugin requires API 26+ (Android 8.0+)
- Edit
android/app/build.gradle:
defaultConfig { minSdk 26 // Must be 26 or higher! }
-
Initialization not called or not awaited
- Ensure
await NativeWorkManager.initialize()inmain()
void main() async { WidgetsFlutterBinding.ensureInitialized(); await NativeWorkManager.initialize(); // ← Must await! runApp(MyApp()); }
- Ensure
-
Build cache corruption
- Clean and rebuild:
flutter clean rm -rf android/build android/app/build flutter pub get flutter build apk --debug
-
Check logcat for details
adb logcat -s NativeWorkmanagerPlugin
See also: Full Android Setup Guide
Common reasons:
-
Doze Mode
- Android defers tasks in Doze
- Use
requiresCharging: trueor wait for idle window
-
Battery Saver Mode
- Tasks have lower priority
- Use
requiresBatteryNotLow: true
-
Network constraint not met
- Task waits for network
- Check
requiresNetwork: trueconstraint
-
Minimum interval not met
- Periodic tasks minimum: 15 minutes
- Check your trigger interval
A: Yes! See Custom Native Workers Guide
Quick example:
Kotlin:
class MyCustomWorker : AndroidWorker {
override suspend fun doWork(input: String?): WorkerResult {
// Your Kotlin code here
return WorkerResult.success("Done!")
}
}Swift:
class MyCustomWorker: IosWorker {
func doWork(input: String?) async throws -> WorkerResult {
// Your Swift code here
return WorkerResult.success(data: "Done!")
}
}A: Minimal impact:
Initialization:
- Native platform APIs (WorkManager, BGTaskScheduler)
- Time: <10ms on modern devices
- Memory: <1MB
Background task execution:
- No impact on app foreground performance
- Tasks run in separate process/thread
A: Yes! Production-ready features:
- ✅ Security audited - No critical vulnerabilities
- ✅ 462 tests passing - 100% pass rate
- ✅ Used in production - Apps with 1M+ users
- ✅ MIT licensed - Commercial use allowed
- ✅ Comprehensive docs - 20+ guides
Didn't find your question?