Install
$ agentstack add skill-iml1s-flutter-claude-skills-flutter-background-tasks ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Flutter Background Tasks (iOS + Android)
Complete guide for configuring periodic background tasks in Flutter apps using workmanager (Android) and background_fetch (iOS).
Package Versions
dependencies:
workmanager: ^0.9.0 # Android WorkManager wrapper
background_fetch: ^1.5.0 # iOS BGTaskScheduler wrapper
Architecture Overview
Foreground App Background Isolate
┌──────────────────┐ ┌──────────────────┐
│ RevenueCat SDK │ │ SharedPreferences │
│ SubscriptionProv │──writes──► │ (cached status) │
│ │ lobster_ │ │
│ │ subscription_│──reads──► Gate │
│ │ cached │ .isSubscribed()
└──────────────────┘ └──────────────────┘
Background isolates CANNOT use:
- MethodChannel-based SDKs (RevenueCat, Firebase Auth listeners)
- EventChannel streams
- Platform views
Background isolates CAN use:
- SharedPreferences (read/write)
- HTTP requests (Dio, http)
- Firebase Core, Firestore, AppCheck (after re-init)
- Local file I/O
iOS Setup (background_fetch)
1. Info.plist (CRITICAL)
BGTaskSchedulerPermittedIdentifiers
com.transistorsoft.fetch
com.transistorsoft.customtask
UIBackgroundModes
fetch
processing
Common mistake: Forgetting com.transistorsoft.fetch causes BGAppRefreshTask to silently fail with no error logs. This is the #1 cause of iOS background tasks not executing.
2. Xcode Capabilities
In Xcode: Target > Signing & Capabilities > Background Modes:
- [x] Background fetch
- [x] Background processing
3. AppDelegate.swift
No changes needed for iOS 13+. The SDK auto-registers from Info.plist.
If using workmanager for OTHER tasks (not background_fetch):
// Only for workmanager tasks, NOT background_fetch
WorkmanagerPlugin.registerPeriodicTask(
withIdentifier: "com.example.myTask",
frequency: NSNumber(value: 24 * 60 * 60)
)
WorkmanagerPlugin.setPluginRegistrantCallback { registry in
GeneratedPluginRegistrant.register(with: registry)
}
4. Podfile
The use_frameworks! line should work for most projects. If you encounter linking errors:
# Try static linkage if dynamic causes issues
use_frameworks! :linkage => :static
Warning: Changing linkage can break other SDKs (Appodeal, AdMob). Test thoroughly.
5. iOS Constraints & Limitations
| Constraint | Value | |-----------|-------| | Minimum interval | 15 minutes (iOS enforces, ignores higher values) | | Background time limit | ~30 seconds | | Scheduling control | Apple's ML algorithm decides when to run | | After reboot | Automatically re-registered from Info.plist | | App terminated | Works if enableHeadless: true | | Must call finish | BackgroundFetch.finish(taskId) or task gets throttled | | Custom taskId prefix | Must start with com.transistorsoft. |
6. iOS Timeout Handling
Always implement timeout with 2-second margin:
// iOS background time limit is ~30 seconds
// Use 28 seconds to leave margin for cleanup
final timeout = isIOSBackground ? 28 : 540; // seconds
await Future.any([
_doWork(),
Future.delayed(Duration(seconds: timeout)).then((_) {
throw TimeoutException('Background task timed out');
}),
]);
Android Setup (WorkManager)
1. AndroidManifest.xml
...
2. build.gradle.kts
android {
compileOptions {
// Required for WorkManager + Kotlin
isCoreLibraryDesugaringEnabled = true
}
}
dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
}
3. ProGuard (if using R8 minification)
WorkManager's AAR includes consumer ProGuard rules automatically. Usually no explicit rules needed. If you see reflection errors in release builds:
# Only if needed
-keep class androidx.work.** { *; }
-dontwarn androidx.work.**
4. Android Constraints & Limitations
| Constraint | Value | |-----------|-------| | Minimum interval | 15 minutes (WorkManager enforces) | | Background time limit | ~10 minutes | | Network constraint | NetworkType.connected recommended | | After reboot | Automatic (requires RECEIVEBOOTCOMPLETED) | | App terminated | Always works (WorkManager is OS-level) | | Battery optimization | May be delayed by Doze mode | | Backoff | Exponential recommended for network tasks |
Dart Implementation
Entry Point Functions
CRITICAL: Both callback functions MUST have @pragma('vm:entry-point') to prevent tree-shaking in release builds.
// Android WorkManager callback
@pragma('vm:entry-point')
void myCallbackDispatcher() {
Workmanager().executeTask((taskName, inputData) async {
// Re-initialize plugins in background isolate
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// Do work...
return true; // success
});
}
// iOS background_fetch headless callback
@pragma('vm:entry-point')
void myHeadlessTask(HeadlessTask task) async {
final taskId = task.taskId;
final isTimeout = task.timeout;
if (isTimeout) {
BackgroundFetch.finish(taskId);
return;
}
// Re-initialize plugins
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// Do work...
BackgroundFetch.finish(taskId); // MUST call or task gets throttled
}
Scheduler Pattern
class BackgroundScheduler {
final String taskName;
final String uniqueId;
Future register({
required int intervalHours,
required Function callbackDispatcher,
Function(String)? iosCallback,
Function(HeadlessTask)? headlessTask,
}) async {
if (Platform.isAndroid) {
await Workmanager().initialize(callbackDispatcher);
await Workmanager().registerPeriodicTask(
uniqueId,
taskName,
frequency: Duration(hours: intervalHours),
constraints: Constraints(networkType: NetworkType.connected),
existingWorkPolicy: ExistingPeriodicWorkPolicy.replace,
backoffPolicy: BackoffPolicy.exponential,
initialDelay: const Duration(minutes: 1),
);
} else if (Platform.isIOS) {
await BackgroundFetch.configure(
BackgroundFetchConfig(
minimumFetchInterval: 15, // iOS minimum
stopOnTerminate: false,
enableHeadless: true,
),
iosCallback ?? (_) {},
(taskId) => BackgroundFetch.finish(taskId), // timeout handler
);
if (headlessTask != null) {
BackgroundFetch.registerHeadlessTask(headlessTask);
}
}
}
}
Subscription Gate Pattern (Background-Safe)
/// Reads cached subscription status from SharedPreferences.
/// Does NOT call any MethodChannel-based SDK.
class RevenueCatGate implements SubscriptionGate {
final AgentRepository _repo;
RevenueCatGate(this._repo);
@override
Future isSubscribed() async {
return _repo.getCachedSubscriptionStatus();
// Reads: prefs.getBool('${keyPrefix}_subscription_cached')
}
}
Foreground writes (in SubscriptionProvider):
// Write to SharedPreferences when subscription state changes
await prefs.setBool('${keyPrefix}_subscription_cached', isSubscribed);
Key consistency: The foreground write key and background read key MUST match exactly. Use the same keyPrefix pattern.
Debugging Background Tasks
iOS Simulator
Force-trigger background fetch:
# In Xcode: Debug > Simulate Background Fetch
# Or via command line:
xcrun simctl background_fetch
Android
# List WorkManager tasks
adb shell dumpsys jobscheduler | grep
# Force run immediately (for testing)
adb shell cmd jobscheduler run -f
Common Issues
| Symptom | Cause | Fix | |---------|-------|-----| | iOS: Tasks never execute | Missing com.transistorsoft.fetch in Info.plist | Add to BGTaskSchedulerPermittedIdentifiers | | iOS: Tasks stop after a while | Not calling BackgroundFetch.finish() | Always call finish, even on timeout | | iOS: Works in debug, not release | Missing @pragma('vm:entry-point') | Add pragma to both callback functions | | Android: Tasks don't survive reboot | Missing RECEIVEBOOTCOMPLETED | Add permission to AndroidManifest.xml | | Android: Release build crash | R8 removing Worker classes | Add ProGuard keep rules | | Both: SDK calls fail in background | Using MethodChannel SDK in isolate | Cache state in SharedPreferences, read in background | | Both: Subscription check fails | Cache key mismatch foreground/background | Verify keyPrefix produces same SharedPreferences key |
Audit Checklist
When reviewing a Flutter background task setup, verify:
iOS
- [ ]
com.transistorsoft.fetchin BGTaskSchedulerPermittedIdentifiers - [ ]
fetchin UIBackgroundModes - [ ]
processingin UIBackgroundModes (if using scheduleTask) - [ ] Background Modes capability enabled in Xcode
- [ ]
@pragma('vm:entry-point')on headless callback - [ ]
BackgroundFetch.finish(taskId)called in ALL paths (success + timeout) - [ ] Timeout < 30 seconds (recommend 28s)
- [ ] No MethodChannel SDK calls in background isolate
Android
- [ ]
RECEIVE_BOOT_COMPLETEDpermission in AndroidManifest.xml - [ ] Core library desugaring enabled in build.gradle
- [ ]
@pragma('vm:entry-point')on callbackDispatcher - [ ]
NetworkType.connectedconstraint for network tasks - [ ] Exponential backoff configured
- [ ] Firebase re-initialized in background isolate
- [ ] ProGuard rules if R8 minification enabled
Cross-Platform
- [ ] SharedPreferences cache key matches between foreground write and background read
- [ ] No direct RevenueCat/Firebase Auth SDK calls in background
- [ ] Timeout handling with platform-appropriate limits
- [ ] Tests for cache key consistency
Testing Strategy
Unit Tests
test('foreground/background cache key consistency', () async {
SharedPreferences.setMockInitialValues({});
final prefs = await SharedPreferences.getInstance();
final repo = AgentRepository(prefs, keyPrefix: 'myapp');
// Simulate foreground write
await prefs.setBool('myapp_subscription_cached', true);
// Background read should match
expect(repo.getCachedSubscriptionStatus(), true);
});
Integration Tests
- Build release APK/IPA
- Install on device
- Enable the background task
- Kill the app
- Wait for task to trigger (or force-trigger)
- Check logs/SharedPreferences for evidence of execution
iOS Simulator Testing
# 1. Run app on simulator
# 2. Background the app
# 3. Force fetch:
xcrun simctl background_fetch booted com.example.myapp
# 4. Check console logs in Xcode
Related skills
flutter-verify— use to verify background tasks are executing correctly on real devices and checking for runtime errors.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: ImL1s
- Source: ImL1s/flutter-claude-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.