native_workmanager

Background tasks for Flutter that actually work — native speed, zero Flutter Engine overhead.

pub package pub points License: MIT Platform

HTTP syncs, file downloads, crypto operations, image processing — all running natively in Kotlin or Swift while your app is in the background, without ever spawning a Flutter Engine.


The problem with existing solutions

Every Flutter background task library forces you to boot a Flutter Engine to run Dart code in the background. That costs ~50 MB of RAM, 1–2 seconds of cold-start latency, and drains battery. For simple I/O tasks like syncing data or downloading a file, this is completely unnecessary.

native_workmanager takes a different approach: built-in Kotlin/Swift workers handle the most common tasks natively, with zero Flutter overhead. Dart callbacks are still supported for custom logic — but you choose when to pay for them.


Why native_workmanager?

workmanager native_workmanager
HTTP / file tasks without Flutter Engine
Task chains (A → B → C, retries per step)
11 built-in workers (HTTP, file, crypto, image)
Custom Kotlin/Swift workers (no fork required)
Dart callbacks for custom logic
Constraints enforced (network, charging…)
Periodic tasks that actually repeat
RAM for a pure-native task ~50 MB ~2 MB

Requirements

Platform Minimum
Android API 26 (Android 8.0)
iOS 14.0
Flutter 3.10+

Installation

flutter pub add native_workmanager

Platform setup: Android · iOS


Quick Start

1. Initialize once in main():

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await NativeWorkManager.initialize();
  runApp(MyApp());
}

2. Schedule a task — that's it:

// Periodic API sync — pure Kotlin/Swift, no Flutter Engine
await NativeWorkManager.enqueue(
  taskId: 'hourly-sync',
  trigger: TaskTrigger.periodic(Duration(hours: 1)),
  worker: NativeWorker.httpSync(url: 'https://api.example.com/sync'),
  constraints: Constraints(requiresNetwork: true),
);

3. Listen for results:

NativeWorkManager.events.listen((event) {
  print('${event.taskId}: ${event.success ? "done" : event.message}');
});

11 Built-in Workers

No third-party code. No Flutter Engine. Every worker runs natively in Kotlin (Android) and Swift (iOS).

HTTP

// Fire-and-forget API ping
NativeWorker.httpSync(
  url: 'https://api.example.com/heartbeat',
  method: HttpMethod.post,
  headers: {'Authorization': 'Bearer $token'},
)

// Download with automatic resume on failure
NativeWorker.httpDownload(
  url: 'https://cdn.example.com/update.zip',
  savePath: '${docsDir.path}/update.zip',
  enableResume: true,
  expectedChecksum: 'a3b2c1...',   // SHA-256 verified after download
)

// Upload a file with additional form fields
NativeWorker.httpUpload(
  url: 'https://api.example.com/photos',
  filePath: '/tmp/photo1.jpg',
  additionalFields: {'albumId': '42'},
)

// Full request with response validation
NativeWorker.httpRequest(
  url: 'https://api.example.com/order',
  method: HttpMethod.post,
  body: jsonEncode({'itemId': 99}),
)

File

// Compress a folder to ZIP
NativeWorker.fileCompress(
  inputPath: '${docsDir.path}/logs/',
  outputPath: '${cacheDir.path}/logs_2026.zip',
  level: CompressionLevel.high,
  deleteOriginal: true,
)

// Extract ZIP — zip-slip and zip-bomb protected
NativeWorker.fileDecompress(
  zipPath: '${cacheDir.path}/model.zip',
  targetDir: '${docsDir.path}/model/',
  deleteAfterExtract: true,
)

// Move, copy, delete, list, mkdir
NativeWorker.fileMove(
  sourcePath: '/tmp/raw.bin',
  destinationPath: '${docsDir.path}/processed.bin',
)

Image

// Resize and compress — EXIF-aware, 10× faster than Dart image packages
NativeWorker.imageProcess(
  inputPath: '${cacheDir.path}/raw.heic',
  outputPath: '${docsDir.path}/thumb.jpg',
  maxWidth: 1280,
  maxHeight: 720,
  quality: 85,
  outputFormat: ImageFormat.jpeg,
)

Crypto

// AES-256 encrypt (random IV + PBKDF2 key derivation)
NativeWorker.cryptoEncrypt(
  inputPath: '${docsDir.path}/report.pdf',
  outputPath: '${docsDir.path}/report.pdf.enc',
  password: 'secret',
)

// Verify file integrity
NativeWorker.hashFile(
  filePath: '${docsDir.path}/firmware.bin',
  algorithm: HashAlgorithm.sha256,
)

Task Chains

Chain workers sequentially — each step retries independently, and output from one step flows into the next. Pure native, zero Flutter Engine.

await NativeWorkManager.beginWith(
  TaskRequest(
    id: 'download',
    worker: NativeWorker.httpDownload(
      url: 'https://cdn.example.com/model.zip',
      savePath: '/tmp/model.zip',
    ),
  ),
).then(
  TaskRequest(
    id: 'extract',
    worker: NativeWorker.fileDecompress(
      zipPath: '/tmp/model.zip',
      targetDir: '/data/model/',
      deleteAfterExtract: true,
    ),
  ),
).then(
  TaskRequest(
    id: 'verify',
    worker: NativeWorker.hashFile(
      filePath: '/data/model/weights.bin',
      algorithm: HashAlgorithm.sha256,
    ),
  ),
).named('update-model').enqueue();

Each step only runs if the previous one succeeded. If a step fails and exhausts retries, the chain stops and emits a failure event. See Chain Processing guide →


Custom Native Workers

Need to use Android Keystore, TensorFlow Lite, Core ML, or any platform API not covered by the built-ins? Write a native worker — no forking, no MethodChannel boilerplate.

Android (Kotlin):

class MLInferenceWorker : AndroidWorker {
    override suspend fun doWork(input: String?): WorkerResult {
        val imagePath = JSONObject(input!!).getString("imagePath")
        val result = TFLiteModel.run(imagePath)   // TensorFlow Lite, Core ML, Room…
        return WorkerResult.Success(data = mapOf("label" to result.label))
    }
}

// Register once in MainActivity.kt
SimpleAndroidWorkerFactory.setUserFactory { name ->
    if (name == "MLInferenceWorker") MLInferenceWorker() else null
}

iOS (Swift):

class MLInferenceWorker: IosWorker {
    func doWork(input: String?) async throws -> WorkerResult {
        let imagePath = try JSONDecoder().decode(Config.self, from: input!.data(using: .utf8)!).imagePath
        let result = try await CoreMLModel.run(imagePath)   // Core ML, CryptoKit, CoreData…
        return .success(data: ["label": result.label])
    }
}

// Register once in AppDelegate.swift
IosWorkerFactory.registerWorker(className: "MLInferenceWorker") { MLInferenceWorker() }

Dart (same API on both platforms):

await NativeWorkManager.enqueue(
  taskId: 'run-inference',
  trigger: TaskTrigger.oneTime(),
  worker: NativeWorker.custom(
    className: 'MLInferenceWorker',
    input: {'imagePath': '/photos/IMG_001.jpg'},
  ),
);

Full extensibility guide → · Use-case walkthrough →


Dart Callbacks

For logic that must run in Dart (database writes, state management, push notifications), use DartWorker. The Flutter Engine is reused when the app is in the foreground — no cold-start penalty.

// 1. Register top-level callbacks at startup
await NativeWorkManager.initialize(
  dartWorkers: {
    'syncContacts': syncContactsCallback,  // must be top-level or static
    'cleanCache':  cleanCacheCallback,
  },
);

// 2. Schedule
await NativeWorkManager.enqueue(
  taskId: 'nightly-sync',
  trigger: TaskTrigger.periodic(Duration(hours: 24)),
  worker: DartWorker(
    callbackId: 'syncContacts',
    input: {'lastSyncTs': timestamp},
  ),
  constraints: Constraints(requiresCharging: true, requiresNetwork: true),
);

// 3. Implement — top-level function required
@pragma('vm:entry-point')
Future<bool> syncContactsCallback(Map<String, dynamic>? input) async {
  final since = input?['lastSyncTs'] as int?;
  await ContactsService.sync(since: since);
  return true;   // true = success, false = retry
}

Events & Progress

// Task completion
NativeWorkManager.events.listen((event) {
  print('${event.taskId}: ${event.success ? "done" : "failed — ${event.message}"}');
  if (event.resultData != null) print('  data: ${event.resultData}');
});

// Download / upload progress
NativeWorkManager.progress.listen((update) {
  print('${update.taskId}: ${update.progress}%'
        '${update.message != null ? " — ${update.message}" : ""}');
});

Constraints & Triggers

// Run only on Wi-Fi while charging
await NativeWorkManager.enqueue(
  taskId: 'heavy-backup',
  trigger: TaskTrigger.oneTime(Duration(minutes: 30)),
  worker: NativeWorker.httpUpload(
    url: 'https://backup.example.com/upload',
    filePath: backupPath,
  ),
  constraints: Constraints(
    requiresUnmeteredNetwork: true,   // Wi-Fi only
    requiresCharging: true,
    isHeavyTask: true,                // Android: foreground service (no 10-min limit)
  ),
);

// Cancel when user logs out
await NativeWorkManager.cancelByTag('user-session');

Available triggers: oneTime(), oneTime(Duration), periodic(Duration), exact(DateTime), windowed, contentUri, batteryOkay, batteryLow, deviceIdle, storageLow.


Migrating from workmanager

~90% API compatible. Common patterns translate directly:

// Before
Workmanager().registerPeriodicTask(
  'sync', 'apiSync',
  frequency: Duration(hours: 1),
  constraints: Constraints(networkType: NetworkType.connected),
);

// After — and the task actually repeats this time
await NativeWorkManager.enqueue(
  taskId: 'sync',
  trigger: TaskTrigger.periodic(Duration(hours: 1)),
  worker: NativeWorker.httpSync(url: 'https://api.example.com/sync'),
  constraints: Constraints(requiresNetwork: true),
);

Step-by-step migration guide → · Automated migration CLI →


What's New in v1.0.8

  • DartWorker works in debug / integration-test mode on iOSFlutterCallbackCache returns nil in JIT builds; fixed by routing through the existing main method channel instead of a secondary engine. All 37 iOS integration tests now pass.
  • DartWorker callback input now correctly decoded — input was being passed as {'raw': '...'} wrapper instead of the actual decoded map; callbacks now receive {'key': 'value'} as expected.
  • Path traversal hardened across all Android workersHttpUpload, FileDecompression, FileSystem, ImageProcess, FileCompression, Crypto all now use File.canonicalPath (replaces bypassable contains("..") check).
  • HttpDownloadWorker null-body NPE fixedresponse.body!! force-unwrap replaced with explicit null check.
  • Version alignmentpubspec.yaml, podspec, build.gradle all corrected to 1.0.8.

Full changelog →


Documentation

Guide Description
Getting Started 3-minute setup with copy-paste examples
API Reference Complete API for all public types
Android Setup ProGuard, permissions, foreground service
iOS Background Limits BGTaskScheduler, 30-second rule, periodic limitations
Migration Guide Migrate from workmanager step-by-step
Extensibility Writing custom Kotlin/Swift workers
Security Path traversal, URL validation, sandboxing
FAQ Common questions and troubleshooting

Use cases: Periodic Sync · File Upload with Retry · Background Cleanup · Photo Backup · Chain Processing · Custom Workers


Support


MIT License · Nguyễn Tuấn Việt · BrewKits

If native_workmanager saves you time, a star on GitHub goes a long way — it helps other developers find the package.

Libraries

native_workmanager
Native background task manager for Flutter.