Skip to content

brewkits/native_workmanager

Repository files navigation

native_workmanager

native_workmanager

Background tasks for Flutter β€” 25+ built-in workers, zero Flutter Engine overhead.
HTTP, file ops, image processing, encryption β€” all in pure Kotlin & Swift.

pub.dev Pub Points CI MIT Android 8.0+ iOS 13.0+


The 30-second pitch

// 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.


Quick Start

1. Add the dependency:

dependencies:
  native_workmanager: ^1.2.0

2. 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_ios

Why developers switch from workmanager

The 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 βœ… βœ…
Remote Trigger (Push) ❌ βœ… (FCM/APNs + HMAC Security)
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.


25+ Built-in Workers

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
PDF 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

Track progress in real time

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')

Task Chains

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.


Custom Dart Workers

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.

Android killed-app support β€” When Android kills your app and WorkManager later fires a DartWorker, the process restarts without Flutter. The plugin automatically restores the callbackHandle from SharedPreferences, but your Application class must implement Configuration.Provider so WorkManager uses the plugin's WorkerFactory. One-time setup β€” see Android Setup Guide.

Code generation for DartWorker

The companion native_workmanager_gen package generates type-safe callback IDs and a worker registry from @WorkerCallback annotations, eliminating string-based registration and magic IDs:

@WorkerCallback('health-sync')
Future<bool> syncHealthData(Map<String, dynamic>? input) async { ... }

// Generated: WorkerCallbacks.healthSync, auto-registered in WorkerRegistry

Platform Support

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

Migrating from workmanager

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.


Common Use Cases

πŸ“₯ 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();

Listen to task events

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}');
  }
});

Documentation

Guide Description
Getting Started Full setup walkthrough
API Reference All public types and methods
Android Setup Guide DartWorker killed-app persistence
iOS Setup Guide BGTaskScheduler details
Migration from workmanager Switch in under 10 minutes
Security SSRF, path traversal, data redaction
native_workmanager_gen Code generator for type-safe DartWorker callbacks

Support


MIT License Β· Made by BrewKits

Found this useful? A ⭐ on GitHub helps others discover it.