Goal: Schedule your first background task in under 3 minutes.
- Flutter SDK 3.0+ installed
- Basic understanding of background tasks concept
- Android Studio or VS Code with Flutter extension
-
Android: API 26+ (Android 8.0+) required
- Set
minSdk 26inandroid/app/build.gradle - Full Android setup guide →
- Set
-
iOS: iOS 14.0+ required
- Background tasks have 30-second execution limit
- Full iOS setup guide →
Add native_workmanager to your pubspec.yaml:
flutter pub add native_workmanagerOr manually:
dependencies:
native_workmanager: ^1.2.2Then run:
flutter pub getIf you only need native workers (HTTP, file operations), use basic initialization:
import 'package:flutter/material.dart';
import 'package:native_workmanager/native_workmanager.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize native_workmanager
await NativeWorkManager.initialize();
runApp(MyApp());
}If you need to run Dart code in background tasks:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize with Dart worker callbacks
await NativeWorkManager.initialize(
registerPlugins: true, // Optional: registers all plugins in background
dartWorkers: {
'processData': _processDataCallback,
'syncDatabase': _syncDatabaseCallback,
},
);
runApp(MyApp());
}
// Dart worker callbacks
@pragma('vm:entry-point')
Future<bool> _processDataCallback(Map<String, dynamic>? input) async {
// Your Dart logic here
print('Processing data: $input');
return true; // Success
}
@pragma('vm:entry-point')
Future<bool> _syncDatabaseCallback(Map<String, dynamic>? input) async {
// Database sync logic
return true;
}For periodic API calls, data sync, webhooks:
import 'package:native_workmanager/native_workmanager.dart';
// Somewhere in your app (e.g., after login)
Future<void> schedulePeriodicSync() async {
await NativeWorkManager.enqueue(
taskId: 'periodic-sync', // Unique identifier
trigger: TaskTrigger.periodic(
Duration(hours: 1), // Run every hour
),
worker: NativeWorker.httpSync(
url: 'https://api.example.com/sync',
method: HttpMethod.post,
headers: {
'Authorization': 'Bearer YOUR_TOKEN',
'Content-Type': 'application/json',
},
body: '{"userId": "123"}',
),
constraints: Constraints(
requiresNetworkType: NetworkType.connected, // Any network
),
);
print('✅ Periodic sync scheduled!');
}Upload files in the background with automatic retry:
Future<void> scheduleFileUpload(String filePath) async {
await NativeWorkManager.enqueue(
taskId: 'upload-${DateTime.now().millisecondsSinceEpoch}',
trigger: TaskTrigger.oneTime(),
worker: NativeWorker.httpUpload(
url: 'https://api.example.com/upload',
filePath: filePath,
headers: {'Authorization': 'Bearer YOUR_TOKEN'},
),
constraints: Constraints(
requiresNetworkType: NetworkType.unmetered, // Wi-Fi only
requiresBatteryNotLow: true, // Wait for sufficient battery
),
);
print('✅ File upload scheduled!');
}Run Flutter/Dart code when you need access to packages:
// 1. Register callback in main()
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await NativeWorkManager.initialize(
dartWorkers: {
'complexTask': _complexTaskCallback,
},
);
runApp(MyApp());
}
// 2. Define callback
@pragma('vm:entry-point')
Future<bool> _complexTaskCallback(Map<String, dynamic>? input) async {
try {
// Access to all Dart packages
final db = await openDatabase('my_database.db');
final data = await db.query('tasks');
// Process data
for (var item in data) {
// Complex logic here
}
await db.close();
return true; // Success
} catch (e) {
print('Error: $e');
return false; // Failure (will retry if configured)
}
}
// 3. Schedule the task
Future<void> scheduleComplexTask() async {
await NativeWorkManager.enqueue(
taskId: 'complex-task',
trigger: TaskTrigger.oneTime(),
worker: DartWorker(
callbackId: 'complexTask',
input: {'userId': 123, 'action': 'process'},
autoDispose: true, // Dispose Flutter Engine after task
),
);
}Run your app and look for these logs:
✅ Periodic sync scheduled!
[NativeWorkManager] Task enqueued: periodic-sync
[NativeWorkManager] Task started: periodic-sync
[NativeWorkManager] Task completed: periodic-sync (success)
-
Android:
# Force run background task immediately (debug only) adb shell cmd jobscheduler run -f YOUR_PACKAGE_NAME 1 -
iOS:
- Debug → Simulate Background Fetch (Xcode)
- Or wait for next scheduled execution
Add real-time monitoring to your app:
NativeWorkManager.events.listen((event) {
print('Task event: ${event.taskId} - ${event.state}');
if (event.state == TaskState.succeeded) {
print('✅ Task completed successfully!');
} else if (event.state == TaskState.failed) {
print('❌ Task failed: ${event.error}');
}
});Make tasks smarter by adding conditions:
constraints: Constraints(
requiresNetworkType: NetworkType.unmetered, // Wi-Fi only
requiresCharging: true, // Only when charging
requiresBatteryNotLow: true, // Skip if battery low
requiresDeviceIdle: true, // Android: When device idle
requiresStorageNotLow: true, // Skip if storage low
),Automate multi-step workflows:
NativeWorkManager.beginWith(
TaskRequest(
id: 'download',
worker: NativeWorker.httpDownload(/* ... */),
),
)
.then(TaskRequest(
id: 'process',
worker: DartWorker(callbackId: 'processFile'),
))
.then(TaskRequest(
id: 'upload',
worker: NativeWorker.httpUpload(/* ... */),
))
.named('photo-backup-pipeline')
.enqueue();Replace Dart workers with native workers where possible:
Before (Dart Worker - 50 MB RAM):
DartWorker(callbackId: 'httpRequest')After (Native Worker - 5 MB RAM):
NativeWorker.httpRequest(url: '...') // 10x less memory!Learn from real-world examples:
Problem: Getting initialization errors on Android.
Solutions:
- Verify minimum SDK version - Edit
android/app/build.gradle:defaultConfig { minSdk 26 // Must be 26 or higher! }
- Ensure
await NativeWorkManager.initialize()is called inmain()beforerunApp() - Clean and rebuild:
flutter clean rm -rf android/build android/app/build flutter pub get flutter build apk --debug
- Check logcat:
adb logcat -s NativeWorkmanagerPlugin
Full Android troubleshooting →
Problem: Task scheduled but never executes.
Solutions:
- Android: Verify
minSdkis 26+ inandroid/app/build.gradle - Check constraints - task may be waiting for conditions (Wi-Fi, charging, etc.)
- Check device battery optimization settings (Android)
- Verify task ID is unique
- Check logs for errors
// Debug: Remove all constraints
constraints: Constraints(), // No conditionsProblem: Task starts but fails instantly.
Solutions:
- Check logs for error messages
- Verify worker configuration (URL, file paths, etc.)
- Test API endpoint separately
- For Dart workers: Verify callback is registered and has
@pragma('vm:entry-point')
Problem: Works on Android, not on iOS.
Solutions:
- iOS requires app to be in background for 30+ minutes before first execution
- Check Info.plist permissions
- iOS has 30-second execution limit - split long tasks
- Enable background modes in Xcode (Background fetch, Background processing)
Problem: Background tasks using too much memory.
Solutions:
- Use native workers instead of Dart workers (10x improvement)
- Enable
autoDispose: truefor Dart workers - Check for memory leaks in callbacks
- Use constraints to limit concurrent tasks
// Good: Native worker (5 MB)
NativeWorker.httpSync(url: '...')
// Also good: Dart worker with autoDispose (50 MB, then released)
DartWorker(callbackId: '...', autoDispose: true)
// Bad: Dart worker without autoDispose (50 MB stays in memory)
DartWorker(callbackId: '...') // No autoDispose!You've learned how to:
- ✅ Install and initialize native_workmanager
- ✅ Schedule your first background task
- ✅ Use native workers (low memory) vs Dart workers (flexible)
- ✅ Verify tasks are running
- ✅ Troubleshoot common issues
What's Next?
- Read use case examples for real-world patterns
- Learn task chains for complex workflows
- Review production guide before deploying
Questions? Join our Discord community for help!