fix(Android): app shows notifications in foreground state#7012
fix(Android): app shows notifications in foreground state#7012
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ 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). 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. Comment |
There was a problem hiding this comment.
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 | 🟠 MajorAvoid queueing notifications that are intentionally suppressed in foreground.
Lines 253-257 still append to
notificationMessageseven 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 | 🔴 CriticalMake
isAppInForegroundthread-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/onPausecan briefly mark the app as backgrounded during transitions to other activities (e.g., ShareActivity). This could allow native notifications to display during activity transitions. UseProcessLifecycleOwnerfrom 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
📒 Files selected for processing (2)
android/app/src/main/java/chat/rocket/reactnative/MainActivity.ktandroid/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)
|
Android Build Available Rocket.Chat Experimental 4.70.0.108316 |
There was a problem hiding this comment.
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 | 🟠 MajorAdd
volatilemodifier to ensure thread-safe visibility of the foreground flag.
setAppInForeground()is called from MainActivity's lifecycle callbacks (main thread), whileisAppInForeground()is read by Firebase Cloud Messaging'sonMessageReceived()(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
📒 Files selected for processing (2)
android/app/src/main/java/chat/rocket/reactnative/MainActivity.ktandroid/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java
android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt
Outdated
Show resolved
Hide resolved
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java
Outdated
Show resolved
Hide resolved
|
@corerabbitai review |
| activeActivityCount++ | ||
| if (activeActivityCount == 1) { | ||
| // App moved from background to foreground | ||
| CustomPushNotification.setAppInForeground(true) |
There was a problem hiding this comment.
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.
|
Android Build Available Rocket.Chat Experimental 4.71.0.108358 |
OtavioStasiak
left a comment
There was a problem hiding this comment.
LGTM!
Tested on Xiaomi MI A3 Android 11
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
Checklist
Further comments
Summary by CodeRabbit