feat(android): Opt-in expedited WorkManager execution for background callbacks - #435
feat(android): Opt-in expedited WorkManager execution for background callbacks#435galacticgibbon wants to merge 2 commits into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
WalkthroughChangesThe Android background-work flow now carries an optional expedited flag from broadcast creation through the receiver to WorkManager, which configures expedited execution with a non-expedited quota fallback. Expedited background work
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant BroadcastCreator
participant HomeWidgetBackgroundReceiver
participant HomeWidgetBackgroundWorker
participant WorkManager
BroadcastCreator->>HomeWidgetBackgroundReceiver: Send broadcast with EXTRA_IS_EXPEDITED
HomeWidgetBackgroundReceiver->>HomeWidgetBackgroundWorker: enqueueWork(expedited)
HomeWidgetBackgroundWorker->>WorkManager: Enqueue expedited or regular work
WorkManager-->>HomeWidgetBackgroundWorker: Fall back when expedited quota is unavailable
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/home_widget/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetBackgroundWorker.kt`:
- Around line 121-123: Add foreground-info support to HomeWidgetBackgroundWorker
by implementing getForegroundInfo() with a valid notification and foreground
service type, and ensure the expedited enqueue path using setExpedited remains
compatible with the minSdkVersion 16 worker. Reuse the worker’s existing
context/configuration and notification setup where available.
In
`@packages/home_widget/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetIntent.kt`:
- Around line 58-62: Preserve the existing JVM entry points by adding explicit
old-arity overloads: in
packages/home_widget/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetIntent.kt:58-62,
add getBroadcast(Context, Uri) delegating to the defaulted implementation; in
packages/home_widget/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetBackgroundWorker.kt:115-125,
add enqueueWork(Context, Intent) delegating to the new implementation. Keep the
current default-parameter APIs unchanged.
- Around line 58-62: Update getBroadcast so the expedited value contributes to
the PendingIntent identity, using a stable distinct request code or another
identity field for expedited versus non-expedited scheduling. Preserve
EXTRA_IS_EXPEDITED and the existing intent behavior, while ensuring
FLAG_UPDATE_CURRENT cannot reuse one mode’s PendingIntent for the other.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ac30c74e-7979-4ef5-bd75-4ff61e21cd91
📒 Files selected for processing (3)
packages/home_widget/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetBackgroundReceiver.ktpackages/home_widget/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetBackgroundWorker.ktpackages/home_widget/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetIntent.kt
| if (expedited) { | ||
| workRequestBuilder.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate file and relevant symbols =="
git ls-files | rg 'HomeWidgetBackgroundWorker\.kt$|manifest|build\.gradle|gradle' || true
echo
echo "== file outline =="
ast-grep outline packages/home_widget/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetBackgroundWorker.kt --view expanded || true
echo
echo "== relevant lines =="
nl -ba packages/home_widget/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetBackgroundWorker.kt | sed -n '1,220p'
echo
echo "== manifest minSdk variants =="
fd -a 'AndroidManifest.xml|build.gradle|build.gradle.kts' packages/home_widget/android -t f | sort | while read -r f; do
echo "--- $f"
rg -n 'minSdk|targetSdk|compileSdk|<uses-sdk|manifest' "$f" || true
done
echo
echo "== WorkManager dependency versions =="
rg -n 'work-manager|androidx.work|workRuntime|workRuntimeKtx|workRuntime' packages/home_widget -S || true
echo
echo "== getForegroundInfo usages =="
rg -n 'fun getForegroundInfo|getForegroundInfo\(|setExpedited|OutOfQuotaPolicy|foreground' packages/home_widget/android/src/main/kotlin packages/home_widget/android -S || trueRepository: ABausG/home_widget
Length of output: 2372
🌐 Web query:
Android WorkManager CoroutineWorker getForegroundInfo expedited without foreground notification older Android crash documentation
💡 Result:
When using WorkManager's expedited jobs, you must implement the getForegroundInfo method in your CoroutineWorker [1][2]. Failure to do so will result in an IllegalStateException and a runtime crash when the WorkRequest is executed on Android versions prior to Android 12 (API level 31), where WorkManager falls back to using a foreground service to maintain compatibility [1][3][4]. The crash occurs because the WorkManager framework calls getForegroundInfo to obtain the necessary Notification to start the foreground service, and the default implementation in CoroutineWorker throws an exception [3][2][5]. Key Technical Requirements and Best Practices: 1. Mandatory Implementation: You must override getForegroundInfo in your CoroutineWorker if you use setExpedited [1][2]. Even if you do not explicitly start a foreground service yourself, WorkManager requires this method to manage the fallback foreground service automatically on older devices [1][3]. 2. Error Handling: Always wrap calls to setForeground in a try-catch block [1]. This is critical because foreground service execution is subject to strict OS background-start restrictions (especially on Android 12 and higher), which can lead to ForegroundServiceStartNotAllowedException [1][4][6]. 3. Foreground Service Types: If your app targets Android 14 (API level 34) or higher, you must specify the appropriate foreground service type in your manifest and when calling setForeground [7]. 4. Versioning: Update to the latest version of WorkManager to benefit from improved stability, as specific fixes for foreground service-related crashes are regularly released (e.g., version 2.10.5 addressed specific overlapping foreground worker issues) [8].
Citations:
- 1: https://developer.android.com/develop/background-work/background-tasks/persistent/getting-started/define-work
- 2: https://developer.android.com/reference/kotlin/androidx/work/CoroutineWorker
- 3: https://stackoverflow.com/questions/71389874/coroutineworker-crashes-with-no-getforegroundinfo
- 4: https://medium.com/androiddevelopers/using-workmanager-on-android-12-f7d483ca0ecb
- 5: https://developer.android.com/reference/androidx/work/CoroutineWorker
- 6: https://stackoverflow.com/questions/78750348/foregroundservicestartnotallowedexception-in-workmanager
- 7: https://developer.android.com/develop/background-work/background-tasks/persistent/how-to/long-running
- 8: https://developer.android.com/develop/background-work/services/fgs/troubleshooting
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== HomeWidgetBackgroundWorker source =="
python3 - <<'PY'
from pathlib import Path
p = Path("packages/home_widget/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetBackgroundWorker.kt")
for i, line in enumerate(p.read_text().splitlines(), 1):
print(f"{i:4d}: {line}")
PY
echo
echo "== Android build/manifest sdk info =="
for f in packages/home_widget/android/build.gradle packages/home_widget/android/src/main/AndroidManifest.xml packages/home_widget/example/android/app/build.gradle packages/home_widget/example/android/src/main/AndroidManifest.xml; do
if [ -f "$f" ]; then
echo "--- $f"
while IFS= read -r line; do
case "$line" in
*minSdk*|*targetSdk*|*compileSdk*|*applicationId*|*<uses-sdk*|*<service*|*foregroundServiceType*)
printf '%s\n' "$line" ;;
esac
done < "$f"
fi
done
echo
echo "== searches for getForegroundInfo, setForeground, expedited, OutOfQuotaPolicy, notification =="
rg -n 'getForegroundInfo|setForeground|setExpedited|OutOfQuotaPolicy|foregroundServiceType|<service|Notification|ForegroundService' packages/home_widget/android packages/home_widget/example/android -S || true
echo
echo "== Kotlin source structural search for CoroutineWorker overrides =="
python3 - <<'PY'
from pathlib import Path
import re
p=Path("packages/home_widget/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetBackgroundWorker.kt")
s=p.read_text()
print("extends:", bool(re.search(r'CoroutineWorker', s)))
for name in ['doWork', 'getForegroundInfo', 'onStopped', 'onStart', 'setForeground']:
print(f"{name}: {bool(re.search(rf'override\s+(?:suspend\s+)?fun\s+{name}\\s*\\(', s))}")
PYRepository: ABausG/home_widget
Length of output: 5312
🏁 Script executed:
#!/bin/bash
set -u
echo "== SDK and manifest configuration files =="
for f in packages/home_widget/android/build.gradle packages/home_widget/android/AndroidManifest.xml packages/home_widget/example/android/app/build.gradle packages/home_widget/example/android/AndroidManifest.xml; do
if [ -f "$f" ]; then
echo "--- $f"
grep -nE 'minSdk|targetSdk|compileSdk|applicationId|<uses-sdk|<service|foregroundServiceType|service|permission' "$f" || true
fi
done 2>/dev/null || true
echo
echo "== Android directory manifests/build files =="
find packages/home_widget packages/home_widget/example -maxdepth 4 -type f \( -name 'AndroidManifest.xml' -o -name 'build.gradle' -o -name 'build.gradle.kts' \) | sort | while IFS= read -r f; do
has_match=0
while IFS= read -r line; do
case "$line" in
*minSdk*|*targetSdk*|*compileSdk*|*applicationId*|*<uses-sdk*|*foregroundServiceType*|*<service*) has_match=1; printf '%s\n' "$line" ;;
esac
done < "$f"
[ "$has_match" -eq 0 ] || true
done 2>/dev/null || true
echo
echo "== worker override check =="
python3 - <<'PY'
from pathlib import Path
import re
p=Path("packages/home_widget/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetBackgroundWorker.kt")
s=p.read_text()
print("extends CoroutineWorker:", bool(re.search(r'CoroutineWorker', s)))
for name in ['doWork', 'getForegroundInfo', 'onStopped', 'onStart', 'setForeground']:
print(f"{name}: {bool(re.search(rf'override\s+(?:suspend\s+)?fun\s+{name}\s*\(', s))}")
start=s.find("fun enqueueWork")
print("\nexpedited section:")
print("\n".join(f"{i:4d}: {line}" for i,line in enumerate(s.splitlines(), 1) if start <= (i-1) <= start+20))
PYRepository: ABausG/home_widget
Length of output: 816
Add foreground-info support before enabling expedited workers.
HomeWidgetBackgroundWorker is a CoroutineWorker with minSdkVersion 16, but enqueueWork(..., expedited = true) only calls setExpedited(...) and does not override getForegroundInfo(). Coroutines workers in WorkManager need a valid foreground notification path on pre-API-31 devices when used as expedited work, unless the worker is refactored/scheduled outside of Android.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/home_widget/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetBackgroundWorker.kt`
around lines 121 - 123, Add foreground-info support to
HomeWidgetBackgroundWorker by implementing getForegroundInfo() with a valid
notification and foreground service type, and ensure the expedited enqueue path
using setExpedited remains compatible with the minSdkVersion 16 worker. Reuse
the worker’s existing context/configuration and notification setup where
available.
…ndingIntent request code, @jvmoverloads
Description
Follow-up to #377, where you asked for the Android changes to be split out and motivated separately — this PR contains only that change.
Problem
HomeWidgetBackgroundWorker.enqueueWork()schedules background callbacks as regularOneTimeWorkRequests. WorkManager treats these as deferrable: under Doze/battery-saver/App Standby buckets, execution of a widget interaction (e.g. tapping a checkbox on a home-screen widget) can be delayed by seconds or longer. For interactive widgets the user is watching the widget while the tap is processed, so the delay reads as the widget being broken.Change
Adds an opt-in
EXTRA_IS_EXPEDITEDboolean onHomeWidgetBackgroundIntent. When set, the work request is built withsetExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST), so taps run promptly while the app has expedited quota and degrade gracefully to normal scheduling when quota is exhausted. Default isfalse— existing behavior is unchanged unless a caller opts in.We've been running this in production in a task-journal app (Bullet) for widget check-off interactions since late 2025.
🤖 Generated with Claude Code