Background tasks for Flutter that run in pure Kotlin & Swift — no Flutter Engine boot, no 50 MB RAM hit, no 2-second cold-start penalty.
1. Add the dependency:
dependencies:
native_workmanager: ^1.1.02. Initialize once in main():
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await NativeWorkManager.initialize();
runApp(MyApp());
}3. Schedule your first background task:
await NativeWorkManager.enqueue(
taskId: 'daily-sync',
worker: NativeWorker.httpSync(url: 'https://api.example.com/sync'),
constraints: Constraints(requiresWifi: true),
);iOS only — run once to configure BGTaskScheduler in your Xcode project automatically:
dart run native_workmanager:setup_iosThe popular workmanager plugin boots a full Flutter Engine for every background task — ~50 MB RAM, up to 3 seconds startup, and a process that the OS kills aggressively on battery-constrained devices.
native_workmanager skips the engine entirely. Workers run as pure Kotlin coroutines or Swift async tasks.
| Metric | workmanager (Dart-based) | native_workmanager |
|---|---|---|
| Memory per task | ~50–100 MB | ~2–5 MB |
| Task startup | 1,500–3,000 ms | < 50 ms |
| Battery impact | High | Ultra-low |
| Survives OS task kill | ❌ Engine crash | ✅ Native resilience |
| Custom Dart workers | ✅ | ✅ (opt-in via DartWorker) |
| Feature | Android | iOS |
|---|---|---|
| One-time tasks | ✅ | ✅ |
| Periodic tasks | ✅ | ✅ (BGAppRefresh) |
| Task chains | ✅ | ✅ |
| Constraints (Wi-Fi, charging, storage) | ✅ | ✅ |
| Foreground service (long tasks) | ✅ | — |
| Custom Dart workers | ✅ | ✅ |
| Min OS version | Android 8.0 (API 26) | iOS 14.0 |
25+ production-grade workers, zero engine overhead:
| Category | Workers |
|---|---|
| HTTP / Network | httpDownload (resumable), httpUpload (multipart), parallelDownload (chunked), httpSync, httpRequest |
| Media | imageResize, imageCrop, imageConvert, imageThumbnail (all EXIF-aware) |
pdfMerge, pdfCompress, imagesToPdf |
|
| Crypto | cryptoEncrypt (AES-256-GCM), cryptoDecrypt, cryptoHash (SHA-256/512), hmacSign |
| File System | fileCopy, fileMove, fileDelete, fileCompress (ZIP), fileDecompress, fileList |
| Storage | moveToSharedStorage (Android MediaStore / iOS Files) |
| Real-time | webSocket (connect / send / receive) — Android |
Chain workers into persistent pipelines. Each step only runs when the previous one succeeds. Data flows automatically between steps.
await NativeWorkManager
.beginWith(TaskRequest(
id: 'download',
worker: NativeWorker.httpDownload(
url: 'https://cdn.example.com/photo.jpg',
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: 'https://api.example.com/photos',
filePath: '/tmp/thumb.jpg',
),
))
.named('photo-pipeline')
.enqueue();- Persistent — survives device reboots and app kills (SQLite-backed state)
- Per-step retry — Step 2 retries independently; Step 1 never re-runs
- Parallel steps — use
.thenAll([...])to run tasks concurrently then join
Need app-specific logic? Register a Dart function as a background worker:
@pragma('vm:entry-point')
Future<bool> myWorker(Map<String, dynamic> input) async {
final userId = input['userId'] as String;
await syncUserData(userId);
return true;
}
// Register once at startup
NativeWorkManager.registerDartWorker('user-sync', myWorker);
// Schedule it
await NativeWorkManager.enqueue(
taskId: 'sync-user-42',
worker: DartWorker(workerName: 'user-sync', input: {'userId': '42'}),
);NativeWorkManager.events.listen((event) {
if (event.isStarted) return; // lifecycle event, not a result
if (event.success) {
print('✅ ${event.taskId} completed');
print(' result: ${event.resultData}');
} else {
print('❌ ${event.taskId} failed: ${event.message}');
}
});📸 Photo Backup Pipeline
await NativeWorkManager
.beginWith(TaskRequest(
id: 'fetch',
worker: NativeWorker.httpDownload(url: photoUrl, savePath: '/tmp/photo.jpg'),
))
.then(TaskRequest(
id: 'compress',
worker: NativeWorker.imageResize(
inputPath: '/tmp/photo.jpg',
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();🔐 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(intervalMinutes: 60),
constraints: Constraints(requiresNetworkConnectivity: true),
policy: ExistingTaskPolicy.keep,
);| Guide | Description |
|---|---|
| Getting Started | Full setup walkthrough with copy-paste examples |
| API Reference | Complete reference for all public types |
| Migration from workmanager | Switch in under 5 minutes |
| iOS Setup Guide | BGTaskScheduler configuration details |
| Architecture | How zero-engine execution works |
- GitHub Issues — bug reports and feature requests
- Discussions — community help and questions
MIT License · Made by BrewKits
If native_workmanager saves you time, a ⭐ on GitHub goes a long way.