Skip to content

fix(apps): ghost installs — re-sync on activity restart (E3 R3)#539

Merged
rainxchzed merged 2 commits into
mainfrom
fix/e3-r3-ghost-installs
May 7, 2026
Merged

fix(apps): ghost installs — re-sync on activity restart (E3 R3)#539
rainxchzed merged 2 commits into
mainfrom
fix/e3-r3-ghost-installs

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented May 7, 2026

Fixes E3 R3 from roadmap/FREE_FEATURES.md. Survey #21: "Sometimes deleted apps are still shown as installed."

Problem

Three paths already remove orphaned rows:

  1. PackageEventReceiver.onPackageRemoved — broadcast handler
  2. SyncInstalledAppsUseCase — cold-start reconciler called from MainViewModel.init
  3. UpdateCheckWorker — periodic sync

Gap: when GitHub Store is foregrounded, the user leaves to system Settings, uninstalls an app, returns. The broadcast may be dropped (OEM background restrictions, Doze, manifest receiver instantiation race). MainViewModel.init does not re-fire on resume. Periodic worker may be hours away. Result: ghost row persists until next pull-to-refresh or cold start.

Fix

MainActivity.onRestart() fires SyncInstalledAppsUseCase on the app-scoped CoroutineScope. onRestart runs only on stop→start transitions, so it does not duplicate the cold-start sync from MainViewModel.init. The use case is idempotent (runs in withContext(IO), deletes orphaned rows in a transaction), safe under the rare race with concurrent broadcast handlers.

Acceptance criteria

  • adb shell pm uninstall <pkg> → broadcast handler removes row in foreground (already worked, no regression)
  • App backgrounded → user uninstalls externally → returns to GHS → row gone via onRestart re-sync
  • Reinstall under same package → onPackageInstalled flow handles correctly (no change)
  • Cold start unchanged — MainViewModel.init still primary sync path

Test plan

  • Manual on Android device
    • Install GHS, install some app via GHS, confirm row visible
    • adb shell pm uninstall com.example.app while GHS in foreground → row gone within 5s (broadcast path)
    • Background GHS, adb shell pm uninstall <pkg>, foreground GHS → row gone on resume (onRestart path)
    • Reinstall via GHS → row reappears correctly
  • Compile clean: :composeApp:compileDebugKotlinAndroid
  • ktlint warnings present in GithubStoreApp.kt are pre-existing, unrelated to this fix

Notes

  • Unit test for SyncInstalledAppsUseCase reconciler logic deferred — repo currently has no test scaffolding (*Test.kt absent across all modules). When test infra lands, add coverage for "row in DB without matching package" branch.

What's-new

  • Added FIXED bullet to whatsnew/17.json across 13 locales.

Summary by CodeRabbit

  • New Features

    • Ignore updates per app — silence the update badge for any app you don't want to update.
  • Bug Fixes

    • Uninstalled apps are now immediately removed from the tracked list, even if still present in system Settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack

Walkthrough

MainActivity now syncs installed apps on activity restart via an injected SyncInstalledAppsUseCase running in an appScope coroutine with error logging. Release notes for version 17 (1.9.0) are updated across 12 locales documenting per-app update ignoring and immediate removal of uninstalled apps from tracked lists.

Changes

App Sync on Activity Restart & Release Notes

Layer / File(s) Summary
DI Setup
composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/MainActivity.kt
Adds coroutine and Logger imports; injects SyncInstalledAppsUseCase and appScope fields for async sync operations.
onRestart Implementation
composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/MainActivity.kt
Overrides onRestart() to launch a coroutine that executes syncInstalledAppsUseCase(), capturing and logging any failures via Logger.w().
Release Notes v1.9.0
core/presentation/src/commonMain/composeResources/files/whatsnew/17.json, whatsnew/ar/17.json, whatsnew/bn/17.json, whatsnew/es/17.json, whatsnew/fr/17.json, whatsnew/hi/17.json, whatsnew/it/17.json, whatsnew/ja/17.json, whatsnew/ko/17.json, whatsnew/pl/17.json, whatsnew/ru/17.json, whatsnew/tr/17.json, whatsnew/zh-CN/17.json
Adds NEW section documenting per-app update badge suppression and FIXED section documenting immediate removal of uninstalled apps from tracked lists across all 12 supported locales.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Poem

🐰 On restart we hop with mighty will,
Syncing apps from your system's hill,
Uninstalled ones vanish without fuss,
Twelve tongues announce the fix for us!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: MainActivity now triggers app re-sync on activity restart to fix ghost installs when apps are uninstalled from system settings.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/e3-r3-ghost-installs

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/MainActivity.kt (1)

112-114: ⚡ Quick win

Avoid swallowing coroutine cancellation in restart sync.

runCatching captures CancellationException along with other exceptions, which interferes with structured concurrency. Rethrow CancellationException explicitly and only log non-cancellation failures:

♻️ Proposed refactor
+import kotlinx.coroutines.CancellationException
@@
     override fun onRestart() {
         super.onRestart()
         appScope.launch {
-            runCatching { syncInstalledAppsUseCase() }
-                .onFailure { Logger.w(it) { "onRestart sync failed" } }
+            try {
+                syncInstalledAppsUseCase()
+            } catch (e: CancellationException) {
+                throw e
+            } catch (t: Throwable) {
+                Logger.w(t) { "onRestart sync failed" }
+            }
         }
     }
🤖 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 `@composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/MainActivity.kt`
around lines 112 - 114, The current appScope.launch block uses runCatching {
syncInstalledAppsUseCase() } which will catch CancellationException and swallow
coroutine cancellations; change it to call syncInstalledAppsUseCase() inside a
try/catch: catch CancellationException and rethrow it immediately, and in a
separate catch (e: Throwable) call Logger.w(e) { "onRestart sync failed" } so
only non-cancellation failures are logged. Reference the appScope.launch block,
the syncInstalledAppsUseCase() call, and Logger.w usage when making the change.
🤖 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.

Nitpick comments:
In `@composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/MainActivity.kt`:
- Around line 112-114: The current appScope.launch block uses runCatching {
syncInstalledAppsUseCase() } which will catch CancellationException and swallow
coroutine cancellations; change it to call syncInstalledAppsUseCase() inside a
try/catch: catch CancellationException and rethrow it immediately, and in a
separate catch (e: Throwable) call Logger.w(e) { "onRestart sync failed" } so
only non-cancellation failures are logged. Reference the appScope.launch block,
the syncInstalledAppsUseCase() call, and Logger.w usage when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8bff02bf-1fbe-4d30-952b-ee958d8fc3d5

📥 Commits

Reviewing files that changed from the base of the PR and between 0ae1277 and fa7b9f0.

📒 Files selected for processing (14)
  • composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/MainActivity.kt
  • core/presentation/src/commonMain/composeResources/files/whatsnew/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ar/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/bn/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/es/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/fr/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/hi/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/it/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ja/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ko/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/pl/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ru/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/tr/17.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/zh-CN/17.json

@rainxchzed rainxchzed merged commit c3631f4 into main May 7, 2026
1 check passed
@rainxchzed rainxchzed deleted the fix/e3-r3-ghost-installs branch May 7, 2026 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant