fix: resolve stuck 'preparing to install' after self-update (#478)#482
Conversation
After GitHub Store updates itself, Android does not deliver ACTION_PACKAGE_REPLACED to the app's own receivers, leaving isPendingInstall = true permanently in the database. Changes: - GithubStoreApp: registerSelfAsInstalledApp() now detects and resolves a stale isPendingInstall flag on every cold start by querying the system PackageManager. - PackageEventReceiver: handle ACTION_MY_PACKAGE_REPLACED (the Android-sanctioned broadcast for self-updates) with a fallback to context.packageName since it carries no data URI. - AndroidManifest: add a dedicated intent-filter for MY_PACKAGE_REPLACED (no data scheme required). Closes #478 Co-Authored-By: Oz <oz-agent@warp.dev>
WalkthroughThis PR resolves the app remaining in "pending install" status after self-updates. It adds intent-filter support for ChangesSelf-Update Resolution Flow
Sequence DiagramsequenceDiagram
participant System
participant PackageEventReceiver
participant GithubStoreApp
participant PackageMonitor
participant Database
System->>PackageEventReceiver: MY_PACKAGE_REPLACED broadcast
PackageEventReceiver->>PackageEventReceiver: Extract packageName from context
PackageEventReceiver->>Database: Notify about package replacement
Note over GithubStoreApp: On next app startup
GithubStoreApp->>Database: Query installed app (isPendingInstall=true)
Database-->>GithubStoreApp: Return stale pending app
GithubStoreApp->>PackageMonitor: Fetch current package info
PackageMonitor-->>GithubStoreApp: Return installed version
GithubStoreApp->>Database: Update version & clear isPendingInstall flag
Database-->>GithubStoreApp: Confirmed
GithubStoreApp->>GithubStoreApp: resolveSelfPendingInstall() complete
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 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. Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/app/GithubStoreApp.kt (1)
217-242: ⚖️ Poor tradeoffPotential race condition with stale snapshot (low practical risk).
The
existingobject is fetched inregisterSelfAsInstalledApp()before calling this method. IfPackageEventReceiver.onPackageInstalled()concurrently updates the same row (viaMY_PACKAGE_REPLACED), therepo.updateApp(existing.copy(...))here could overwrite those changes with stale data.Both paths clear
isPendingInstall, so the primary fix works. The risk is minor field inconsistencies (e.g., if receiver updated version fields first). Given that self-update is infrequent and the next update check would correct any drift, this is low-impact.For robustness, consider using
updateInstalledVersion()(targeted column write) or re-fetching before update.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/app/GithubStoreApp.kt` around lines 217 - 242, The update in resolveSelfPendingInstall currently calls repo.updateApp(existing.copy(...)), which can overwrite concurrent changes from PackageEventReceiver.onPackageInstalled; instead either re-fetch the current row from the repository before applying changes or use a targeted column update such as repo.updateInstalledVersion(packageName, isPendingInstall = false, installedVersionName = systemInfo.versionName, installedVersionCode = systemInfo.versionCode, isUpdateAvailable = latestVersionCode > systemInfo.versionCode) so you only touch the specific fields you intend to clear/update (reference resolveSelfPendingInstall, repo.updateApp, updateInstalledVersion, and registerSelfAsInstalledApp).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In
`@composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/app/GithubStoreApp.kt`:
- Around line 217-242: The update in resolveSelfPendingInstall currently calls
repo.updateApp(existing.copy(...)), which can overwrite concurrent changes from
PackageEventReceiver.onPackageInstalled; instead either re-fetch the current row
from the repository before applying changes or use a targeted column update such
as repo.updateInstalledVersion(packageName, isPendingInstall = false,
installedVersionName = systemInfo.versionName, installedVersionCode =
systemInfo.versionCode, isUpdateAvailable = latestVersionCode >
systemInfo.versionCode) so you only touch the specific fields you intend to
clear/update (reference resolveSelfPendingInstall, repo.updateApp,
updateInstalledVersion, and registerSelfAsInstalledApp).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 195014c4-f7ed-412d-be15-186297d5e69b
📒 Files selected for processing (3)
composeApp/src/androidMain/AndroidManifest.xmlcomposeApp/src/androidMain/kotlin/zed/rainxch/githubstore/app/GithubStoreApp.ktcore/data/src/androidMain/kotlin/zed/rainxch/core/data/services/PackageEventReceiver.kt
Problem
After GitHub Store updates itself, the install status permanently shows "preparing to install" because Android does not deliver
ACTION_PACKAGE_REPLACEDto the app's own receivers. The existingregisterSelfAsInstalledApp()returned early when the DB row already existed, never clearing the staleisPendingInstall = trueflag.Fix (3-layered)
1.
GithubStoreApp.registerSelfAsInstalledApp()When the existing DB row has
isPendingInstall = true, a newresolveSelfPendingInstall()method queries the system PackageManager, updates version info, and clears the pending flag. Runs inApplication.onCreate()— the earliest startup point.2.
PackageEventReceiverAdded
ACTION_MY_PACKAGE_REPLACEDhandling — Android's dedicated broadcast for self-updates (guaranteed delivery to the updated app). SinceMY_PACKAGE_REPLACEDcarries no data URI,onReceive()falls back tocontext.packageName.3.
AndroidManifest.xmlAdded a separate
<intent-filter>forMY_PACKAGE_REPLACED(without thepackagedata scheme, since this broadcast doesn't use one).The existing
SyncInstalledAppsUseCaseremains as a third safety net.Closes #478
Warp conversation
Co-Authored-By: Oz oz-agent@warp.dev
Summary by CodeRabbit
Bug Fixes
Improvements