Skip to content

fix(chat): join the room on resume, as the counterpart of leaving on pause - #6909

Merged
mahibi merged 5 commits into
nextcloud:masterfrom
ToteMeiSter:fix/chat-rejoin-on-resume
Oct 19, 2026
Merged

mahibi merged 5 commits into
nextcloud:masterfrom
ToteMeiSter:fix/chat-rejoin-on-resume

Conversation

@ToteMeiSter

@ToteMeiSter ToteMeiSter commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Problem

After leaving a chat the app sometimes joins the room again 35-300 ms later (DELETE .../participants/active, then POST .../participants/active). The session stays in the room until the next leave, minutes later. While its last ping is fresh the server treats the user as online and sends no push for new messages.

The chat leaves the room in onPause, but joined it from the first capabilities state after a reset to the initial load. ChatViewModel.leaveRoom and every onSaveInstanceState reset getCapabilitiesViewState, so the next refresh of the conversation emitted GetCapabilitiesInitialLoadState and ChatActivity joined the room. LiveData delivers to a started observer, so a refresh between onPause and onStop joined the room right after it was left. onDestroy does not leave it. After a return from the background the join also depended on such a refresh.

Fix

  • onResume calls ensureJoined() once the first initial load of the screen is done (the conversation can be set by the message list before the capabilities are loaded). The initial load observer calls it only while the screen is resumed, for the first open. ChatViewModel.claimRoomJoin() allows one join per resume and is released in ChatViewModel.onPause, where the pending join is disposed as well.
  • leaveRoom no longer resets the capabilities state. onSaveInstanceState resets it only when isChangingConfigurations is true, where the new activity needs the initial load to build its UI; a stop without a configuration change keeps the state.
  • The initial load no longer runs on every return, so onResume repeats what depends on the current state: shared files of a new intent, focus of the input, scheduled messages, upcoming event, out-of-office status and the room refresh timer. The deletion warnings of event and ended-call rooms are not shown again on a return.

Tests

ChatViewModelRoomJoinTest uses the real ChatViewModel with mocked repositories: a refresh of the conversation after leaveRoom is not an initial load, and leave, refresh, return without a configuration change joins exactly once per resume. It also covers a configuration change. The first two tests fail on master. The wiring in ChatActivity (onResume, the initial load flag, the isChangingConfigurations check in onSaveInstanceState) is not covered by a unit test.

Known remaining issues

  • Back right after opening, while the join request is in flight: the request is disposed in onPause, no leave is sent, the session expires after about 30 s. Same as before.
  • A failed join is not retried before the next resume.
  • If the screen is left before the first capabilities load, nothing is joined or refreshed on the return until that load arrives.
  • Split screen on API 26-28: the chat joins when it gets the focus, as it leaves when it loses it.

🏁 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

🤖 AI

  • The content of this PR was partly or fully generated using AI (Claude Code, Assisted-by trailer in the commit)

Tested on

  • Huawei DEL-LX9: tested
  • Samsung SM-S926B: tested

🤖 Generated with Claude Code

ToteMeiSter and others added 4 commits October 19, 2026 14:39
…pause

The chat left the room in onPause, but joined it from the first
capabilities state after a reset to the initial load. leaveRoom and every
onSaveInstanceState reset that state, so the next refresh of the
conversation joined the room again. LiveData delivers to a started
observer, so a refresh between onPause and onStop joined the room right
after it was left. Nothing leaves it again, the session stayed in the
room, and the server sent no push while its last ping was fresh. After a
return from the background the join also depended on such a refresh.

Join from onResume once the conversation is known, and from the initial
load while the screen is resumed, at most once per resume. Stop resetting
the capabilities state in leaveRoom, and reset it in onSaveInstanceState
only when the activity is recreated for a configuration change.

The initial load no longer runs again on a return to the chat, so
onResume repeats what it did that depends on the current state: the shared
files of a new intent, the focus of the input, the scheduled messages, the
upcoming event, the out-of-office status and the room refresh timer.

Assisted-by: Claude-Code:claude-sonnet-5-5
Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
The conversation shown in the message list can be set before the
capabilities are loaded, for example when the screen was left before the
answer of a federated server. A return to the chat then joined the room and
focused the input before the initial load, and a call started by the join
read capabilities that were not set yet.

Track the first initial load of the screen and join and run the return
actions only after it. Focus the input only when its fragment is added.

Cover a configuration change in the viewmodel test and release the
viewmodel and its scope after each test.

Assisted-by: Claude-Code:claude-sonnet-5-5
Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
A return to the chat while the leave of onPause was still running joined
the room at once. The server could handle the leave after the join, and
the late leave success cleared the current room holder and cancelled the
room timer that the return had started again.

Join only after a running leave and cancel that wait on pause. Cancel the
room timer on leave success only while the leave is still wanted.

A failed first load of federated capabilities left the error state, so
every later load was an update and the chat never joined the room. Treat
the error state as a pending initial load.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
Since 8adb868 the chat leaves the signaling room and drops its session
at the start of leaveRoom, but ApplicationWideCurrentRoomHolder kept the
session until the leave success, as fd4fc9b put it there. When the
chat was closed before that success, its observer was already removed
and the holder was never cleared. A chat opened again for the room then
reused the left session, skipped the join and was not in the room for
the server, which sent pushes for its messages.

Clear the holder together with the session when the leave starts. The
leave runs only without an active call, so a late leave success no
longer resets the state of a call started in the meantime.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
@mahibi
mahibi force-pushed the fix/chat-rejoin-on-resume branch from ba69929 to 9860f32 Compare October 19, 2026 15:31
…load

A failed load of federated capabilities was treated as a pending initial
load even after the initial load was delivered. The capabilities of a
federated conversation are loaded on every refresh, so one failed refresh
made the next one an initial load again: the chat showed the deletion
warnings again, started a second room timer and replaced the input
fragment.

Track whether the initial load was delivered and treat the error as
pending only before that.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
@mahibi mahibi added this to the 25.1.0 milestone Oct 19, 2026
@mahibi
mahibi merged commit 7639432 into nextcloud:master Oct 19, 2026
14 of 17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants