Background tasks for Flutter — 25+ built-in workers, zero Flutter Engine overhead.
HTTP, file ops, image processing, encryption — all in pure Kotlin & Swift.
// Download → resize → upload — survives app kill, device reboot, low memory
await NativeWorkManager
.beginWith(TaskRequest(id: 'dl',
worker: NativeWorker.httpDownload(url: photoUrl, savePath: '/tmp/raw.jpg')))
.then(TaskRequest(id: 'resize',
worker: NativeWorker.imageResize(inputPath: '/tmp/raw.jpg',
outputPath: '/tmp/thumb.jpg', maxWidth: 512)))
.then(TaskRequest(id: 'upload',
worker: NativeWorker.httpUpload(url: uploadUrl, filePath: '/tmp/thumb.jpg')))
.named('photo-pipeline')
.enqueue();No boilerplate. No native code to write. No AndroidManifest.xml changes. Each step retries independently — if the upload fails, only the upload retries.
1. Add the dependency:
dependencies:
native_workmanager: ^1.1.22. Initialize once in main():
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await NativeWorkManager.initialize();
runApp(MyApp());
}3. Schedule a background task:
await NativeWorkManager.enqueue(
taskId: 'daily-sync',
worker: NativeWorker.httpSync(url: 'https://api.example.com/sync'),
constraints: const Constraints(requiresNetwork: true),
);iOS only — run once to configure BGTaskScheduler automatically:
dart run native_workmanager:setup_iosThe dominant workmanager plugin spins up a full Flutter Engine per background task: ~50–100 MB RAM, up to 3 seconds cold start, a Dart isolate the OS kills the moment memory gets tight. On Xiaomi/Samsung/Huawei devices with aggressive battery optimization, the engine never even starts.
native_workmanager runs tasks as pure Kotlin coroutines and Swift async functions — no engine, no isolate, no cold-start penalty.
workmanager |
native_workmanager |
|
|---|---|---|
| Memory per task | ~50–100 MB | ~2–5 MB |
| Task startup | 1,500–3,000 ms | < 50 ms |
| Built-in HTTP workers | ❌ | ✅ (resumable download, chunked upload, parallel) |
| Built-in image workers | ❌ | ✅ (resize, crop, convert, thumbnail — EXIF-aware) |
| Built-in crypto workers | ❌ | ✅ (AES-256-GCM, SHA-256/512, HMAC) |
| Task chains (A→B→C) | ❌ | ✅ (persist across reboots) |
| Per-task progress stream | ❌ | ✅ |
| Survives device reboot | ✅ | ✅ |
| Custom Dart workers | ✅ | ✅ (opt-in via DartWorker) |
If you only do HTTP syncs and file ops, you probably don't need Dart workers at all. Use the native workers directly — they're production-hardened and need zero engine overhead.
All workers run natively. No Flutter Engine. No setup beyond initialize().
| Category | Workers |
|---|---|
| HTTP | httpDownload (resumable), httpUpload (multipart), parallelDownload (chunked), httpSync, httpRequest |
| Image | imageResize, imageCrop, imageConvert, imageThumbnail — all EXIF-aware |
pdfMerge, pdfCompress, imagesToPdf |
|
| Crypto | cryptoEncrypt (AES-256-GCM), cryptoDecrypt, cryptoHash (SHA-256/512), hmacSign |
| File | fileCopy, fileMove, fileDelete, fileList |
| Storage | moveToSharedStorage (Android MediaStore / iOS Files app) |
| Real-time | webSocket — Android |
enqueue() returns a TaskHandler that streams progress and completion events for that specific task — no manual filtering required.
final handler = await NativeWorkManager.enqueue(
taskId: 'big-download',
worker: NativeWorker.httpDownload(
url: 'https://cdn.example.com/video.mp4',
savePath: '/tmp/video.mp4',
),
);
// Stream progress for this task only
handler.progress.listen((p) {
print('${p.progress}% — ${p.networkSpeedHuman} — ETA ${p.timeRemainingHuman}');
});
// Await completion
final result = await handler.result;
print(result.success ? 'Done!' : 'Failed: ${result.message}');Or drop in the built-in widget:
TaskProgressCard(handler: handler, title: 'Downloading video')Chain workers into persistent pipelines. Each step only runs when the previous one succeeds, and the entire chain survives app kills and device reboots (SQLite-backed state).
await NativeWorkManager
.beginWith(TaskRequest(
id: 'download',
worker: NativeWorker.httpDownload(
url: 'https://cdn.example.com/report.pdf',
savePath: '/tmp/report.pdf',
),
))
.then(TaskRequest(
id: 'encrypt',
worker: NativeWorker.cryptoEncrypt(
inputPath: '/tmp/report.pdf',
outputPath: '/tmp/report.enc',
password: vaultKey,
),
))
.then(TaskRequest(
id: 'upload',
worker: NativeWorker.httpUpload(
url: 'https://vault.example.com/store',
filePath: '/tmp/report.enc',
),
))
.named('secure-report-pipeline')
.enqueue();Use .thenAll([...]) to run tasks in parallel, then continue the chain when all finish.
For app-specific logic that must run in Dart, register a top-level function as a background worker:
@pragma('vm:entry-point')
Future<bool> syncHealthData(Map<String, dynamic>? input) async {
final userId = input?['userId'] as String?;
await uploadHealthMetrics(userId);
return true;
}
// Register once at startup
NativeWorkManager.registerDartWorker('health-sync', syncHealthData);
// Schedule it
await NativeWorkManager.enqueue(
taskId: 'sync-user-42',
worker: DartWorker(callbackId: 'health-sync', input: {'userId': '42'}),
);Dart workers boot a headless Flutter isolate (~50 MB, 1–2 s cold start). The isolate is cached for 5 minutes so back-to-back tasks pay the boot cost only once. For HTTP and file tasks, use native workers instead.
| Feature | Android | iOS |
|---|---|---|
| One-time tasks | ✅ | ✅ |
| Periodic tasks | ✅ | ✅ (BGAppRefresh) |
| Exact-time triggers | ✅ | ✅ |
| Task chains (persistent) | ✅ | ✅ |
| Network / charging constraints | ✅ | ✅ |
| Per-task progress stream | ✅ | ✅ |
| Foreground service (long tasks) | ✅ | — |
| Custom Dart workers | ✅ | ✅ |
| Min OS version | Android 8.0 (API 26) | iOS 14.0 |
Most migrations take under 10 minutes. The conceptual model is the same; the API is a strict superset.
workmanager |
native_workmanager |
|---|---|
Workmanager().initialize(...) |
NativeWorkManager.initialize() |
Workmanager().registerOneOffTask(...) |
NativeWorkManager.enqueue(worker: NativeWorker.httpSync(...)) |
Workmanager().registerPeriodicTask(...) |
NativeWorkManager.enqueue(trigger: TaskTrigger.periodic(...)) |
| Custom Dart callback | DartWorker(callbackId: ...) |
See Migration Guide for a step-by-step walkthrough.
📥 Resumable large file download
await NativeWorkManager.enqueue(
taskId: 'download-dataset',
worker: NativeWorker.httpDownload(
url: 'https://data.example.com/dataset.zip',
savePath: '/tmp/dataset.zip',
headers: {'Authorization': 'Bearer $token'},
allowResume: true,
),
constraints: const Constraints(requiresUnmeteredNetwork: true),
);🔐 Encrypt & upload sensitive file
await NativeWorkManager
.beginWith(TaskRequest(
id: 'encrypt',
worker: NativeWorker.cryptoEncrypt(
inputPath: '/documents/report.pdf',
outputPath: '/tmp/report.enc',
password: securePassword,
),
))
.then(TaskRequest(
id: 'upload',
worker: NativeWorker.httpUpload(
url: 'https://vault.example.com/store',
filePath: '/tmp/report.enc',
),
))
.named('secure-backup')
.enqueue();⏱ Periodic background sync
await NativeWorkManager.enqueue(
taskId: 'hourly-sync',
worker: NativeWorker.httpSync(url: 'https://api.example.com/sync'),
trigger: TaskTrigger.periodic(const Duration(hours: 1)),
constraints: const Constraints(requiresNetwork: true),
existingPolicy: ExistingTaskPolicy.keep,
);📸 Photo backup pipeline
await NativeWorkManager
.beginWith(TaskRequest(
id: 'compress',
worker: NativeWorker.imageResize(
inputPath: photoPath,
outputPath: '/tmp/photo_compressed.jpg',
maxWidth: 1920,
quality: 85,
),
))
.then(TaskRequest(
id: 'upload',
worker: NativeWorker.httpUpload(
url: 'https://backup.example.com/upload',
filePath: '/tmp/photo_compressed.jpg',
),
))
.named('photo-backup')
.enqueue();NativeWorkManager.events.listen((event) {
if (event.isStarted) {
print('▶ ${event.taskId} started (${event.workerType})');
return;
}
if (event.success) {
print('✅ ${event.taskId} — ${event.resultData}');
} else {
print('❌ ${event.taskId} — ${event.message}');
}
});| Guide | |
|---|---|
| Getting Started | Full setup walkthrough |
| API Reference | All public types and methods |
| Migration from workmanager | Switch in under 10 minutes |
| iOS Setup Guide | BGTaskScheduler details |
| Architecture | How zero-engine execution works |
| Security | SSRF, path traversal, data redaction |
- GitHub Issues — bugs and feature requests
- Discussions — questions and community help
MIT License · Made by BrewKits
Found this useful? A ⭐ on GitHub helps others discover it.