Skip to content

brewkits/native_workmanager

Repository files navigation

native_workmanager

Background task manager for Flutter — native workers, task chains, zero Flutter Engine overhead.

pub package pub points License: MIT Platform

Schedule background tasks that survive app restarts, reboots, and force-quits. Native Kotlin/Swift workers handle I/O without spawning the Flutter Engine.


Why native_workmanager?

workmanager native_workmanager
HTTP/file tasks without Flutter Engine
Task chains (A → B → C)
11 built-in workers
Constraints enforced (network, charging…) ✅ fixed in v1.0.5
Periodic tasks that actually repeat ✅ fixed in v1.0.5
Dart callbacks for custom logic
Custom Kotlin/Swift workers (no fork)

Quick Start

Requirements: Android API 26+ · iOS 14.0+

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

Periodic sync — 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),
);

Custom Dart logic:

await NativeWorkManager.enqueue(
  taskId: 'process-data',
  trigger: TaskTrigger.oneTime(initialDelay: Duration(minutes: 5)),
  worker: DartWorker(callbackId: 'processData'),
  constraints: Constraints(requiresNetwork: true, requiresCharging: true),
);

11 Built-in Workers

Worker What it does
httpSync Fire-and-forget API call
httpDownload Download with resume + checksum
httpUpload Single or multi-file upload with progress
httpRequest Full HTTP request with response validation
fileCompress ZIP a folder or file list
fileDecompress Extract ZIP (zip-slip + zip-bomb protected)
fileSystem Copy, move, delete, list, mkdir
imageProcess Resize/compress/convert, EXIF-aware
cryptoEncrypt AES-256-GCM encrypt (random salt, PBKDF2)
cryptoDecrypt AES-256-GCM decrypt
hashFile MD5, SHA-1, SHA-256, SHA-512

Custom Native Workers

Extend with your own Kotlin or Swift workers — no forking, no MethodChannel boilerplate. Runs on native thread, zero Flutter Engine overhead.

// Android — implement AndroidWorker
class EncryptWorker : AndroidWorker {
    override suspend fun doWork(input: String?): WorkerResult {
        val path = Json.parseToJsonElement(input!!).jsonObject["path"]!!.jsonPrimitive.content
        // Android Keystore, Room, TensorFlow Lite — any native API
        return WorkerResult.Success()
    }
}
// Register in MainActivity.kt (once):
// SimpleAndroidWorkerFactory.setUserFactory { name -> if (name == "EncryptWorker") EncryptWorker() else null }
// iOS — implement IosWorker
class EncryptWorker: IosWorker {
    func doWork(input: String?) async throws -> WorkerResult {
        // CryptoKit, Core Data, Core ML — any native API
        return .success()
    }
}
// Register in AppDelegate.swift (once):
// IosWorkerFactory.registerWorker(className: "EncryptWorker") { EncryptWorker() }
// Dart — identical call on both platforms
await NativeWorkManager.enqueue(
  taskId: 'encrypt-file',
  trigger: TaskTrigger.oneTime(),
  worker: NativeWorker.custom(
    className: 'EncryptWorker',
    input: {'path': '/data/document.pdf'},
  ),
);

Full guide → · Architecture →


Task Chains

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),
  ),
).enqueue(chainName: 'update-model');
// Pure native — zero Flutter Engine, each step retries independently

Events & Progress

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

NativeWorkManager.progress.listen((u) => print('${u.taskId}: ${u.progress}% — ${u.bytesDownloaded}B'));

Migrating from workmanager

Before:

Workmanager().registerPeriodicTask('sync', 'apiSync', frequency: Duration(hours: 1));

After:

NativeWorkManager.enqueue(
  taskId: 'sync',
  trigger: TaskTrigger.periodic(Duration(hours: 1)),
  worker: NativeWorker.httpSync(url: 'https://api.example.com/sync'),
);

~90% API compatible. Migration guide → · Migration CLI →


What's New in v1.0.5

  • Periodic tasks repeat correctly — trigger type was hardcoded to OneTime (fixed)
  • Constraints enforcedrequiresNetwork, initialDelay, etc. were silently ignored on Android (fixed)
  • Chain resume preserves worker config — all config was lost after app kill (fixed)
  • Custom iOS worker registrationIosWorker protocol is now public (fixed)
  • HttpDownload resume — partial downloads preserved on error so retries use Range header (fixed)
  • Swift Package Manager support — works with both SPM and CocoaPods

Full changelog →


Documentation

Quick Start · API Reference · FAQ · Android Setup · iOS Background Limits · Migration Guide · Security

Use cases: Periodic Sync · File Upload · Photo Backup · Chain Processing · Custom Workers


Support


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

If this saves you time, a ⭐ on GitHub helps a lot.