native_workmanager 1.0.8
native_workmanager: ^1.0.8 copied to clipboard
Background task manager for Flutter using platform-native APIs. Zero Flutter Engine overhead for I/O operations.
native_workmanager #
Background tasks for Flutter that actually work — native speed, zero Flutter Engine overhead.
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 iOS —
FlutterCallbackCachereturns 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 workers —
HttpUpload,FileDecompression,FileSystem,ImageProcess,FileCompression,Cryptoall now useFile.canonicalPath(replaces bypassablecontains("..")check). - HttpDownloadWorker null-body NPE fixed —
response.body!!force-unwrap replaced with explicit null check. - Version alignment —
pubspec.yaml, podspec,build.gradleall corrected to1.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 #
- Issues — bug reports and feature requests
- Discussions — questions and ideas
- datacenter111@gmail.com — direct contact
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.