Skip to content

feat: Implement 'Mark as Read' action for push notifications …#6867

Open
DSingh0304 wants to merge 7 commits intoRocketChat:developfrom
DSingh0304:feature/mark-as-read
Open

feat: Implement 'Mark as Read' action for push notifications …#6867
DSingh0304 wants to merge 7 commits intoRocketChat:developfrom
DSingh0304:feature/mark-as-read

Conversation

@DSingh0304
Copy link

@DSingh0304 DSingh0304 commented Dec 28, 2025

Proposed changes

This PR implements a "Mark as Read" quick action button for push notifications on both iOS and Android platforms. This feature allows users to mark messages/rooms as read directly from a push notification without opening the app, providing a convenient way to manage notifications.

Implementation Details:

  • Added new notification action MARK_AS_READ_ACTION alongside the existing Reply action
  • Calls the existing POST /api/v1/subscriptions.read API endpoint (available since Rocket.Chat server v0.61.0)
  • Notification is dismissed after successful API call
  • Follows the same pattern as the existing Reply notification action

Platforms:

  • ✅ iOS (13+)
  • ✅ Android (API 24+)

Issue(s)

Closes #6842

How to test or reproduce

Prerequisites:

  • Rocket.Chat server (v0.61.0+) with push notifications configured
  • Physical device or production build (debug builds may have FCM token registration limitations)

Testing Steps:

  1. Build and install the app on a device
  2. Login to your Rocket.Chat account
  3. Grant notification permissions when prompted
  4. Send a message to yourself from another device/browser (or have someone send you a message)
  5. Close/minimize the app (notification only appears when app is backgrounded)
  6. Wait for notification to appear
  7. Pull down/expand the notification to see action buttons
  8. Verify you see both:
    • "Reply" button (existing)
    • "Mark as read" button (NEW) ✨
  9. Tap "Mark as read"
  10. Expected result:
    • Notification dismisses
    • Room shows as read when you open the app (no unread badge)

Note on Testing:

Due to Firebase configuration limitations in development builds, full end-to-end push notification testing requires production Firebase credentials. The implementation follows the existing Reply action pattern and uses the established subscriptions.read API endpoint.

Screenshots

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Implementation Approach:

This feature was implemented by following the existing Reply notification action pattern:

Files Modified/Created (8 total):

Common (2 files):

  • app/i18n/locales/en.json - Added "Mark_as_read" translation
  • app/lib/notifications/push.ts - Registered MARK_AS_READ_ACTION notification action

iOS (3 files):

  • ios/Shared/RocketChat/API/Requests/MarkAsRead.swift - NEW: API request definition
  • ios/Shared/RocketChat/RocketChat.swift - Added markAsRead() helper method
  • ios/ReplyNotification.swift - Added handleMarkAsReadAction() handler

Android (3 files):

  • android/app/src/main/java/.../MarkAsReadBroadcast.java - NEW: Broadcast receiver
  • android/app/src/main/AndroidManifest.xml - Registered broadcast receiver
  • android/app/src/main/java/.../CustomPushNotification.java - Added notification action button

API Endpoint:

Uses the existing POST /api/v1/subscriptions.read endpoint which:

  • Requires rid (room ID) parameter
  • Is already used internally by the app
  • Available since Rocket.Chat server v0.61.0
  • No server-side changes required

Testing Limitations:

Full push notification testing requires production Firebase credentials which are not available in development builds. This is a known limitation discussed in the community (reference: community conversation). The code follows established patterns and compiles successfully on both platforms.

Why This Approach:

  1. Consistency - Mirrors the existing Reply action implementation
  2. No breaking changes - Purely additive feature
  3. Minimal server impact - Uses existing API endpoint
  4. User convenience - Common use case in many messaging apps

Summary by CodeRabbit

  • New Features

    • Added a "Mark as read" action to notifications on iOS and Android so users can mark messages as read directly from notification alerts without opening the app.
    • Tapping the action sends the mark-as-read request and clears the corresponding system notification on success.
  • Improvements

    • Notifications handle app initialization delays more reliably and fetch avatars more robustly with timeouts and fallbacks.
  • Localization

    • Added English label for the "Mark as read" notification action.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 28, 2025

Walkthrough

Adds a "Mark as read" notification action across platforms: Android manifest and broadcast receiver, Android notification wiring and avatar/loading adjustments, iOS action handling and API request, shared iOS API request and RocketChat method, push config update, and English localization key. Action posts to /api/v1/subscriptions.read and dismisses notification on success.

Changes

Cohort / File(s) Summary
Android Manifest
android/app/src/main/AndroidManifest.xml
Declare new broadcast receiver chat.rocket.reactnative.notification.MarkAsReadBroadcast (android:enabled="true", android:exported="false").
Android Notification & Receiver
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java, android/app/src/main/java/chat/rocket/reactnative/notification/MarkAsReadBroadcast.java
Add notificationMarkAsRead() to attach "Mark as read" action (handles API-level PendingIntent flags); inject action into full notifications; add async wait-for-React initialization, stronger logging, avatar fetch timeout, and message-id loading; new MarkAsReadBroadcast parses payload, POSTs /api/v1/subscriptions.read, clears local messages, and cancels system notification on success.
iOS Notification Handling
ios/ReplyNotification.swift
Handle MARK_AS_READ_ACTION by adding handleMarkAsReadAction(...) that decodes payload, calls RocketChat.markAsRead, manages background task and completion.
iOS API Layer
ios/Shared/RocketChat/API/Requests/MarkAsRead.swift, ios/Shared/RocketChat/RocketChat.swift
Add MarkAsReadRequest (POST /api/v1/subscriptions.read), MarkAsReadBody, MarkAsReadResponse; add RocketChat.markAsRead(rid:completion:) wrapper.
Push Config & Localization
app/lib/notifications/push.ts, app/i18n/locales/en.json
Add MARK_AS_READ_ACTION to MESSAGE category (iOS) and new localization key "Mark_as_read": "Mark as read".

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant OS as Notification System
    participant App
    participant Server

    User->>OS: Tap "Mark as read" action
    OS->>App: Deliver intent / UNNotificationResponse

    rect rgba(220,235,245,0.5)
    Note over App: Extract payload (rid, serverURL, auth tokens)
    App->>App: Validate payload & auth
    end

    rect rgba(245,235,220,0.5)
    Note over App: Send API request
    App->>Server: POST /api/v1/subscriptions.read { rid } (with auth headers)
    end

    alt success
        Server-->>App: { success: true }
        App->>App: Clear local unread state / cache
        App->>OS: Cancel/dismiss notification
    else failure
        Server-->>App: error / non-200
        App->>App: Log error (leave notification)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • OtavioStasiak

Poem

🐰 A tiny hop, a button pressed,
Mark as read — let inbox rest,
From Android intents to Swift's reply,
A little request, the badge says bye,
I twitch my whiskers — notifications light.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and concisely describes the primary change: implementing a 'Mark as Read' action for push notifications on iOS and Android.
Linked Issues check ✅ Passed The implementation fulfills all coding requirements from issue #6842: adds 'Mark as Read' action to iOS (ReplyNotification.swift, MarkAsRead.swift API) and Android (MarkAsReadBroadcast.java) with i18n support; calls subscriptions.read API to mark rooms as read and dismisses notifications.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the 'Mark as Read' feature scope: notification action declaration, native handlers, API integration, i18n strings, and supporting modifications to CustomPushNotification for action injection are in-scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

🧪 Unit Test Generation v2 is now available!

We have significantly improved our unit test generation capabilities.

To enable: Add this to your .coderabbit.yaml configuration:

reviews:
  finishing_touches:
    unit_tests:
      enabled: true

Try it out by using the @coderabbitai generate unit tests command on your code files or under ✨ Finishing Touches on the walkthrough!

Have feedback? Share your thoughts on our Discord thread!


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

🧹 Nitpick comments (2)
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (1)

659-689: Consider externalizing the action label for localization.

The method correctly implements the Mark as Read action following the same pattern as notificationReply. However, the label is hardcoded as "Mark as read" instead of loading from Android string resources, which prevents localization.

🔎 Suggested improvement for localization

If you want to support localization similar to the iOS implementation, consider:

  1. Add to android/app/src/main/res/values/strings.xml:
<string name="mark_as_read">Mark as read</string>
  1. Update the method:
-        String label = "Mark as read";
+        String label = mContext.getString(R.string.mark_as_read);

Alternatively, if localization isn't needed on Android (the label won't be translated), the current hardcoded approach is acceptable.

ios/ReplyNotification.swift (1)

125-145: Consider adding error handling for mark-as-read failures.

The implementation correctly extracts the payload and performs the mark-as-read operation with proper background task management. However, unlike handleReplyAction (lines 113-120), this method doesn't handle or notify the user about failures.

🔎 Suggested improvement for consistency

To match the error handling pattern in handleReplyAction:

     rocketchat.markAsRead(rid: rid) { response in
       DispatchQueue.main.async {
+        defer {
+          UIApplication.shared.endBackgroundTask(backgroundTask)
+          completionHandler()
+        }
+
+        guard let response = response, response.success else {
+          // Optionally show failure notification
+          let content = UNMutableNotificationContent()
+          content.body = "Failed to mark as read."
+          let request = UNNotificationRequest(identifier: "markAsReadFailure", content: content, trigger: nil)
+          UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
+          return
+        }
-        UIApplication.shared.endBackgroundTask(backgroundTask)
-        completionHandler()
       }
     }

This also addresses the SwiftLint warning about the unused response parameter.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira 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 0d13c14 and b2fd8db.

📒 Files selected for processing (8)
  • android/app/src/main/AndroidManifest.xml
  • android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java
  • android/app/src/main/java/chat/rocket/reactnative/notification/MarkAsReadBroadcast.java
  • app/i18n/locales/en.json
  • app/lib/notifications/push.ts
  • ios/ReplyNotification.swift
  • ios/Shared/RocketChat/API/Requests/MarkAsRead.swift
  • ios/Shared/RocketChat/RocketChat.swift
🧰 Additional context used
🧬 Code graph analysis (4)
ios/ReplyNotification.swift (3)
ios/NotificationService/NotificationService.swift (1)
  • didReceive (9-73)
ios/Shared/Extensions/String+Extensions.swift (1)
  • removeTrailingSlash (25-31)
ios/Shared/RocketChat/RocketChat.swift (1)
  • markAsRead (84-95)
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (3)
android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfBroadcast.kt (1)
  • TAG (14-73)
android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.kt (2)
  • context (21-210)
  • createNotificationChannel (54-77)
android/app/src/main/java/chat/rocket/reactnative/notification/MarkAsReadBroadcast.java (1)
  • MarkAsReadBroadcast (22-86)
android/app/src/main/java/chat/rocket/reactnative/notification/MarkAsReadBroadcast.java (1)
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (1)
  • CustomPushNotification (48-721)
ios/Shared/RocketChat/RocketChat.swift (4)
ios/Shared/RocketChat/API/API.swift (1)
  • fetch (54-91)
ios/Shared/RocketChat/API/Request.swift (1)
  • request (45-72)
ios/Shared/RocketChat/Database.swift (1)
  • readRoomEncrypted (117-125)
ios/Shared/RocketChat/Encryption.swift (2)
  • encryptContent (212-269)
  • decryptContent (175-210)
🪛 SwiftLint (0.57.0)
ios/ReplyNotification.swift

[Warning] 139-139: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

ios/Shared/RocketChat/RocketChat.swift

[Error] 25-25: @escaping must have a trailing space before the associated type

(attribute_name_spacing)


[Error] 39-39: @escaping must have a trailing space before the associated type

(attribute_name_spacing)


[Warning] 34-34: Avoid using unneeded break statements

(unneeded_break_in_switch)


[Error] 84-84: @escaping must have a trailing space before the associated type

(attribute_name_spacing)


[Warning] 71-71: Avoid using unneeded break statements

(unneeded_break_in_switch)


[Warning] 92-92: Avoid using unneeded break statements

(unneeded_break_in_switch)

🔇 Additional comments (7)
android/app/src/main/java/chat/rocket/reactnative/notification/MarkAsReadBroadcast.java (1)

22-43: LGTM!

The onReceive implementation correctly extracts notification data, validates the notification ID format, and delegates to the markAsRead method. Error handling for invalid notification IDs is appropriate.

app/i18n/locales/en.json (1)

502-502: LGTM!

The localization entry follows the existing naming convention and is properly placed in alphabetical order.

app/lib/notifications/push.ts (1)

91-110: LGTM! Properly implements iOS notification action.

The MARK_AS_READ_ACTION correctly mirrors the existing REPLY_ACTION pattern, with appropriate configuration:

  • Uses localized button title via I18n.t('Mark_as_read')
  • Sets opensAppToForeground: false to handle the action in the background
  • Properly registered in the MESSAGE category alongside existing actions
android/app/src/main/AndroidManifest.xml (1)

95-98: LGTM! Receiver declaration is properly configured.

The receiver configuration is correct:

  • android:exported="false" properly restricts access to internal app components only (security best practice)
  • No intent-filter needed since it's triggered by direct PendingIntent from the notification action
  • Logical placement between similar notification receivers
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (1)

446-446: LGTM! Action properly integrated into notification flow.

The notificationMarkAsRead call is correctly placed alongside notificationReply, ensuring the Mark as Read action appears only on full notifications (not on fallback message-id-only placeholders).

ios/ReplyNotification.swift (1)

44-48: LGTM! Action handler properly registered.

The MARK_AS_READ_ACTION is correctly intercepted and handled natively before forwarding to the original delegate, following the same pattern as REPLY_ACTION.

ios/Shared/RocketChat/API/Requests/MarkAsRead.swift (1)

11-34: LGTM! API request implementation follows established patterns.

The implementation correctly follows the Request/Response pattern used throughout the iOS codebase:

  • MarkAsReadBody properly encodes the required rid parameter
  • MarkAsReadResponse conforms to the Response protocol
  • MarkAsReadRequest uses the correct HTTP POST method and endpoint path
  • Body encoding uses JSONEncoder consistently with other requests

…edentials, and a memory monitor, while refactoring Android notification handling and updating Firebase configuration.
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

🧹 Nitpick comments (2)
android/app/src/main/java/chat/rocket/reactnative/notification/MarkAsReadBroadcast.java (2)

25-25: Configure timeouts for OkHttpClient.

While the static instance addresses the reuse concern from previous reviews, the client lacks timeout configuration. Network operations without timeouts can hang indefinitely, particularly on poor connections or when the server is unresponsive.

🔎 Proposed timeout configuration
-    private static final OkHttpClient client = new OkHttpClient();
+    private static final OkHttpClient client = new OkHttpClient.Builder()
+            .connectTimeout(10, TimeUnit.SECONDS)
+            .readTimeout(10, TimeUnit.SECONDS)
+            .writeTimeout(10, TimeUnit.SECONDS)
+            .build();

You'll also need to add the import:

import java.util.concurrent.TimeUnit;

56-56: Prefer Gson for JSON construction.

Manual string formatting is fragile—if rid contains quotes or backslashes, the JSON will be malformed. Using Gson ensures proper escaping and is more maintainable.

🔎 Proposed refactor
+        // Create a simple wrapper class or use a Map
+        java.util.Map<String, String> payload = new java.util.HashMap<>();
+        payload.put("rid", rid);
+        String json = new Gson().toJson(payload);
-        String json = String.format("{\"rid\":\"%s\"}", rid);

         RequestBody body = RequestBody.create(JSON, json);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira 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 b2fd8db and e487fe4.

📒 Files selected for processing (2)
  • android/app/src/main/java/chat/rocket/reactnative/notification/MarkAsReadBroadcast.java
  • ios/Shared/RocketChat/RocketChat.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • ios/Shared/RocketChat/RocketChat.swift
🔇 Additional comments (2)
android/app/src/main/java/chat/rocket/reactnative/notification/MarkAsReadBroadcast.java (2)

60-61: No action needed. The ejson.token() and ejson.userId() methods already validate their inputs and return non-null values (returning empty strings as fallback when credentials are missing), with appropriate error logging. The code at lines 60-61 is safe from null reference exceptions.

Likely an incorrect or invalid review comment.


77-78: No action needed. CustomPushNotification.clearMessages(notId) is thread-safe because it performs an atomic remove() operation on a ConcurrentHashMap, which is designed for concurrent access.

Likely an incorrect or invalid review comment.

…n, and improve Android notification async handling.
@diegolmello
Copy link
Member

@DSingh0304 Thanks for your contribution!
It may take a while to review, since we're just starting the year, but we'll do it eventually 🙏🏼

@DSingh0304 DSingh0304 had a problem deploying to experimental_android_build January 5, 2026 13:12 — with GitHub Actions Failure
@DSingh0304 DSingh0304 had a problem deploying to experimental_ios_build January 5, 2026 13:12 — with GitHub Actions Failure
@DSingh0304 DSingh0304 had a problem deploying to official_android_build January 5, 2026 13:12 — with GitHub Actions Failure
@DSingh0304
Copy link
Author

@DSingh0304 Thanks for your contribution! It may take a while to review, since we're just starting the year, but we'll do it eventually 🙏🏼

Yes Sure !!

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

Caution

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

⚠️ Outside diff range comments (2)
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (2)

160-167: ⚠️ Potential issue | 🔴 Critical

Remove the duplicate message-id-only check (compile error).

Two consecutive if statements open the same block, leaving the first one unclosed.

🧹 Proposed fix
-        if (receivedEjson != null && receivedEjson.notificationType != null
-                && receivedEjson.notificationType.equals("message-id-only")) {
-        
-        if (receivedEjson != null && receivedEjson.notificationType != null && receivedEjson.notificationType.equals("message-id-only")) {
+        if (receivedEjson != null && receivedEjson.notificationType != null
+                && receivedEjson.notificationType.equals("message-id-only")) {

241-301: ⚠️ Potential issue | 🔴 Critical

Conflicting E2E implementations are interleaved (duplicate variable/braces).

The current block mixes the old and new flows, redeclares decrypted, and leaves mismatched braces. Please consolidate to a single strategy and ensure success/fallback paths call showNotification.

🧹 Proposed consolidation (fast path + async fallback)
-        // Check if React context is immediately available
-        if (reactApplicationContext != null) {
-            // Fast path: decrypt immediately
-            String decrypted = Encryption.shared.decryptMessage(ejson, reactApplicationContext);
-
-            if (decrypted != null) {
-                bundle.putString("message", decrypted);
-        // Decrypt immediately using regular Android Context (mContext)
-        // This works without React Native initialization
-        String decrypted = Encryption.shared.decryptMessage(ejson, mContext);
-        
-        if (decrypted != null) {
-            bundle.putString("message", decrypted);
-            synchronized(this) {
-                mBundle = bundle;
-            }
-            return;
-        }
+        if (reactApplicationContext != null) {
+            String decrypted = Encryption.shared.decryptMessage(ejson, reactApplicationContext);
+            if (decrypted != null) {
+                bundle.putString("message", decrypted);
+                synchronized (this) {
+                    mBundle = bundle;
+                }
+                showNotification(bundle, ejson, notId);
+                return;
+            }
+        }

         // Slow path: wait for React context asynchronously
         Log.i(TAG, "Waiting for React context to decrypt E2E notification");

         E2ENotificationProcessor processor = new E2ENotificationProcessor(
                 // Context provider
                 () -> reactApplicationContext,

                 // Callback
                 new E2ENotificationProcessor.NotificationCallback() {
                     `@Override`
                     public void onDecryptionComplete(Bundle decryptedBundle, Ejson decryptedEjson,
                             String notificationId) {
                         mBundle = decryptedBundle;
                         Ejson finalEjson = safeFromJson(decryptedBundle.getString("ejson", "{}"), Ejson.class);
                         showNotification(decryptedBundle, finalEjson, notificationId);
                     }

                     `@Override`
                     public void onDecryptionFailed(Bundle originalBundle, Ejson originalEjson, String notificationId) {
                         Log.w(TAG, "E2E decryption failed for notification");
+                        originalBundle.putString("message", "Encrypted message");
+                        synchronized (CustomPushNotification.this) {
+                            mBundle = originalBundle;
+                        }
+                        showNotification(originalBundle, originalEjson, notificationId);
                     }

                     `@Override`
                     public void onTimeout(Bundle originalBundle, Ejson originalEjson, String notificationId) {
                         Log.w(TAG, "Timeout waiting for React context for E2E notification");
+                        originalBundle.putString("message", "Encrypted message");
+                        synchronized (CustomPushNotification.this) {
+                            mBundle = originalBundle;
+                        }
+                        showNotification(originalBundle, originalEjson, notificationId);
                     }
                 });

         processor.processAsync(bundle, ejson, notId);
-            showNotification(bundle, ejson, notId);
-        } else {
-            Log.w(TAG, "E2E decryption failed for notification, showing fallback notification");
-            // Show fallback notification so user knows a message arrived
-            // Use a placeholder message since we can't decrypt
-            bundle.putString("message", "Encrypted message");
-            synchronized(this) {
-                mBundle = bundle;
-            }
-            showNotification(bundle, ejson, notId);
-        }
🤖 Fix all issues with AI agents
In
`@android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java`:
- Around line 495-528: The getAvatar method contains an unreachable final return
calling NotificationHelper.fetchAvatarBitmap because both the try block and its
catch already return; remove that dead code (the final "return
NotificationHelper.fetchAvatarBitmap(mContext, uri, largeIcon());") or refactor
so the method returns once (e.g., assign avatar in try/catch and have a single
final return), ensuring references to getAvatar, largeIcon, and
NotificationHelper.fetchAvatarBitmap are updated/removed accordingly so the code
compiles.
- Around line 73-78: The method isReactInitialized() references an undeclared
reactApplicationContext; declare a class-level field named
reactApplicationContext (type ReactApplicationContext) as volatile, import
com.facebook.react.bridge.ReactApplicationContext, and ensure it is populated
when the React instance is created (e.g., set it from your
ReactNativeHost/ReactInstanceManager or during initialization in the class
constructor or an init method). Make reads/writes thread-safe by keeping the
volatile modifier and update any places that previously accessed a local/react
context to use this new field so isReactInitialized() returns a valid check
against the populated context.

Comment on lines +73 to +78
/**
* Check if React Native is initialized
*/
private boolean isReactInitialized() {
return reactApplicationContext != null;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

reactApplicationContext is undefined here.

This won’t compile unless the React context is declared/imported and populated (make it volatile since it’s polled from another thread).

🔧 Suggested fix (declare & wire the React context)
+import com.facebook.react.bridge.ReactApplicationContext;
...
+    `@Nullable`
+    private static volatile ReactApplicationContext reactApplicationContext;
+
+    public static void setReactApplicationContext(`@Nullable` ReactApplicationContext context) {
+        reactApplicationContext = context;
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Check if React Native is initialized
*/
private boolean isReactInitialized() {
return reactApplicationContext != null;
}
import com.facebook.react.bridge.ReactApplicationContext;
public class CustomPushNotification {
`@Nullable`
private static volatile ReactApplicationContext reactApplicationContext;
public static void setReactApplicationContext(`@Nullable` ReactApplicationContext context) {
reactApplicationContext = context;
}
/**
* Check if React Native is initialized
*/
private boolean isReactInitialized() {
return reactApplicationContext != null;
}
}
🤖 Prompt for AI Agents
In
`@android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java`
around lines 73 - 78, The method isReactInitialized() references an undeclared
reactApplicationContext; declare a class-level field named
reactApplicationContext (type ReactApplicationContext) as volatile, import
com.facebook.react.bridge.ReactApplicationContext, and ensure it is populated
when the React instance is created (e.g., set it from your
ReactNativeHost/ReactInstanceManager or during initialization in the class
constructor or an init method). Make reads/writes thread-safe by keeping the
volatile modifier and update any places that previously accessed a local/react
context to use this new field so isReactInitialized() returns a valid check
against the populated context.

Comment on lines 495 to 528
private Bitmap getAvatar(String uri) {
if (uri == null || uri.isEmpty()) {
if (ENABLE_VERBOSE_LOGS) {
Log.w(TAG, "getAvatar called with null/empty URI");
}
return largeIcon();
}

if (ENABLE_VERBOSE_LOGS) {
String sanitizedUri = uri;
int queryStart = uri.indexOf("?");
if (queryStart != -1) {
sanitizedUri = uri.substring(0, queryStart) + "?[auth_params]";
}
Log.d(TAG, "Fetching avatar from: " + sanitizedUri);
}

try {
// Use a 3-second timeout to avoid blocking the FCM service for too long
// FCM has a 10-second limit, so we need to fail fast and use fallback icon
Bitmap avatar = Glide.with(mContext)
.asBitmap()
.apply(RequestOptions.bitmapTransform(new RoundedCorners(10)))
.load(uri)
.submit(100, 100)
.get(3, TimeUnit.SECONDS);

return avatar != null ? avatar : largeIcon();
} catch (final ExecutionException | InterruptedException | TimeoutException e) {
Log.e(TAG, "Failed to fetch avatar: " + e.getMessage(), e);
return largeIcon();
}
return NotificationHelper.fetchAvatarBitmap(mContext, uri, largeIcon());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Remove unreachable legacy return after the Glide path.

Both the try and catch paths already return, so the final NotificationHelper.fetchAvatarBitmap line is unreachable and fails compilation.

🧹 Proposed fix
-        return NotificationHelper.fetchAvatarBitmap(mContext, uri, largeIcon());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private Bitmap getAvatar(String uri) {
if (uri == null || uri.isEmpty()) {
if (ENABLE_VERBOSE_LOGS) {
Log.w(TAG, "getAvatar called with null/empty URI");
}
return largeIcon();
}
if (ENABLE_VERBOSE_LOGS) {
String sanitizedUri = uri;
int queryStart = uri.indexOf("?");
if (queryStart != -1) {
sanitizedUri = uri.substring(0, queryStart) + "?[auth_params]";
}
Log.d(TAG, "Fetching avatar from: " + sanitizedUri);
}
try {
// Use a 3-second timeout to avoid blocking the FCM service for too long
// FCM has a 10-second limit, so we need to fail fast and use fallback icon
Bitmap avatar = Glide.with(mContext)
.asBitmap()
.apply(RequestOptions.bitmapTransform(new RoundedCorners(10)))
.load(uri)
.submit(100, 100)
.get(3, TimeUnit.SECONDS);
return avatar != null ? avatar : largeIcon();
} catch (final ExecutionException | InterruptedException | TimeoutException e) {
Log.e(TAG, "Failed to fetch avatar: " + e.getMessage(), e);
return largeIcon();
}
return NotificationHelper.fetchAvatarBitmap(mContext, uri, largeIcon());
}
private Bitmap getAvatar(String uri) {
if (uri == null || uri.isEmpty()) {
if (ENABLE_VERBOSE_LOGS) {
Log.w(TAG, "getAvatar called with null/empty URI");
}
return largeIcon();
}
if (ENABLE_VERBOSE_LOGS) {
String sanitizedUri = uri;
int queryStart = uri.indexOf("?");
if (queryStart != -1) {
sanitizedUri = uri.substring(0, queryStart) + "?[auth_params]";
}
Log.d(TAG, "Fetching avatar from: " + sanitizedUri);
}
try {
// Use a 3-second timeout to avoid blocking the FCM service for too long
// FCM has a 10-second limit, so we need to fail fast and use fallback icon
Bitmap avatar = Glide.with(mContext)
.asBitmap()
.apply(RequestOptions.bitmapTransform(new RoundedCorners(10)))
.load(uri)
.submit(100, 100)
.get(3, TimeUnit.SECONDS);
return avatar != null ? avatar : largeIcon();
} catch (final ExecutionException | InterruptedException | TimeoutException e) {
Log.e(TAG, "Failed to fetch avatar: " + e.getMessage(), e);
return largeIcon();
}
}
🤖 Prompt for AI Agents
In
`@android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java`
around lines 495 - 528, The getAvatar method contains an unreachable final
return calling NotificationHelper.fetchAvatarBitmap because both the try block
and its catch already return; remove that dead code (the final "return
NotificationHelper.fetchAvatarBitmap(mContext, uri, largeIcon());") or refactor
so the method returns once (e.g., assign avatar in try/catch and have a single
final return), ensuring references to getAvatar, largeIcon, and
NotificationHelper.fetchAvatarBitmap are updated/removed accordingly so the code
compiles.

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.

feature: Add "Mark as Read" action button to push notifications

2 participants