hybrid_task_runner 1.1.0
hybrid_task_runner: ^1.1.0 copied to clipboard
A Flutter package implementing a Hybrid Background Strategy using AlarmManager for precision scheduling and WorkManager for reliable long-running task execution on Android.
Hybrid Task Runner #
A Flutter package for running background tasks on Android. It combines AlarmManager + WorkManager to get the best of both worlds.
Platform: Android only (iOS is not supported yet)
Why Was This Made? #
Background execution on Android is complicated. There are two main options:
- AlarmManager - High precision, can set exact times, but only runs for ~10 seconds
- WorkManager - Can run for a long time (10+ minutes), but timing is not exact as it's batched by the system
This package combines both:
- AlarmManager triggers at the precise time
- Immediately enqueues a WorkManager task
- WorkManager runs the heavy task (can run 10+ minutes)
- After completion, schedules the next alarm
So you get timing precision AND long execution duration.
Dependencies #
dependencies:
android_alarm_manager_plus: ^4.0.0
workmanager: ^0.9.0
shared_preferences: ^2.0.0
Why use these?
| Package | Purpose |
|---|---|
android_alarm_manager_plus |
Set exact alarm, wake up device, survive reboot |
workmanager |
Execute long-running task, survive process death |
shared_preferences |
Store callback handle & config across isolates |
Install #
dependencies:
hybrid_task_runner:
git:
url: https://github.com/user/hybrid_task_runner.git
Android Setup #
1. Edit android/app/src/main/AndroidManifest.xml #
Add permissions inside <manifest>:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
Add service & receivers inside <application>:
<!-- AlarmManager -->
<service
android:name="dev.fluttercommunity.plus.androidalarmmanager.AlarmService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false"/>
<receiver
android:name="dev.fluttercommunity.plus.androidalarmmanager.AlarmBroadcastReceiver"
android:exported="false"/>
<receiver
android:name="dev.fluttercommunity.plus.androidalarmmanager.RebootBroadcastReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
2. Ensure minSdkVersion >= 21 #
In android/app/build.gradle:
android {
defaultConfig {
minSdkVersion 21
}
}
Usage #
1. Create the callback function #
IMPORTANT: Must be a top-level function (outside of any class) with the @pragma('vm:entry-point') annotation.
@pragma('vm:entry-point')
Future<bool> myBackgroundTask() async {
// Do heavy work here
// Can run for 10+ minutes
await syncDataToServer();
await processLocalFiles();
return true; // return false if failed
}
Why must it be top-level? Because the callback runs in a separate isolate, not in your app's main isolate.
2. Initialize in main() #
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await HybridRunner.initialize();
runApp(MyApp());
}
3. Start the runner #
await HybridRunner.start(
callback: myBackgroundTask,
loopInterval: Duration(minutes: 15),
runImmediately: true,
taskOverlapPolicy: TaskOverlapPolicy.parallel, // optional
);
Task Overlap Policy
Controls what happens when a new task is triggered while a previous task is still running:
| Policy | Behavior |
|---|---|
TaskOverlapPolicy.replace |
Cancel the running task, start the new one (default) |
TaskOverlapPolicy.skipIfRunning |
Ignore the new task if one is already running |
TaskOverlapPolicy.parallel |
Run both tasks simultaneously, no waiting |
Example use cases:
- replace: When you only care about the latest data sync
- skipIfRunning: When tasks should never overlap (e.g., database operations)
- parallel: When each task is independent (e.g., processing different files)
4. Stop if needed #
await HybridRunner.stop();
5. Check status #
bool isRunning = await HybridRunner.isActive;
Duration? interval = await HybridRunner.loopInterval;
Multi-Task API #
For more complex scenarios, you can register multiple named tasks with independent schedules.
Register looping tasks #
// Task 1: Sync data every 15 minutes
await HybridRunner.registerTask(
name: 'syncData',
callback: syncDataTask,
interval: Duration(minutes: 15),
taskOverlapPolicy: TaskOverlapPolicy.skipIfRunning,
);
// Task 2: Process files every 30 minutes
await HybridRunner.registerTask(
name: 'processFiles',
callback: processFilesTask,
interval: Duration(minutes: 30),
taskOverlapPolicy: TaskOverlapPolicy.parallel,
);
Register one-time tasks #
One-time tasks run once and are automatically removed after execution.
// Run once after 30 minutes
await HybridRunner.registerTask(
name: 'sendReminder',
callback: sendReminderTask,
interval: Duration(minutes: 30),
isOneTime: true, // Runs once, then removed
);
// Run immediately (1 second delay)
await HybridRunner.registerTask(
name: 'initialSync',
callback: initialSyncTask,
interval: Duration(minutes: 1),
isOneTime: true,
runImmediately: true,
);
View all registered tasks #
final tasks = await HybridRunner.getRegisteredTasks();
for (final task in tasks) {
print('Task: ${task.name}');
print(' Interval: ${task.interval.inMinutes} minutes');
print(' Active: ${task.isActive}');
print(' One-time: ${task.isOneTime}');
print(' Registered: ${task.registeredAt}');
}
Stop a specific task #
final stopped = await HybridRunner.stopTask('syncData');
print('Task stopped: $stopped'); // true if found and stopped
Stop all tasks #
await HybridRunner.stopAllTasks();
How It Works #
User starts runner
↓
Schedule AlarmManager (exact time)
↓
[Device sleep / App closed]
↓
AlarmManager fires! (max 10 sec)
↓
Enqueue WorkManager task
↓
WorkManager executes callback (max 10+ min)
↓
Schedule next alarm
↓
[Loop continues...]
Limitations #
Force Close #
If the user force closes the app (from Settings > Apps > Force Stop), all alarms are cancelled. This is Android behavior, not a bug.
Workaround: This package also registers a WorkManager periodic task as backup. After ~15 minutes, the task will run again and re-schedule the alarm.
Battery Optimization #
Some vendors (Xiaomi, Oppo, Vivo, Samsung) have aggressive battery optimization that can kill background processes. Users need to whitelist the app in settings.
No Minimum Interval #
Unlike standalone WorkManager periodic tasks (which have a 15-minute minimum), the hybrid approach has no minimum interval. AlarmManager can trigger at any time, and WorkManager one-off tasks run immediately when enqueued.
The 15-minute minimum only applies to the backup periodic task - a fallback that runs if alarms are cancelled (e.g., after force close). This backup will re-schedule the alarm when it runs.
Tips #
- Test on a real device - Emulator is sometimes not accurate for background behavior
- Don't force close - Just swipe away or press back
- Whitelist from battery optimization - Important for reliability
- Short intervals work - Unlike pure WorkManager, you can use intervals < 15 minutes
API Reference #
Single-Task API (Simple) #
| Method | Description |
|---|---|
HybridRunner.initialize() |
Initialize AlarmManager and WorkManager. Call once in main(). |
HybridRunner.start({...}) |
Start a single task loop with the given callback and interval. |
HybridRunner.stop() |
Stop the single task loop. |
HybridRunner.isActive |
Check if the runner is currently active. |
HybridRunner.loopInterval |
Get the current loop interval. |
Multi-Task API (Advanced) #
| Method | Description |
|---|---|
HybridRunner.registerTask({...}) |
Register a named task (looping or one-time). |
HybridRunner.getRegisteredTasks() |
Get a list of all registered tasks. |
HybridRunner.stopTask(name) |
Stop and remove a specific task by name. |
HybridRunner.stopAllTasks() |
Stop all registered tasks. |
registerTask Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
String |
required | Unique task identifier |
callback |
Function |
required | Top-level async function |
interval |
Duration |
required | Interval (loop) or delay (one-time) |
taskOverlapPolicy |
TaskOverlapPolicy |
replace |
Overlap behavior |
runImmediately |
bool |
false |
Start immediately |
isOneTime |
bool |
false |
Run once then auto-remove |
Classes & Enums #
| Type | Description |
|---|---|
TaskOverlapPolicy |
Enum: replace, skipIfRunning, parallel |
RegisteredTask |
Task model with: name, interval, isActive, isOneTime, registeredAt |
License #
MIT