Skip to content

Refresh the conversation list while it is open - #6746

Merged
mahibi merged 5 commits into
masterfrom
feat/noid/conversation-list-foreground-sync
Oct 7, 2026
Merged

mahibi merged 5 commits into
masterfrom
feat/noid/conversation-list-foreground-sync

Conversation

@AndyScherzinger

@AndyScherzinger AndyScherzinger commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

Stack — review and merge bottom-up. #6744 and #6745 are merged, so #6746 now targets master directly; #6763 targets #6746 and shows its commits until that merges.

  1. #6744 — merged — modifiedSince delta sync
  2. #6745 — merged — periodic background refresh
  3. #6746 — foreground refresh loop ← you are here
  4. #6763 — prefetch marking + mobile-preload-chat capability

With the list open, a message sent from another device only shows up when a push arrives or the user leaves and comes back. This adds the 30-second refresh timer iOS has, which matters most for the people who have no working push at all — no Play Services, no UnifiedPush distributor — for whom this becomes the only freshness mechanism the list has.

What it does

  • A loop bound to the resumed lifecycle, so it cannot run behind the screen or outlive it. It waits before the first sync rather than after: onResume already fetches, and syncing on entry would fetch twice on every resume.
  • Ticks are dropped while a sync is already in flight, so the loop can never queue behind itself, and while a search is open, so the list is not swapped out from under what the user is reading.
  • Every tick is a delta sync. Without #6744 this would be a full room list every 30 seconds.

What a reviewer should push back on

  • Two guards, not four. Being offline is already handled one layer down, where the sync returns without a request; battery saver already stops the expensive half, the message catch-up. Adding either here would only duplicate a check that exists.
  • User status dots refresh every 5 minutes, not every 30 seconds. Delta syncs drop includeStatus, because with it the server returns every one-to-one room regardless of modifiedSince and most of the saving disappears. iOS keeps status fresher here; this trades that for the request volume.
  • Accessibility: the tick reuses the existing silent sync path and introduces no animation and no focus change. Worth a TalkBack pass with focus parked mid-list.

Not in this PR

  • A full refresh on delete / disinvite signaling messages, which would let the five-minute rule relax.

🚧 TODO

  • TalkBack pass: focus parked mid-list across several ticks
  • Manual: list open on device A, message from device B, appears within ~30 s

🏁 Checklist

  • ⛑️ Tests (unit and/or integration) are included or not needed
  • 🔖 Capability is checked or not needed
  • 🔙 Backport requests are created or not needed: /backport to stable-xx.x
  • 📅 Milestone is set
  • 🌸 PR title is meaningful (if it should be in the changelog: is it meaningful to users?)

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI

@AndyScherzinger AndyScherzinger added this to the 25.1.0 milestone Sep 21, 2026
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from abc0d72 to 7d19a5c Compare September 21, 2026 20:23
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-foreground-sync branch from 008bbaf to e931686 Compare September 21, 2026 20:23
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 7d19a5c to f92c673 Compare September 22, 2026 14:16
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-foreground-sync branch from e931686 to cc193bf Compare September 22, 2026 14:16
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from f92c673 to 523a0fa Compare September 22, 2026 14:52
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-foreground-sync branch from cc193bf to 4916c3c Compare September 22, 2026 14:52
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 523a0fa to 6621cf4 Compare September 23, 2026 09:21
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-foreground-sync branch from 4916c3c to 8fd1b66 Compare September 23, 2026 09:21
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 6621cf4 to 76d0e52 Compare September 23, 2026 12:26
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-foreground-sync branch from 8fd1b66 to 1c54f14 Compare September 23, 2026 12:26
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 76d0e52 to fbd79d1 Compare September 23, 2026 14:21
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-foreground-sync branch from 1c54f14 to d1cf526 Compare September 23, 2026 14:21
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from fbd79d1 to 9b7b1a6 Compare September 23, 2026 15:12
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-foreground-sync branch from d1cf526 to 61d24d9 Compare September 23, 2026 15:12
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 9b7b1a6 to d9a67e2 Compare September 24, 2026 06:09
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-foreground-sync branch from 61d24d9 to 6cbc0f4 Compare September 24, 2026 06:09
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from d9a67e2 to ae967b3 Compare September 24, 2026 08:29
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-foreground-sync branch from 6cbc0f4 to e314853 Compare September 24, 2026 08:29
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from ae967b3 to 3da4c9f Compare September 24, 2026 14:42
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-foreground-sync branch from e314853 to 79bbdf7 Compare September 24, 2026 14:43
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 3da4c9f to 9596d4e Compare September 24, 2026 16:33
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-foreground-sync branch from 79bbdf7 to d6f1aeb Compare September 24, 2026 16:33
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 9596d4e to b9aa2b8 Compare September 24, 2026 16:59
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-foreground-sync branch from d6f1aeb to f7eb4c3 Compare September 24, 2026 16:59
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 95d81d0 to 5c93aee Compare October 2, 2026 09:57
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-foreground-sync branch from f4d924e to 137d96b Compare October 2, 2026 09:57
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 5c93aee to 0b0389f Compare October 2, 2026 15:51
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-foreground-sync branch from 137d96b to 05f03bd Compare October 2, 2026 15:51
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 0b0389f to d190020 Compare October 2, 2026 16:23
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-foreground-sync branch from 05f03bd to 35d92e1 Compare October 2, 2026 16:24
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from d190020 to e74c3a1 Compare October 2, 2026 17:01
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-foreground-sync branch from 35d92e1 to 66b34e9 Compare October 2, 2026 17:01
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from e74c3a1 to 81d9079 Compare October 4, 2026 08:35
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-foreground-sync branch from 66b34e9 to 4ac0f98 Compare October 4, 2026 08:36
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 81d9079 to 40eb091 Compare October 5, 2026 22:31
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-foreground-sync branch from 4ac0f98 to dd6512e Compare October 5, 2026 22:31
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 40eb091 to 911db4c Compare October 6, 2026 05:50
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-foreground-sync branch from dd6512e to cce7913 Compare October 6, 2026 05:50
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 911db4c to ffc197a Compare October 6, 2026 08:25
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-foreground-sync branch from cce7913 to f880b1f Compare October 6, 2026 08:25
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from ffc197a to 731fcc1 Compare October 6, 2026 09:01
@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 30a49f11-45e2-4cce-b425-77843c8da028
📥 Commits

Reviewing files that changed from the base of the PR and between 5fc7b3c and 621cac5.

📒 Files selected for processing (4)
  • app/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.kt
  • app/src/main/java/com/nextcloud/talk/conversationlist/viewmodels/ConversationsListViewModel.kt
  • app/src/test/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepositoryTest.kt
  • app/src/test/java/com/nextcloud/talk/conversationlist/viewmodels/ConversationsListViewModelForegroundRefreshTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

The conversation repository adds a periodic-sync eligibility check and serializes room-list syncs by account. While the activity is resumed, it requests a refresh every 30 seconds. The view model skips refreshes during an active sync, while search is active, or when periodic synchronization is not due. New tests cover these conditions and verify that foreground refreshes do not request a full sync.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to 621ca

The refresh loop follows the documented sync cadence and stops when the list is no longer resumed. No actionable merge-blocking risk remains after normal checks.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 40 functions across 6 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: refreshing the conversation list while it is open.
Description check ✅ Passed The description explains the change, scope, behavior, reviewer considerations, TODOs, checklist, and AI involvement. The template's screenshots section is omitted, but the description is otherwise com…
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.
  • Fix all pre-merge checks with AI
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 2d765032-196b-4842-9f4c-5e625259780f
📥 Commits

Reviewing files that changed from the base of the PR and between 72ea15c and e04abd6.

📒 Files selected for processing (5)
  • app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt
  • app/src/main/java/com/nextcloud/talk/conversationlist/data/OfflineConversationsRepository.kt
  • app/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.kt
  • app/src/main/java/com/nextcloud/talk/conversationlist/viewmodels/ConversationsListViewModel.kt
  • app/src/test/java/com/nextcloud/talk/conversationlist/viewmodels/ConversationsListViewModelForegroundRefreshTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

@AndyScherzinger

Copy link
Copy Markdown
Member Author

@mahibi one thing from #6745 I would rather you decide than decide for you, since it is your call on master code rather than something this PR introduces.

In #6745 you removed the connectivity pre-check from the background sync (leave reachability of the background sync to WorkManager) and kept it in getRooms:

override fun getRooms(user: User, forceFullSync: Boolean): Job =
    scope.launch {
        if (networkMonitor.isOnline.value) {

NetworkMonitorImpl requires NET_CAPABILITY_VALIDATED, which a captive portal or a network that has not finished validating does not have. On the background path that no longer matters. On this one it does, because this PR turns getRooms into something that runs every 30 seconds for as long as the list is open: on such a network the list then silently refreshes nothing, for as long as the user looks at it, and nothing is logged or shown either, since the request is never attempted.

The same reasoning you applied to the worker would say to attempt the request and report the failure, which also gets a sync error onto syncErrorFlow for an empty cache instead of nothing at all. The argument against is that it spends a request on a network we have reason to believe is unusable, every 30 seconds.

I have left it alone because it predates this PR and because you made the opposite choice for the foreground path deliberately. Happy to change it here, in a separate PR, or not at all — whichever you prefer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ff4db2f9-5980-4ac4-90e4-5de13690736d
📥 Commits

Reviewing files that changed from the base of the PR and between e04abd6 and 5fc7b3c.

📒 Files selected for processing (2)
  • app/src/main/java/com/nextcloud/talk/conversationlist/viewmodels/ConversationsListViewModel.kt
  • app/src/test/java/com/nextcloud/talk/conversationlist/viewmodels/ConversationsListViewModelForegroundRefreshTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Codacy

Lint

TypemasterPR
Warnings138138
Errors1815

SpotBugs

CategoryBaseNew
Bad practice77
Correctness1111
Dodgy code4040
Internationalization33
Malicious code vulnerability33
Performance88
Security1111
Total8383

@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

📱 QA build

Download app-qa-debug.apk
QR code Open the QR code for this download
Commit 621cac5
Version 6746
Available until 7 days after this build

The QA build installs alongside a released Nextcloud app, so you can keep
using your existing install while testing.

Downloading the file requires a GitHub account, so open this link on the
device you want to test on, or transfer the APK to it.

With the list on screen, a message sent from another device shows up
only when a push arrives or the user leaves the screen and comes back.
That gap matters most for the people who have no working push at all -
no Play Services, no UnifiedPush distributor - for whom nothing else
refreshes the list while they are looking at it.

Re-sync it every thirty seconds instead, matching the iOS timer. The
loop is bound to the resumed lifecycle, so it cannot run behind the
screen or outlive it, and it waits before the first sync rather than
after: onResume already fetches, and syncing on entering the loop would
fetch twice every time the screen is resumed.

A tick is dropped while a sync is already in flight, so the loop can
never queue behind itself, and while a search is open, where swapping
the list out would move what the user is reading. Being offline and
battery saver are deliberately not checked here: the first is already
handled where the sync would make its request, and the second already
stops the expensive half, the message catch-up it triggers.

Every tick asks only for what changed, which is what makes a timer this
short affordable at all.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
The loop fires whatever else is going on, so what decides whether the
list stays usable is when a tick is dropped: one that is not dropped
while a sync is running queues requests behind each other, and one that
is not dropped during a search swaps the list out from under what the
user is reading.

The two cases pin each other down. A tick during an in-flight sync must
leave the call count at one, and a tick after the previous sync finished
must take it to two, so neither dropping every tick nor dropping none of
them passes both.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
@mahibi

mahibi commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator

@mahibi one thing from #6745 I would rather you decide than decide for you, since it is your call on master code rather than something this PR introduces.

In #6745 you removed the connectivity pre-check from the background sync (leave reachability of the background sync to WorkManager) and kept it in getRooms:

override fun getRooms(user: User, forceFullSync: Boolean): Job =
    scope.launch {
        if (networkMonitor.isOnline.value) {

NetworkMonitorImpl requires NET_CAPABILITY_VALIDATED, which a captive portal or a network that has not finished validating does not have. On the background path that no longer matters. On this one it does, because this PR turns getRooms into something that runs every 30 seconds for as long as the list is open: on such a network the list then silently refreshes nothing, for as long as the user looks at it, and nothing is logged or shown either, since the request is never attempted.

The same reasoning you applied to the worker would say to attempt the request and report the failure, which also gets a sync error onto syncErrorFlow for an empty cache instead of nothing at all. The argument against is that it spends a request on a network we have reason to believe is unusable, every 30 seconds.

I have left it alone because it predates this PR and because you made the opposite choice for the foreground path deliberately. Happy to change it here, in a separate PR, or not at all — whichever you prefer.

mahibi added 3 commits October 7, 2026 16:04
getRooms cleared the loading flag when its own job finished, even if
another sync started meanwhile was still running. With resume, pull to
refresh and the foreground refresh tick all starting syncs, they now
overlap more often, and two things went wrong when they did:

- pull to refresh stopped spinning when the older sync finished, while
  the forced full sync it started was still running, so a conversation
  left elsewhere could stay visible after the indicator was gone
- the next refresh tick saw no sync in flight and started another one
  alongside the one still running

Count the syncs in flight and clear the flag only when the last one
completes. getRooms is also called from a background event subscriber,
so the count and the flag update are guarded by a lock.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
Each room list sync reads the stored conversations, merges its response
into them and writes the result, and a full sync deletes what its
response no longer has. Nothing kept two syncs of the same account from
running at once, and they are started from several places: the screen,
pull to refresh, the foreground refresh tick and the background worker.

Interleaved, they undo each other. A delta sync that is still running
when a pull to refresh starts a full sync writes its response after the
full sync has deleted a conversation the user left elsewhere, and puts
it back until the next full sync. An older response can likewise
overwrite newer state. The refresh tick only avoids starting a sync
while another one runs; the other callers do not check.

Hold a per-account lock for the whole sync, including the choice of
modifiedSince, so a waiting sync builds on what the previous one stored.
Accounts do not wait for each other. Waiting for the lock counts against
the worker's time budget, and a failed read of the stored sync state is
now handled as a failed sync instead of escaping the sync's coroutine.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…nning sync

refreshRoomsIfIdle checked for a running room list sync and an open
search first, and only then read from storage whether a sync was due.
That read suspends, so a sync or a search started while it ran went
unnoticed and the tick refreshed anyway: a redundant request, or a list
refreshed under a search the skip was meant to protect.

Read the cadence first, so nothing suspends between the checks and the
call to getRooms. The read now also runs on ticks that are then skipped,
which is one small storage read every thirty seconds.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3. to review Waiting for reviews AI assisted

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants