Skip to content

Conversation

@xsahil03x
Copy link
Member

@xsahil03x xsahil03x commented Sep 26, 2025

Submit a pull request

Fixes: FLU-258, FLU-235

Fixes: #2397

Summary by CodeRabbit

  • Bug Fixes

    • Channel details (member count, config, extra data) no longer reset on first load.
  • Refactor

    • Debounced persistence for channel state and threads to reduce redundant writes and improve startup ordering.
    • Improved offline thread loading and cleanup of debounce timers.
    • Early-return guards to skip persistence when no data present.
  • Documentation

    • Changelog updated with an Upcoming entry describing the bug fix.
  • Tests

    • Updated tests and timing to reflect the new persistence/debounce behavior.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 26, 2025

Walkthrough

Adds debounced persistence for channel state and channel threads, loads threads from offline persistence during channel initialization and retries failed messages, adds early-return guards in persistence client for empty inputs, updates tests to the new async persistence flow, and updates the changelog with a bugfix note.

Changes

Cohort / File(s) Summary
Changelog
packages/stream_chat/CHANGELOG.md
Adds an Upcoming entry noting a bug fix where ChannelState.memberCount, ChannelState.config, and ChannelState.extraData were reset on first load.
Channel debounced persistence & init flow
packages/stream_chat/lib/src/client/channel.dart
Adds debounced persistence paths for channel state and channel threads; seeds in-memory state then triggers debounced persistence; loads threads from persistence on init and retries failed messages; cancels debouncers on dispose; replaces immediate thread persistence calls with a debounced updater.
Persistence safeguards
packages/stream_chat/lib/src/db/chat_persistence_client.dart
Adds early-return guards in updateChannelThreads and updateChannelStates to skip processing when inputs are empty; retains existing behavior for non-empty inputs.
Tests
packages/stream_chat/test/src/client/client_test.dart
Tests updated to reflect new persistence/thread load behavior: getChannelThreads now returns channel→messages map; replaced getChannelStateByCid expectations with updateChannelState/updateChannelThreads verifications; increased async delays to accommodate debounced persistence.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor App
  participant Channel
  participant Persistence as PersistenceClient
  participant Storage as OfflineStorage

  Note over App,Channel: Channel initialization with persisted threads (new flow)
  App->>Channel: initialize(cid)
  Channel->>Channel: seed in-memory ChannelState
  Channel-->>Channel: debounce(triggerPersistChannelState)
  Channel->>Persistence: getChannelThreads([cid])
  Persistence->>Storage: read threads for cid
  Storage-->>Persistence: threads
  Persistence-->>Channel: threads
  Channel-->>Channel: debounce(triggerPersistChannelThreads)
  Channel->>Channel: retryFailedMessages()
  App->>Channel: dispose()
  Channel->>Channel: cancel debouncers
Loading
sequenceDiagram
  autonumber
  participant Channel
  participant Persistence as PersistenceClient
  participant Storage as OfflineStorage

  Note over Channel: Debounced thread update path (on thread changes)
  Channel->>Channel: onThreadsChanged(cid, threads)
  Channel-->>Channel: debounce(updateChannelThreadsPersistence)
  Channel->>Persistence: updateChannelThreads(cid, threads)
  alt threads empty
    Persistence-->>Channel: return early (no-op)
  else threads non-empty
    Persistence->>Storage: persist flattened messages
    Storage-->>Persistence: ack
    Persistence-->>Channel: done
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • renefloor
  • Brazol

Poem

I thump my paws on tidy threads,
I debounce the noise in sleepy heads,
Seed the state, then gently store,
Retry the hops that failed before.
Hop—persist—then nap once more. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Linked Issues Check ⚠️ Warning The proposed changes enhance debounced persistence flows and add guards for empty inputs but do not implement merging of extraData (for #2397), proper loading of ChannelPermission on first load (#258), or restoration of member count and metadata (#235), so the PR does not satisfy the linked issue objectives. Implement logic to merge incoming channel state with existing extraData to preserve fields per issue #2397, ensure ChannelPermission is loaded on first load for FLU-258, and restore member count and metadata on initialization for FLU-235.
Out of Scope Changes Check ⚠️ Warning The PR includes additions of debounced persistence mechanisms for channel threads, early-return guards in the persistence client, and test delay adjustments which are beyond the specified objectives for extraData preservation, channel permission loading, and member count fixes. Consider extracting the debounced thread persistence refactor and test timing changes into separate feature branches and focus this PR solely on addressing the linked issues relating to extraData merging, channel permission initialization, and member count restoration.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly highlights the main change of improving persistence updates for channel state and threads and correctly references the scope of the modifications without extraneous details.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/llc-persistence-channel-state

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
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 008bece and 79303f2.

📒 Files selected for processing (3)
  • packages/stream_chat/CHANGELOG.md (1 hunks)
  • packages/stream_chat/lib/src/client/channel.dart (5 hunks)
  • packages/stream_chat/lib/src/db/chat_persistence_client.dart (2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-08T14:27:59.621Z
Learnt from: xsahil03x
PR: GetStream/stream-chat-flutter#2348
File: packages/stream_chat_flutter_core/lib/src/stream_channel.dart:383-406
Timestamp: 2025-08-08T14:27:59.621Z
Learning: In stream_chat_flutter_core/lib/src/stream_channel.dart, threads (replies) do not support around-anchor loading. Thread replies are fetched as: initial latest page via StreamChannelState.getReplies(), and further pagination via StreamChannelState.queryReplies(parentId, direction: top|bottom). Anchored loads apply only to channel messages, not to threads.

Applied to files:

  • packages/stream_chat/lib/src/client/channel.dart
  • packages/stream_chat/lib/src/db/chat_persistence_client.dart
📚 Learning: 2025-09-25T08:19:01.469Z
Learnt from: xsahil03x
PR: GetStream/stream-chat-flutter#2394
File: packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart:82-92
Timestamp: 2025-09-25T08:19:01.469Z
Learning: In the Stream Chat Flutter library, when deleting a message that hasn't been sent to the server yet (message.remoteCreatedAt == null) or is bounced with error, the _deleteMessage method in channel.dart automatically handles deletion locally via _deleteLocalMessage without making API calls, preventing 404 errors and deletingFailed states.

Applied to files:

  • packages/stream_chat/lib/src/client/channel.dart
  • packages/stream_chat/lib/src/db/chat_persistence_client.dart
📚 Learning: 2025-09-25T08:19:01.469Z
Learnt from: xsahil03x
PR: GetStream/stream-chat-flutter#2394
File: packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart:82-92
Timestamp: 2025-09-25T08:19:01.469Z
Learning: In the Stream Chat Flutter library, when deleting a message with MessageSendingStatus.failed or MessageSendingStatus.failed_update status, the _deleteMessage method in channel.dart automatically handles deletion locally via _deleteLocalMessage without making API calls, preventing 404 errors and deletingFailed states.

Applied to files:

  • packages/stream_chat/lib/src/client/channel.dart
  • packages/stream_chat/lib/src/db/chat_persistence_client.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: build (android)
  • GitHub Check: build (ios)
  • GitHub Check: analyze
  • GitHub Check: format
  • GitHub Check: test
  • GitHub Check: stream_chat_localizations
  • GitHub Check: stream_chat
  • GitHub Check: stream_chat_flutter
  • GitHub Check: stream_chat_flutter_core
  • GitHub Check: stream_chat_persistence

Copy link
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: 0

Caution

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

⚠️ Outside diff range comments (1)
packages/stream_chat/test/src/client/client_test.dart (1)

3655-3658: Fix persistence mock so the “same user” path doesn’t throw

The new guard in StreamChatClient.connectUser checks the user already associated with chatPersistenceClient. In this test we instantiate a fresh MockPersistenceClient, but we never program it to report the user passed via openPersistenceConnection(user). As a result the guard believes a different user is attached and throws, matching the failure we’re seeing in CI (StreamChatError: 'Chat persistence client is already connected to a different user…' in the PersistenceConnectionTests connectUser stage).

Please update the mock setup in this test to keep the connected-user metadata in sync with the user used here (e.g. stub the getter the client relies on, or have the connectUser stub capture the provided user ID). Once the mock mirrors the real persistence client’s behavior, this expectation should pass again.

🧹 Nitpick comments (1)
packages/stream_chat/test/src/client/client_test.dart (1)

11-13: Drop the stray abc helper

This top-level helper isn’t referenced anywhere, adds noise to the test suite, and can be safely removed.

-void abc(String abc) {
-  print(abc);
-}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 79303f2 and 5333ff1.

📒 Files selected for processing (1)
  • packages/stream_chat/test/src/client/client_test.dart (7 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-08T14:27:59.621Z
Learnt from: xsahil03x
PR: GetStream/stream-chat-flutter#2348
File: packages/stream_chat_flutter_core/lib/src/stream_channel.dart:383-406
Timestamp: 2025-08-08T14:27:59.621Z
Learning: In stream_chat_flutter_core/lib/src/stream_channel.dart, threads (replies) do not support around-anchor loading. Thread replies are fetched as: initial latest page via StreamChannelState.getReplies(), and further pagination via StreamChannelState.queryReplies(parentId, direction: top|bottom). Anchored loads apply only to channel messages, not to threads.

Applied to files:

  • packages/stream_chat/test/src/client/client_test.dart
🪛 GitHub Actions: legacy_version_analyze
packages/stream_chat/test/src/client/client_test.dart

[error] 3669-3669: Test failed: PersistenceConnectionTests connectUser threw StreamChatError: 'Chat persistence client is already connected to a different user, please close the connection before connecting a new one.'

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: stream_chat_flutter_core
  • GitHub Check: build (android)
  • GitHub Check: build (ios)
  • GitHub Check: stream_chat_localizations
  • GitHub Check: analyze
  • GitHub Check: test
  • GitHub Check: stream_chat_flutter
  • GitHub Check: stream_chat_persistence
  • GitHub Check: stream_chat

Copy link
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: 0

🧹 Nitpick comments (1)
packages/stream_chat/test/src/client/client_test.dart (1)

668-669: Avoid 1 s sleeps in the tests.

Bumping the wait to 1050 ms makes these specs noticeably slower and still leaves them at the mercy of scheduler jitter. Could we drive the debounced writer deterministically instead (e.g. inject a shorter debounce duration for tests or tick the clock with fakeAsync) so we don’t depend on wall‑clock timing here?

Also applies to: 749-749

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5333ff1 and 6bf2d9a.

📒 Files selected for processing (1)
  • packages/stream_chat/test/src/client/client_test.dart (6 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-08T14:27:59.621Z
Learnt from: xsahil03x
PR: GetStream/stream-chat-flutter#2348
File: packages/stream_chat_flutter_core/lib/src/stream_channel.dart:383-406
Timestamp: 2025-08-08T14:27:59.621Z
Learning: In stream_chat_flutter_core/lib/src/stream_channel.dart, threads (replies) do not support around-anchor loading. Thread replies are fetched as: initial latest page via StreamChannelState.getReplies(), and further pagination via StreamChannelState.queryReplies(parentId, direction: top|bottom). Anchored loads apply only to channel messages, not to threads.

Applied to files:

  • packages/stream_chat/test/src/client/client_test.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: build (android)
  • GitHub Check: analyze
  • GitHub Check: build (ios)
  • GitHub Check: test
  • GitHub Check: stream_chat_flutter_core
  • GitHub Check: stream_chat_localizations
  • GitHub Check: stream_chat_flutter
  • GitHub Check: stream_chat_persistence
  • GitHub Check: analyze_legacy_versions

Copy link
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: 0

🧹 Nitpick comments (1)
packages/stream_chat/test/src/client/client_test.dart (1)

668-669: Replace the hardcoded debounce wait with untilCalled

Sleeping for 1.05 s just to let the debounced persistence flush makes the test slow and still timing‑sensitive. mocktail already exposes await untilCalled(() => persistence.updateChannelState(any()));, so we can wait deterministically for the interaction (and likewise in the sister test) instead of burning a second and hoping the timer fired.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6bf2d9a and e2c4ee5.

📒 Files selected for processing (1)
  • packages/stream_chat/test/src/client/client_test.dart (8 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-08T14:27:59.621Z
Learnt from: xsahil03x
PR: GetStream/stream-chat-flutter#2348
File: packages/stream_chat_flutter_core/lib/src/stream_channel.dart:383-406
Timestamp: 2025-08-08T14:27:59.621Z
Learning: In stream_chat_flutter_core/lib/src/stream_channel.dart, threads (replies) do not support around-anchor loading. Thread replies are fetched as: initial latest page via StreamChannelState.getReplies(), and further pagination via StreamChannelState.queryReplies(parentId, direction: top|bottom). Anchored loads apply only to channel messages, not to threads.

Applied to files:

  • packages/stream_chat/test/src/client/client_test.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: test
  • GitHub Check: build (android)
  • GitHub Check: build (ios)
  • GitHub Check: analyze
  • GitHub Check: stream_chat_flutter
  • GitHub Check: stream_chat_persistence
  • GitHub Check: analyze_legacy_versions
  • GitHub Check: stream_chat_localizations
  • GitHub Check: stream_chat_flutter_core
  • GitHub Check: stream_chat

@codecov
Copy link

codecov bot commented Sep 27, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 63.86%. Comparing base (008bece) to head (e2c4ee5).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2401      +/-   ##
==========================================
+ Coverage   63.84%   63.86%   +0.02%     
==========================================
  Files         413      413              
  Lines       25862    25865       +3     
==========================================
+ Hits        16511    16519       +8     
+ Misses       9351     9346       -5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment on lines -667 to +668
await delay(300);
await delay(1050);
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this can also be done with expectAsync(1/2/3) instead?

@xsahil03x xsahil03x merged commit 65cb290 into master Sep 30, 2025
19 checks passed
@xsahil03x xsahil03x deleted the fix/llc-persistence-channel-state branch September 30, 2025 10:20
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.

StreamChatPersistenceClient drops extraData keys (e.g. 'name') after initialLoad of StreamChannelListController

3 participants