Skip to content

Stabilize watchlist paging during sync updates - #129

Merged
anod merged 2 commits into
masterfrom
fix/watchlist-recent-paging-overlap
Aug 8, 2026
Merged

Stabilize watchlist paging during sync updates#129
anod merged 2 commits into
masterfrom
fix/watchlist-recent-paging-overlap

Conversation

@anod

@anod anod commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • Fix the duplicate Lazy key crash by making each WatchListPagingSource generation page over a stable app row-id snapshot
  • Prevent sync/status updates from moving the same app across OFFSET windows while Paging appends more pages
  • Keep recently-discovered ordering valid for row snapshots and add regression coverage for rows moving across page offsets

Crash investigation

Crashlytics logs for the sample event showed manual sync running while the watchlist was paging. During sync, apps including Chrome were marked as viewed, changing app status and reordering the STATUS DESC list between an initial 60-item load and later appends. That can emit the same app row in multiple pages and trigger Compose duplicate Lazy keys.

Validation

  • ./gradlew :app:assembleDebug
  • ./gradlew :app:testDebugUnitTest
  • ./gradlew :app:lintDebug
  • ./gradlew ktlintCheck

Copilot AI lite review requested due to automatic review settings June 10, 2026 19:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the app-list SQL used by watchlist-style paging to ensure each app_list row joins to at most one changelog row per (app_id, version_code), preventing duplicate emitted rows that can trigger Compose Lazy key collisions when legacy/invalid changelog duplicates exist.

Changes:

  • Update AppListTable.Queries.createAppsListQuery() to join via a derived “latest changelog row per (app_id, code)” subquery, then join back to changelog by _id.
  • Add a regression test that simulates legacy duplicate changelog rows (by dropping the unique index) and asserts the paging result does not duplicate apps.
  • Update query-shape assertions in AppListTableQueriesTest to reflect the new join structure.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
app/src/main/java/com/anod/appwatcher/database/AppListTable.kt Changes the dynamic app-list query to join changelog through a “latest per version” derived table to prevent duplicated app rows.
app/src/test/java/com/anod/appwatcher/database/AppListTableQueriesTest.kt Updates assertions to validate the new derived-table join shape.
app/src/test/java/com/anod/appwatcher/watchlist/WatchListPagingSourceRoomTest.kt Adds a regression test covering duplicate changelog versions and a helper to insert legacy-duplicate data.

Comment thread app/src/main/java/com/anod/appwatcher/database/AppListTable.kt
@anod
anod force-pushed the fix/watchlist-recent-paging-overlap branch from b5facaf to a47be1e Compare June 10, 2026 19:38
@anod anod changed the title Prevent duplicate watchlist rows from changelog joins Stabilize watchlist paging during sync updates Jun 10, 2026
@anod
anod force-pushed the fix/watchlist-recent-paging-overlap branch from a47be1e to f62c7ba Compare June 11, 2026 07:38
Copilot AI review requested due to automatic review settings June 11, 2026 07:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread app/src/main/java/com/anod/appwatcher/watchlist/WatchListPagingSource.kt Outdated
Comment thread app/src/main/java/com/anod/appwatcher/watchlist/WatchListPagingSource.kt Outdated
@anod
anod force-pushed the fix/watchlist-recent-paging-overlap branch from f62c7ba to 671138a Compare June 11, 2026 08:10
Capture a stable app row-id snapshot per WatchListPagingSource generation and page by those row IDs so status changes during sync cannot move the same app across offset windows and emit duplicate Lazy keys.

Add regression coverage for rows moving across paging offsets while keeping recently-discovered row snapshot ordering valid.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@anod
anod force-pushed the fix/watchlist-recent-paging-overlap branch from 671138a to de06f77 Compare June 11, 2026 08:57
Copilot AI review requested due to automatic review settings June 11, 2026 08:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 8 changed files in this pull request and generated 1 comment.

Comment thread app/src/main/java/com/anod/appwatcher/watchlist/WatchListPagingSource.kt Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 26db8098-5a61-4c72-ab65-a5aa15e79972
Copilot AI review requested due to automatic review settings August 8, 2026 07:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (1)

app/src/main/java/com/anod/appwatcher/watchlist/WatchListPagingSource.kt:116

  • nextKey calculation still uses loadedDataSize < limit (via calculateKeys), so when snapshot.rows.size is an exact multiple of limit the paging source will advertise an extra append key and then return an empty page at the end. Since you now have the full snapshot size, you can stop precisely when offset + pageRows.size >= snapshot.rows.size to avoid the redundant empty load.
        val (prevKey, nextKey) = calculateKeys(
            key = params.key,
            offset = offset,
            loadSize = params.loadSize,
            loadedDataSize = pageRows.size,

@anod
anod merged commit 9bff8b7 into master Aug 8, 2026
4 checks passed
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.

3 participants