Skip to content

fix(apps): re-check on Apps screen resume (#514)#573

Merged
rainxchzed merged 3 commits into
mainfrom
fix/sync-on-apps-resume
May 11, 2026
Merged

fix(apps): re-check on Apps screen resume (#514)#573
rainxchzed merged 3 commits into
mainfrom
fix/sync-on-apps-resume

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented May 11, 2026

Apps row could stay stuck at the old version when an external install (file-manager sideload, update via another app) landed while GHS was background-killed by an aggressive OEM ROM — PackageEventReceiver never received PACKAGE_REPLACED, and SyncInstalledAppsUseCase only ran on cold start (hasLoadedInitialData flag, once per VM lifetime). Result: GHS kept prompting for a version the user already installed (NextPlayer case in #514).

Wire AppsRoot to fire AppsAction.OnLifecycleResume on ON_RESUME. VM routes through autoCheckForUpdatesIfNeeded which honors the existing 30-min cooldown — so rapid resumes don't burn GitHub rate limits, but the first resume after a missed external install catches the drift. Closes #514.

Summary by CodeRabbit

  • New Features

    • Apps screen now triggers a lifecycle-driven refresh when you return to it so newly installed apps are detected promptly, while respecting a 30-minute cooldown to avoid excessive checks.
  • Bug Fixes

    • Update-check indicators and "last checked" timestamps now update immediately when a check starts, providing clearer feedback during refresh.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 11, 2026

Walkthrough

Adds OnLifecycleResume action dispatched from AppsRoot on ON_RESUME; AppsViewModel invokes autoCheckForUpdatesIfNeeded(). checkAllForUpdates() now stamps lastAutoCheckTimestamp and sets isCheckingForUpdates at coroutine start; lastCheckedTimestamp still advances only after successful work.

Changes

Lifecycle-Driven Update Mechanism

Layer / File(s) Summary
Action Definition
feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsAction.kt
New OnLifecycleResume data object added to AppsAction sealed class to represent resume lifecycle events.
Imports / Compose Setup
feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsRoot.kt
Added Compose runtime and Android lifecycle imports (DisposableEffect, Lifecycle, LifecycleEventObserver, LocalLifecycleOwner).
Lifecycle Event Dispatch
feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsRoot.kt
DisposableEffect registers a LifecycleEventObserver that dispatches OnLifecycleResume to the ViewModel on ON_RESUME and unregisters on dispose.
Immediate State Stamp
feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsViewModel.kt
checkAllForUpdates() sets lastAutoCheckTimestamp and _state.isCheckingForUpdates=true at coroutine start; _state.lastCheckedTimestamp is only updated after successful sync/check.
Action Handler
feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsViewModel.kt
onAction gains OnLifecycleResume branch which calls autoCheckForUpdatesIfNeeded().

Sequence Diagram

sequenceDiagram
  participant Lifecycle
  participant AppsRoot
  participant LifecycleObserver
  participant AppsViewModel
  participant UpdateCheck
  Lifecycle->>AppsRoot: composition starts / provide LocalLifecycleOwner
  AppsRoot->>LifecycleObserver: register DisposableEffect observer
  Lifecycle->>LifecycleObserver: ON_RESUME event
  LifecycleObserver->>AppsViewModel: onAction(OnLifecycleResume)
  AppsViewModel->>UpdateCheck: autoCheckForUpdatesIfNeeded()
  UpdateCheck->>UpdateCheck: check cooldown (lastAutoCheckTimestamp)
  UpdateCheck->>UpdateCheck: trigger checkAllForUpdates() if allowed
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related PRs

Poem

🐰 When the screen returns I nudge with care,
A gentle stamp to see what's changed out there.
Not frantic fetch, but timed and neat,
I hop, I check, then rest my feet.
Tiny ears alert, the store stays fair.

🚥 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: adding a re-check mechanism when resuming the Apps screen, which directly addresses the issue of missed external app installations.
Linked Issues check ✅ Passed Changes implement the core requirement from #514: detecting missed external app installations via lifecycle-driven re-checks with cooldown protection, preventing false update prompts after successful installs.
Out of Scope Changes check ✅ Passed All changes are within scope: lifecycle observer in AppsRoot, OnLifecycleResume action in AppsAction, and ViewModel logic to handle resume via cooldown-gated check. No unrelated modifications detected.

✏️ 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/sync-on-apps-resume

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.

Actionable comments posted: 1

🤖 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
`@feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsViewModel.kt`:
- Around line 301-303: autoCheckForUpdatesIfNeeded() can start multiple
concurrent checkAllForUpdates() runs when resumes happen quickly because
lastAutoCheckTimestamp is only updated after completion; add a single-inflight
guard (e.g., an atomic boolean or mutex) inside autoCheckForUpdatesIfNeeded() or
around the call site so that if a check is already running it returns early, and
ensure the guard is cleared when the running check completes or fails; reference
AppsAction.OnLifecycleResume, autoCheckForUpdatesIfNeeded(),
checkAllForUpdates(), and lastAutoCheckTimestamp when adding the guard.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fefdd984-0782-4d13-bc21-c4cd946c7335

📥 Commits

Reviewing files that changed from the base of the PR and between ae8379b and 4061cd5.

📒 Files selected for processing (3)
  • feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsAction.kt
  • feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsRoot.kt
  • feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsViewModel.kt

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 11, 2026

Greptile Summary

  • Adds OnLifecycleResume action wired to ON_RESUME via a DisposableEffect in AppsRoot, routing through the existing autoCheckForUpdatesIfNeeded() cooldown gate to catch installs missed when GHS was background-killed by an OEM ROM.
  • Moves lastAutoCheckTimestamp stamping to the very start of the checkAllForUpdates() coroutine body (inflight guard), while keeping lastCheckedTimestamp (the UI indicator) success-only — matching refresh() semantics and preventing rate-limit hammering on rapid resumes.
  • Both previously-flagged concerns (fully-qualified type names and lastCheckedTimestamp optimistic update) appear addressed in the current revision.

Confidence Score: 5/5

Safe to merge — changes are targeted, well-guarded by the existing 30-minute cooldown, and both previously-flagged issues appear resolved.

No new P1 or P0 issues found. The inflight guard (stamping lastAutoCheckTimestamp before network calls) correctly prevents concurrent duplicate checks from rapid resumes. lastCheckedTimestamp (UI indicator) is still success-only, consistent with refresh(). Imports are present, observer cleanup is correct.

No files require special attention.

Important Files Changed

Filename Overview
feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsAction.kt Adds OnLifecycleResume data object with a clear comment explaining the broadcast-miss scenario it solves; no issues found.
feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsRoot.kt Adds a DisposableEffect(lifecycleOwner) block that registers a LifecycleEventObserver for ON_RESUME; imports are correctly added, observer is properly removed in onDispose.
feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsViewModel.kt Moves lastAutoCheckTimestamp before the try block as an inflight guard; lastCheckedTimestamp remains success-only, consistent with refresh(); OnLifecycleResume handler correctly delegates to autoCheckForUpdatesIfNeeded().

Sequence Diagram

sequenceDiagram
    participant LC as Lifecycle (ON_RESUME)
    participant AR as AppsRoot
    participant VM as AppsViewModel
    participant ACFI as autoCheckForUpdatesIfNeeded()
    participant CAU as checkAllForUpdates()
    participant Net as Network

    AR->>LC: addObserver (DisposableEffect)
    LC-->>AR: ON_RESUME event
    AR->>VM: onAction(OnLifecycleResume)
    VM->>ACFI: call
    alt "lastAutoCheckTimestamp < 30 min ago"
        ACFI-->>VM: skip (cooldown active)
    else cooldown expired
        ACFI->>CAU: call
        CAU->>CAU: stamp lastAutoCheckTimestamp (inflight guard)
        CAU->>Net: syncInstalledAppsUseCase()
        CAU->>Net: checkAllForUpdates()
        alt success
            CAU->>VM: update lastCheckedTimestamp
        else failure
            CAU->>VM: log error (lastCheckedTimestamp unchanged)
        end
    end
    AR->>LC: removeObserver (onDispose)
Loading

Reviews (2): Last reviewed commit: "fix(apps): stamp lastCheckedTimestamp on..." | Re-trigger Greptile

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsViewModel.kt (1)

206-210: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve coroutine cancellation in checkAllForUpdates().

catch (e: Exception) currently swallows CancellationException, which can break structured cancellation behavior in this ViewModel path.

Suggested fix
-            } catch (e: Exception) {
+            } catch (e: CancellationException) {
+                throw e
+            } catch (e: Exception) {
                 logger.error("Check all for updates failed: ${e.message}")
             } finally {
                 _state.update { it.copy(isCheckingForUpdates = false) }
             }
🤖 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
`@feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsViewModel.kt`
around lines 206 - 210, In checkAllForUpdates(), the broad catch (e: Exception)
swallows CancellationException and breaks structured coroutine cancellation;
change the error handling to preserve cancellation by rethrowing
CancellationException (either add a specific catch(e: CancellationException) {
throw e } before the general catch, or in the existing catch check if (e is
CancellationException) throw e) and keep logging other exceptions via
logger.error("Check all for updates failed: ${e.message}"), ensuring the finally
block that updates _state.update { it.copy(isCheckingForUpdates = false) } still
runs.
🤖 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.

Outside diff comments:
In
`@feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsViewModel.kt`:
- Around line 206-210: In checkAllForUpdates(), the broad catch (e: Exception)
swallows CancellationException and breaks structured coroutine cancellation;
change the error handling to preserve cancellation by rethrowing
CancellationException (either add a specific catch(e: CancellationException) {
throw e } before the general catch, or in the existing catch check if (e is
CancellationException) throw e) and keep logging other exceptions via
logger.error("Check all for updates failed: ${e.message}"), ensuring the finally
block that updates _state.update { it.copy(isCheckingForUpdates = false) } still
runs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 46d1947f-ed91-4285-a175-155d69b655af

📥 Commits

Reviewing files that changed from the base of the PR and between 20b58f5 and 2d40da2.

📒 Files selected for processing (2)
  • feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsRoot.kt
  • feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsViewModel.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsRoot.kt

@rainxchzed rainxchzed merged commit d0e3efe into main May 11, 2026
1 check passed
@rainxchzed rainxchzed deleted the fix/sync-on-apps-resume branch May 11, 2026 11:00
This was referenced May 11, 2026
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.

store更新

1 participant