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 14.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.1.2

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


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

Support


MIT License · Made by BrewKits

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