Skip to content

fix(Android): app shows notifications in foreground state#7012

Merged
Rohit3523 merged 21 commits intodevelopfrom
notification-fix
Mar 12, 2026
Merged

fix(Android): app shows notifications in foreground state#7012
Rohit3523 merged 21 commits intodevelopfrom
notification-fix

Conversation

@Rohit3523
Copy link
Copy Markdown
Contributor

@Rohit3523 Rohit3523 commented Feb 26, 2026

Proposed changes

When we are using the app and someone sends a message in any channel, the app shows a blank notification without content. This PR adds a check at the native level to prevent triggering notifications when the app is in the foreground

Issue(s)

https://rocketchat.atlassian.net/browse/CORE-1856
Closes: #6999

How to test or reproduce

Screenshots

Before

Screen.Recording.2026-03-10.at.9.04.00.PM.mov

After

Screen.Recording.2026-03-10.at.9.05.39.PM.mov

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

Summary by CodeRabbit

  • Bug Fixes
    • Notifications are no longer shown as duplicate system alerts when the app is in the foreground; they are routed to the in-app interface instead.
    • Foreground/background detection improved for more immediate suppression of native notifications while the app is active.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 26, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds Android foreground/background tracking and uses it to suppress native push notifications while the app is in the foreground by notifying the notification handler of the app state via ActivityLifecycleCallbacks and a shared flag.

Changes

Cohort / File(s) Summary
App State Tracking
android/app/src/main/java/chat/rocket/reactnative/MainApplication.kt
Registers an ActivityLifecycleCallbacks implementation that maintains activeActivityCount and calls CustomPushNotification.setAppInForeground(true/false) on foreground/background transitions.
Notification Suppression Logic
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java
Adds static volatile boolean isAppInForeground, public setAppInForeground(boolean) and isAppInForeground() methods, verbose logging, and skips native notification display in showNotification() when app is foreground.
Formatting Only
android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt
Whitespace/formatting adjustments only; no functional changes.

Sequence Diagram(s)

sequenceDiagram
    participant Lifecycle as Activity Lifecycle
    participant MALC as MainApplication<br/>ActivityLifecycleCallbacks
    participant CPN as CustomPushNotification
    participant Notif as Notification Display
    participant JS as React Native JS Layer

    rect rgba(100,150,200,0.5)
    Lifecycle->>MALC: onActivityStarted()
    MALC->>MALC: activeActivityCount++
    alt activeActivityCount == 1
        MALC->>CPN: setAppInForeground(true)
        CPN->>CPN: isAppInForeground = true
    end
    end

    rect rgba(200,100,100,0.5)
    Notif->>CPN: showNotification(payload)
    alt isAppInForeground == true
        CPN->>CPN: Skip native display
        CPN->>JS: deliver payload to JS layer (in-app handling)
    else
        CPN->>Notif: display native notification
    end
    end

    rect rgba(100,150,200,0.5)
    Lifecycle->>MALC: onActivityStopped()
    MALC->>MALC: activeActivityCount--
    alt activeActivityCount == 0
        MALC->>CPN: setAppInForeground(false)
        CPN->>CPN: isAppInForeground = false
    end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

type: bug

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% 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 title clearly and specifically describes the main change: preventing Android from showing push notifications when the app is in the foreground state.
Linked Issues check ✅ Passed The code changes implement the core requirements from both #6999 and CORE-1856: adding foreground state tracking and skipping native notification display when the app is in the foreground.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing foreground notification suppression on Android. Formatting changes in MainActivity.kt and notification state tracking in MainApplication.kt and CustomPushNotification.java all support the stated objective.

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

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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
Copy Markdown
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.

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)

249-257: ⚠️ Potential issue | 🟠 Major

Avoid queueing notifications that are intentionally suppressed in foreground.

Lines 253-257 still append to notificationMessages even when Line 294 later suppresses display. This can retain stale data and inflate grouped notifications once app goes background.

Suggested fix
-            notificationMessages.get(notId).add(bundle);
-            if (ENABLE_VERBOSE_LOGS) {
-                Log.d(TAG, "[After add] notificationMessages[" + notId + "].size=" + notificationMessages.get(notId).size());
-            }
-            postNotification(Integer.parseInt(notId));
+            if (isAppInForeground()) {
+                if (ENABLE_VERBOSE_LOGS) {
+                    Log.d(TAG, "App in foreground, skipping native queue/display for notId=" + notId);
+                }
+                return;
+            }
+            notificationMessages.get(notId).add(bundle);
+            if (ENABLE_VERBOSE_LOGS) {
+                Log.d(TAG, "[After add] notificationMessages[" + notId + "].size=" + notificationMessages.get(notId).size());
+            }
+            postNotification(Integer.parseInt(notId));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java`
around lines 249 - 257, The code is adding bundle entries to
notificationMessages (keyed by notId) even for notifications that are later
suppressed in foreground; update the block around
notificationMessages.get(notId).add(bundle) to first check the suppression flag
on the bundle (e.g., bundle.getBoolean("notificationLoaded", false) or the
foreground-suppression flag used later) and only add and call
postNotification(Integer.parseInt(notId)) when the bundle is not suppressed;
keep the verbose Log.d(TAG, ...) calls but move them inside the guarded branch
so suppressed notifications are neither queued nor cause postNotification to
run.

51-91: ⚠️ Potential issue | 🔴 Critical

Make isAppInForeground thread-safe.

The field at line 51 is written from MainActivity's lifecycle callbacks (main thread) and read during Firebase message processing (background thread). Without synchronization, memory visibility is not guaranteed across threads.

Suggested fix
-    private static boolean isAppInForeground = false;
+    private static volatile boolean isAppInForeground = false;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java`
around lines 51 - 91, The static field isAppInForeground is accessed from
multiple threads without synchronization; make it thread-safe by replacing the
plain boolean with a thread-safe construct (e.g.,
java.util.concurrent.atomic.AtomicBoolean) and update all usages: initialize a
private static final AtomicBoolean isAppInForeground = new AtomicBoolean(false),
change setAppInForeground(...) to call isAppInForeground.set(inForeground) and
change isAppInForeground() to return isAppInForeground.get(); ensure any other
direct reads/writes to the old boolean are updated to use the AtomicBoolean
methods (references: isAppInForeground field, setAppInForeground method,
isAppInForeground() method).
🧹 Nitpick comments (1)
android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt (1)

38-48: Consider process-level lifecycle tracking for more robust foreground state management.

Activity-level onResume/onPause can briefly mark the app as backgrounded during transitions to other activities (e.g., ShareActivity). This could allow native notifications to display during activity transitions. Use ProcessLifecycleOwner from androidx.lifecycle for process-level tracking instead—this survives activity transitions and only changes when the entire app moves to background.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt` around
lines 38 - 48, The current foreground/background tracking in MainActivity via
onResume/onPause (which calls CustomPushNotification.setAppInForeground) can
misfire during activity-to-activity transitions; replace or supplement this with
a process-level lifecycle observer using
androidx.lifecycle.ProcessLifecycleOwner: create a LifecycleObserver (or
implement DefaultLifecycleObserver) that calls
CustomPushNotification.setAppInForeground(true/false) on onStart/onStop
(process-level), register it in the Application class (e.g., in onCreate) so
state changes reflect the whole app lifecycle rather than MainActivity
transitions, and remove or keep MainActivity.onResume/onPause only if you need
activity-specific behaviour.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java`:
- Around line 249-257: The code is adding bundle entries to notificationMessages
(keyed by notId) even for notifications that are later suppressed in foreground;
update the block around notificationMessages.get(notId).add(bundle) to first
check the suppression flag on the bundle (e.g.,
bundle.getBoolean("notificationLoaded", false) or the foreground-suppression
flag used later) and only add and call postNotification(Integer.parseInt(notId))
when the bundle is not suppressed; keep the verbose Log.d(TAG, ...) calls but
move them inside the guarded branch so suppressed notifications are neither
queued nor cause postNotification to run.
- Around line 51-91: The static field isAppInForeground is accessed from
multiple threads without synchronization; make it thread-safe by replacing the
plain boolean with a thread-safe construct (e.g.,
java.util.concurrent.atomic.AtomicBoolean) and update all usages: initialize a
private static final AtomicBoolean isAppInForeground = new AtomicBoolean(false),
change setAppInForeground(...) to call isAppInForeground.set(inForeground) and
change isAppInForeground() to return isAppInForeground.get(); ensure any other
direct reads/writes to the old boolean are updated to use the AtomicBoolean
methods (references: isAppInForeground field, setAppInForeground method,
isAppInForeground() method).

---

Nitpick comments:
In `@android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt`:
- Around line 38-48: The current foreground/background tracking in MainActivity
via onResume/onPause (which calls CustomPushNotification.setAppInForeground) can
misfire during activity-to-activity transitions; replace or supplement this with
a process-level lifecycle observer using
androidx.lifecycle.ProcessLifecycleOwner: create a LifecycleObserver (or
implement DefaultLifecycleObserver) that calls
CustomPushNotification.setAppInForeground(true/false) on onStart/onStop
(process-level), register it in the Application class (e.g., in onCreate) so
state changes reflect the whole app lifecycle rather than MainActivity
transitions, and remove or keep MainActivity.onResume/onPause only if you need
activity-specific behaviour.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 566e100 and 9de14d3.

📒 Files selected for processing (2)
  • android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt
  • android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java
📜 Review details
⏰ 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). (1)
  • GitHub Check: ESLint and Test / run-eslint-and-test
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-02-05T13:55:06.688Z
Learnt from: Rohit3523
Repo: RocketChat/Rocket.Chat.ReactNative PR: 6930
File: package.json:101-101
Timestamp: 2026-02-05T13:55:06.688Z
Learning: The RocketChat/Rocket.Chat.ReactNative repository uses a fork of react-native-image-crop-picker (RocketChat/react-native-image-crop-picker) with custom Android edge-to-edge fixes, not the upstream ivpusic/react-native-image-crop-picker package. Dependencies should reference commit pins from the RocketChat fork.

Applied to files:

  • android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt
🧬 Code graph analysis (1)
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (1)
app/reducers/app.ts (1)
  • app (29-77)

@Rohit3523 Rohit3523 had a problem deploying to upload_experimental_android February 26, 2026 20:10 — with GitHub Actions Error
@github-actions
Copy link
Copy Markdown

@Rohit3523 Rohit3523 had a problem deploying to experimental_android_build March 10, 2026 12:20 — with GitHub Actions Error
@Rohit3523 Rohit3523 had a problem deploying to official_android_build March 10, 2026 12:20 — with GitHub Actions Error
@Rohit3523 Rohit3523 had a problem deploying to experimental_ios_build March 10, 2026 12:20 — with GitHub Actions Error
@Rohit3523 Rohit3523 had a problem deploying to official_android_build March 10, 2026 12:29 — with GitHub Actions Error
@Rohit3523 Rohit3523 had a problem deploying to experimental_android_build March 10, 2026 12:29 — with GitHub Actions Error
@Rohit3523 Rohit3523 had a problem deploying to experimental_ios_build March 10, 2026 12:29 — with GitHub Actions Error
@Rohit3523 Rohit3523 marked this pull request as ready for review March 10, 2026 15:37
Copy link
Copy Markdown
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 (1)
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (1)

50-91: ⚠️ Potential issue | 🟠 Major

Add volatile modifier to ensure thread-safe visibility of the foreground flag.

setAppInForeground() is called from MainActivity's lifecycle callbacks (main thread), while isAppInForeground() is read by Firebase Cloud Messaging's onMessageReceived() (background thread). A plain static boolean provides no memory visibility guarantee across threads, allowing the notification thread to read a stale cached value and post notifications even when the app is foregrounded.

Suggested fix
-    private static boolean isAppInForeground = false;
+    private static volatile boolean isAppInForeground = false;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java`
around lines 50 - 91, The static boolean isAppInForeground can be cached across
threads; make its visibility thread-safe by adding the volatile modifier to its
declaration (change "private static boolean isAppInForeground" to "private
static volatile boolean isAppInForeground") so reads in
FirebaseMessagingService.onMessageReceived() observe updates made by
MainActivity via setAppInForeground(); keep the existing setAppInForeground()
and isAppInForeground() methods unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt`:
- Around line 38-48: The current MainActivity onResume/onPause usage toggles
CustomPushNotification.setAppInForeground and should be replaced with
process-level lifecycle tracking: remove or stop using MainActivity.onResume()
and onPause() to set the flag and instead register a ProcessLifecycleOwner
observer (e.g., implement DefaultLifecycleObserver or LifecycleObserver) that
calls CustomPushNotification.setAppInForeground(true) on onStart/onResume and
setAppInForeground(false) on onStop/onPause at the application/process level;
attach the observer via
ProcessLifecycleOwner.get().getLifecycle().addObserver(...) during app
initialization (Application.onCreate or MainActivity setup) so the flag reflects
the entire app visibility rather than a single Activity.

In
`@android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java`:
- Around line 292-297: The current early return when isAppInForeground() is true
drops notifications; modify the block in CustomPushNotification so it either (A)
only suppresses the native notification if the payload's conversationId matches
the currently open conversation (compare payload.get("conversationId") against
your active convo accessor, e.g., getActiveConversationId()), or (B) before
returning forward the full payload into the app's foreground notification
handler (invoke the existing JS bridge/handler used for in-app notifications,
e.g., sendEventToJS or handleInAppNotification(payload)) so messages received
while the app is foregrounded are delivered to the JS layer instead of being
discarded. Ensure you use isAppInForeground() and the payload/conversationId
fields already available in this class to implement the chosen behavior and
remove the unconditional return.

---

Outside diff comments:
In
`@android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java`:
- Around line 50-91: The static boolean isAppInForeground can be cached across
threads; make its visibility thread-safe by adding the volatile modifier to its
declaration (change "private static boolean isAppInForeground" to "private
static volatile boolean isAppInForeground") so reads in
FirebaseMessagingService.onMessageReceived() observe updates made by
MainActivity via setAppInForeground(); keep the existing setAppInForeground()
and isAppInForeground() methods unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f4051e09-d129-43d4-a1a2-c88853c5d587

📥 Commits

Reviewing files that changed from the base of the PR and between 610f564 and 15588e2.

📒 Files selected for processing (2)
  • android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt
  • android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java

@Rohit3523 Rohit3523 had a problem deploying to experimental_ios_build March 10, 2026 15:57 — with GitHub Actions Error
@Rohit3523
Copy link
Copy Markdown
Contributor Author

@corerabbitai review

@Rohit3523 Rohit3523 had a problem deploying to experimental_ios_build March 10, 2026 17:14 — with GitHub Actions Error
@Rohit3523 Rohit3523 had a problem deploying to experimental_android_build March 10, 2026 17:14 — with GitHub Actions Error
@Rohit3523 Rohit3523 had a problem deploying to official_android_build March 10, 2026 17:14 — with GitHub Actions Error
@Rohit3523 Rohit3523 had a problem deploying to experimental_ios_build March 10, 2026 18:17 — with GitHub Actions Error
@Rohit3523 Rohit3523 had a problem deploying to experimental_android_build March 10, 2026 18:17 — with GitHub Actions Error
@Rohit3523 Rohit3523 had a problem deploying to official_android_build March 10, 2026 18:17 — with GitHub Actions Error
activeActivityCount++
if (activeActivityCount == 1) {
// App moved from background to foreground
CustomPushNotification.setAppInForeground(true)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

CustomPushNotification should be in charge of showing push notifications.
Here you're making MainApplication ask it to control app state.

Did you read expo-notifications and react-native-notifications to understand how they work? IIRC react-native-notifications didn't have this issue.

@Rohit3523 Rohit3523 had a problem deploying to official_android_build March 10, 2026 19:56 — with GitHub Actions Error
@Rohit3523 Rohit3523 had a problem deploying to experimental_android_build March 10, 2026 19:56 — with GitHub Actions Error
@Rohit3523 Rohit3523 had a problem deploying to experimental_ios_build March 10, 2026 19:56 — with GitHub Actions Error
@github-actions
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

@OtavioStasiak OtavioStasiak left a comment

Choose a reason for hiding this comment

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

LGTM!
Tested on Xiaomi MI A3 Android 11

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.

bug: Notifications sent for the channel I'm currently in

3 participants