Skip to content

Conversation

xsahil03x
Copy link
Member

@xsahil03x xsahil03x commented Sep 19, 2025

Submit a pull request

Fixes: #2356

Description of the pull request

This commit introduces two new local event types:

  • push_preference.updated: Fired when global push notification preferences are updated.
  • channel.push_preference.updated: Fired when channel-specific push notification preferences are updated.

The setPushPreferences method in the StreamChatClient now emits these events after successfully updating preferences.

Additionally:

  • The StreamChatClient now updates the currentUser.pushPreferences when a push_preference.updated event is received.
  • The ChannelController now listens for channel.push_preference.updated events and updates the channelState.pushPreferences accordingly.
  • The Event model has been updated to include pushPreference and channelPushPreference fields to carry this data.

Summary by CodeRabbit

  • New Features

    • Real-time sync for push notification preferences, including per-channel preference updates.
  • Improvements

    • Setting push preferences now waits for server confirmation and broadcasts updates so local and server state stay consistent.
  • Tests

    • Added tests covering push preference events, per-channel updates, and state propagation.
  • Documentation

    • Changelog notes immediate current-user push preference updates after setting preferences.

This commit introduces two new local event types:
- `push_preference.updated`: Fired when global push notification preferences are updated.
- `channel.push_preference.updated`: Fired when channel-specific push notification preferences are updated.

The `setPushPreferences` method in the `StreamChatClient` now emits these events after successfully updating preferences.

Additionally:
- The `StreamChatClient` now updates the `currentUser.pushPreferences` when a `push_preference.updated` event is received.
- The `ChannelController` now listens for `channel.push_preference.updated` events and updates the `channelState.pushPreferences` accordingly.
- The `Event` model has been updated to include `pushPreference` and `channelPushPreference` fields to carry this data.
Copy link
Contributor

coderabbitai bot commented Sep 19, 2025

Walkthrough

Adds push-preference support: new Event fields and event types, client.setPushPreferences now awaits API and emits user/channel push-preference events, client and channel state subscribe to those events to update user and channel push preferences, JSON serialization updated, and tests added.

Changes

Cohort / File(s) Summary
Client push preference flow
packages/stream_chat/lib/src/client/client.dart
setPushPreferences made async and awaits API; emits push_preference.updated for the user and channel.push_preference.updated per-channel; ClientState subscribes to pushPreference events to update currentUser.pushPreferences.
Channel state listener
packages/stream_chat/lib/src/client/channel.dart
Registers a private listener in ChannelClientState for EventType.channelPushPreferenceUpdated to apply channelPushPreference to channel state pushPreferences.
Event model extensions
packages/stream_chat/lib/src/core/models/event.dart, packages/stream_chat/lib/src/core/models/event.g.dart
Adds pushPreference and channelPushPreference fields to Event; updates constructor, copyWith, and JSON (from/to) handling for push_preference and channel_push_preference.
Event types
packages/stream_chat/lib/src/event_type.dart
Adds pushPreferenceUpdated = 'push_preference.updated' and channelPushPreferenceUpdated = 'channel.push_preference.updated'.
Tests: channel
packages/stream_chat/test/src/client/channel_test.dart
Adds tests that dispatch channel.push_preference.updated events and assert channel state pushPreferences updates (initial and update scenarios).
Tests: client
packages/stream_chat/test/src/client/client_test.dart
Adds tests for setPushPreferences to assert API call and emitted events, and tests handling of push_preference.updated to update currentUser.pushPreferences.
Changelog
packages/stream_chat/CHANGELOG.md
Documents fix: currentUser.pushPreferences updates immediately after setPushPreferences.

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant Client
  participant API as PushPrefsAPI
  participant Events as EventBus
  participant State as Client/ChannelState

  App->>Client: setPushPreferences(preferences)
  Client->>API: upsertPushPreferences(preferences)
  API-->>Client: UpsertPushPreferencesResponse
  Client->>Events: emit push_preference.updated (user)  %% new/changed
  Client->>Events: loop emit channel.push_preference.updated (per cid)  %% new/changed
  Events->>State: push_preference.updated -> update currentUser.pushPreferences
  Events->>State: channel.push_preference.updated -> update Channel.pushPreferences
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • renefloor
  • Brazol

Poem

I twitch my ears at prefs anew,
Events hop out and whisper true,
Users wake with settings changed,
Channels learn what’s rearranged,
A carrot-coded, async cheer—state near! 🥕🐇

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat(llc): add local events for push preference updates" is a concise single-sentence summary that accurately captures the main change (adding local push-preference events and related handling) and matches the changes and objectives in the diff. It is specific and clear enough for a teammate scanning history to understand the primary intent.
Linked Issues Check ✅ Passed The PR emits push_preference.updated and channel.push_preference.updated from setPushPreferences and adds handlers in ClientState and ChannelClientState to update currentUser.pushPreferences and channelState.pushPreferences immediately, directly addressing issue #2356; the Event model and tests were extended accordingly to carry and verify the preference data. This implementation covers both global and per-channel preferences and updates local state as required by the linked issue.
Out of Scope Changes Check ✅ Passed All production changes are limited to push-preference event types, Event model fields, emitting events from setPushPreferences, and adding listeners to update client/channel state; the only other modifications are a changelog entry and added tests (including a duplicated channel test noted in the summary). No unrelated production code changes outside the linked issue's scope were detected.
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 feat/update-current-user-push-preferences

📜 Recent 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 ae3e0a2 and 2c1471c.

📒 Files selected for processing (3)
  • packages/stream_chat/CHANGELOG.md (1 hunks)
  • packages/stream_chat/lib/src/client/channel.dart (2 hunks)
  • packages/stream_chat/test/src/client/channel_test.dart (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/stream_chat/lib/src/client/channel.dart
  • packages/stream_chat/CHANGELOG.md
  • packages/stream_chat/test/src/client/channel_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: build (ios)
  • GitHub Check: build (android)
  • GitHub Check: test
  • GitHub Check: analyze
  • GitHub Check: stream_chat_flutter_core
  • GitHub Check: stream_chat_localizations
  • GitHub Check: stream_chat_persistence
  • GitHub Check: stream_chat_flutter
  • GitHub Check: stream_chat
  • GitHub Check: analyze_legacy_versions

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

🧹 Nitpick comments (4)
packages/stream_chat/lib/src/client/channel.dart (1)

3502-3518: Avoid redundant state writes when preference hasn’t changed

Add a cheap equality/identity guard to prevent no‑op updates and unnecessary persistence writes.

   void _listenChannelPushPreferenceUpdated() {
     _subscriptions.add(
       _channel.on(EventType.channelPushPreferenceUpdated).listen(
         (event) {
           final pushPreferences = event.channelPushPreference;
           if (pushPreferences == null) return;
+          // No-op if nothing changed
+          if (identical(pushPreferences, channelState.pushPreferences) ||
+              pushPreferences == channelState.pushPreferences) {
+            return;
+          }
 
           updateChannelState(
             channelState.copyWith(
               pushPreferences: pushPreferences,
             ),
           );
         },
       ),
     );
   }
packages/stream_chat/test/src/client/client_test.dart (2)

1245-1296: Strengthen assertions: validate payload details (cid and levels), not just event types

Verifying payloads makes the test more robust and guards regressions in emitted event shape.

-      expect(
-        client.eventStream,
-        emitsInOrder([
-          isA<Event>().having(
-            (e) => e.type,
-            'push_preference.updated event',
-            EventType.pushPreferenceUpdated,
-          ),
-          isA<Event>().having(
-            (e) => e.type,
-            'channel.push_preference.updated event',
-            EventType.channelPushPreferenceUpdated,
-          ),
-        ]),
-      );
+      expect(
+        client.eventStream,
+        emitsInOrder([
+          isA<Event>()
+              .having((e) => e.type, 'type', EventType.pushPreferenceUpdated)
+              .having((e) => e.pushPreference?.chatLevel, 'chatLevel',
+                  ChatLevel.mentions),
+          isA<Event>()
+              .having((e) => e.type, 'type',
+                  EventType.channelPushPreferenceUpdated)
+              .having((e) => e.cid, 'cid', channelCid)
+              .having((e) => e.channelPushPreference?.chatLevel, 'chatLevel',
+                  ChatLevel.mentions),
+        ]),
+      );

1298-1325: Prefer waiting on currentUserStream instead of a microtask delay

Listen for the next OwnUser emission to avoid timing flakiness.

-      // Trigger the event
-      client.handleEvent(event);
-
-      // Wait for the event to get processed
-      await Future.delayed(Duration.zero);
-
-      // Should update currentUser.pushPreferences
-      final pushPreferences = client.state.currentUser?.pushPreferences;
-      expect(pushPreferences, isNotNull);
-      expect(pushPreferences?.chatLevel, ChatLevel.mentions);
-      expect(pushPreferences?.callLevel, CallLevel.all);
-      expect(pushPreferences?.disabledUntil, pushPreference.disabledUntil);
+      // Trigger the event
+      client.handleEvent(event);
+
+      // Wait for the user update and assert
+      final updatedUser = await client.state.currentUserStream
+          .where((u) => u?.pushPreferences != null)
+          .first;
+      expect(updatedUser!.pushPreferences!.chatLevel, ChatLevel.mentions);
+      expect(updatedUser.pushPreferences!.callLevel, CallLevel.all);
+      expect(updatedUser.pushPreferences!.disabledUntil,
+          pushPreference.disabledUntil);
packages/stream_chat/lib/src/client/client.dart (1)

1037-1069: Emit events + eagerly update local state; avoid Dart 3‑only pattern destructuring

  • Recommend updating state.currentUser.pushPreferences immediately (in addition to emitting the local event) to avoid UI races where callers read stale state right after await setPushPreferences(...).
  • The for (final MapEntry(:key, :value) ...) pattern requires recent Dart pattern‑matching syntax. If your min SDK isn’t high enough, prefer a classic for (final entry in ...) loop.

Suggested diff:

   Future<UpsertPushPreferencesResponse> setPushPreferences(
     List<PushPreferenceInput> preferences,
   ) async {
     final res = await _chatApi.device.setPushPreferences(preferences);

     final currentUser = state.currentUser;
     final currentUserId = currentUser?.id;
     if (currentUserId == null) return res;

     // Emit events for updated preferences
     final updatedPushPreference = res.userPreferences[currentUserId];
     if (updatedPushPreference != null) {
-      final pushPreferenceUpdatedEvent = Event(
-        type: EventType.pushPreferenceUpdated,
-        pushPreference: updatedPushPreference,
-      );
-      handleEvent(pushPreferenceUpdatedEvent);
+      // Eager local update to prevent brief UI staleness.
+      state.currentUser =
+          state.currentUser?.copyWith(pushPreferences: updatedPushPreference);
+      handleEvent(Event(
+        type: EventType.pushPreferenceUpdated,
+        pushPreference: updatedPushPreference,
+      ));
     }

     // Emit events for updated channel-specific preferences
     final channelPushPreferences = res.userChannelPreferences[currentUserId];
     if (channelPushPreferences != null) {
-      for (final MapEntry(:key, :value) in channelPushPreferences.entries) {
-        final pushPreferenceUpdatedEvent = Event(
-          type: EventType.channelPushPreferenceUpdated,
-          cid: key,
-          channelPushPreference: value,
-        );
-        handleEvent(pushPreferenceUpdatedEvent);
-      }
+      for (final entry in channelPushPreferences.entries) {
+        final cid = entry.key;
+        final pref = entry.value;
+        handleEvent(Event(
+          type: EventType.channelPushPreferenceUpdated,
+          cid: cid,
+          channelPushPreference: pref,
+        ));
+      }
     }

     return res;
   }

Also, please confirm the map keys in res.userChannelPreferences[currentUserId] are CIDs (e.g., type:id) as expected by listeners consuming event.cid. If they are channel IDs instead, state updates won’t route correctly.

📜 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 ee78069 and 0ad0028.

📒 Files selected for processing (7)
  • packages/stream_chat/lib/src/client/channel.dart (2 hunks)
  • packages/stream_chat/lib/src/client/client.dart (2 hunks)
  • packages/stream_chat/lib/src/core/models/event.dart (5 hunks)
  • packages/stream_chat/lib/src/core/models/event.g.dart (2 hunks)
  • packages/stream_chat/lib/src/event_type.dart (1 hunks)
  • packages/stream_chat/test/src/client/channel_test.dart (1 hunks)
  • packages/stream_chat/test/src/client/client_test.dart (1 hunks)
⏰ 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
  • GitHub Check: stream_chat_localizations
  • GitHub Check: analyze_legacy_versions
  • GitHub Check: stream_chat_flutter_core
  • GitHub Check: stream_chat_persistence
  • GitHub Check: test
  • GitHub Check: build (android)
  • GitHub Check: build (ios)
  • GitHub Check: analyze
🔇 Additional comments (8)
packages/stream_chat/lib/src/event_type.dart (1)

173-179: New local event types look good

Names and string values are consistent with existing conventions.

packages/stream_chat/lib/src/client/channel.dart (2)

2182-2183: Good: registering the channel push preference listener during state init

Hooks in at the right point in the constructor and will be disposed via the existing CompositeSubscription.


3502-3518: Resolved — setPushPreferences emits channel events with cid

setPushPreferences creates Event(type: EventType.channelPushPreferenceUpdated, cid: key, channelPushPreference: value) and calls handleEvent, so Channel.on(...) filtering by cid will receive them.

packages/stream_chat/lib/src/core/models/event.g.dart (2)

71-78: JSON parsing for new fields looks correct

Keys and constructors align with PushPreference and ChannelPushPreference.


123-126: Ensure codegen is up to date

Since this is generated, please re-run codegen to avoid drift with event.dart after merging.

Run locally:

  • dart run build_runner build --delete-conflicting-outputs
packages/stream_chat/lib/src/client/client.dart (1)

2164-2167: LGTM: client state now reacts to push preference events

The currentUser = currentUser?.copyWith(pushPreferences: preferences) hook is correct and minimal. Ensure a companion test exists that asserts currentUser.pushPreferences updates after push_preference.updated.

packages/stream_chat/test/src/client/channel_test.dart (1)

4818-4909: LGTM: solid coverage for channel.push_preference.updated

  • Good assertions for both initial set and updating existing prefs.
  • Using Future.delayed(Duration.zero) is a pragmatic way to yield to the event loop for handlers to run.
packages/stream_chat/lib/src/core/models/event.dart (1)

44-46: LGTM — push preference fields wired & serialized

Verified: packages/stream_chat/lib/src/core/models/event.g.dart contains (de)serialization for 'push_preference' and 'channel_push_preference'; EventType.pushPreferenceUpdated and EventType.channelPushPreferenceUpdated exist in packages/stream_chat/lib/src/event_type.dart. No further action required.

Copy link

codecov bot commented Sep 19, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 63.85%. Comparing base (70c0f7e) to head (2c1471c).
⚠️ Report is 2 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2387      +/-   ##
==========================================
+ Coverage   63.81%   63.85%   +0.03%     
==========================================
  Files         413      413              
  Lines       25837    25858      +21     
==========================================
+ Hits        16488    16511      +23     
+ Misses       9349     9347       -2     

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

renefloor
renefloor previously approved these changes Sep 23, 2025
@xsahil03x xsahil03x merged commit 1b93d73 into master Sep 23, 2025
19 checks passed
@xsahil03x xsahil03x deleted the feat/update-current-user-push-preferences branch September 23, 2025 13:52
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.

Setting push preferences does not update currentUser state
2 participants