Skip to content

[Exploratory] Android - Notification banner is not displayed if app is open or minimized #93027

Closed

Description

@applause-bot

If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!


Version Number: 9.4.1-0
Reproducible in staging?: Yes
Reproducible in production?: Yes
If this was caught during regression testing, add the test name, ID and link from BrowserStack: #92497
Email or phone of affected tester (no customers): fischer9966+053122@gmail.com
Issue reported by: Applause Internal Team
Bug source: Pull Request QA execution
Device used: Google Pixel 5 / Android 14
App Component: Notifications

Action Performed:

  1. Open Expensify app for Android
  2. Login with any account
  3. Keep the app open on the chat list or minimize it
  4. Trigger a push notification (e.g. send a chat message from another account to account from step 2)
  5. Verify the notification banner appears and tapping it opens the correct chat.

Expected Result:

A notification banner appears if the app is open or minimized and the user is not in the chat where the message is being received.

Actual Result:

Notifications appears only in the notifications feed. Notification banner does not appears if the app is open or minimized and the user is not in the chat where the message is being received.

Workaround:

Unknown

Platforms:

  • Android: App
  • Android: mWeb Chrome
  • iOS: App
  • iOS: mWeb Safari
  • iOS: mWeb Chrome
  • Windows: Chrome
  • MacOS: Chrome Safari

Screenshots/Videos

Bug7175727_1780998227032.92497-Android-Notification-banner-not-appear-when-app-open.mp4

View all open jobs on GitHub

Applause Internal Information
Exported by: Mita Rachim
Bug ID: 7175727

Activity

  1. applause-bot commented on Jun 9, 2026

    @applause-bot
    Author

    While Applause doesn't embed attachments for this bug, here are the links for quick view:
    https://platform.applause.com/services/links/v1/external/b46c612ab877cc3c1f3cb8b4c56ed0337844475af53050e3c14153400fe2ac3e

  2. trasnake87 commented on Jun 9, 2026

    @trasnake87
    Contributor

    Proposal

    What is the root cause

    Android foreground push notification banners are suppressed by the logic in src/libs/Notification/PushNotification/shouldShowPushNotification.ts, which is called from the Airship foreground display predicate set in src/libs/Notification/PushNotification/ForegroundNotifications/index.android.ts (line 6):

    Airship.push.android.setForegroundDisplayPredicate(
        (pushPayload) => Promise.resolve(shouldShowPushNotification(pushPayload))
    );

    shouldShowPushNotification delegates to Report.shouldShowReportActionNotification (src/libs/actions/Report/index.ts, line 4542). That function has two default-deny checks that fire when Onyx data is not yet populated:

    1. Line 4559–4560 — notification preference gate:

      const notificationPreference = getReportNotificationPreference(allReports?.[...]);
      if (notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS) return false;

      getReportNotificationPreference (src/libs/ReportUtils.ts, line 1891–1896) returns HIDDEN when deprecatedCurrentUserAccountID is undefined or when the report's participants entry has no notificationPreference. The result is that most foreground banners are suppressed by default.

    2. Line 4586–4590 — report existence gate:

      const report = allReports?.[...];
      if (!report || ...) return false;

      When allReports is not yet populated the predicate returns false and suppresses the banner.

    Both gates are correct for local notifications (Pusher-based), where Onyx is fully populated before the check runs. But the Airship setForegroundDisplayPredicate fires as soon as the push arrives — before the Onyx collection is necessarily ready — so both gates produce false negatives.

    For the minimized case: Airship's Android lifecycle observer keeps the process classified as "foregrounded" for roughly 700 ms after the user presses Home (to absorb brief transitions). Any push arriving in that window still calls the predicate. Visibility.isVisible() returns false because AppState.currentState !== 'active', so the same-chat guard on line 4572 does not fire, but the preference gate on line 4560 still returns false (stale data), suppressing the banner before Airship can hand it off to the native path.

    Before this file was introduced, Android used the no-op ForegroundNotifications/index.ts, so Airship showed all foreground push banners without any filtering.

    The iOS implementation (ForegroundNotifications/index.ios.ts) avoids this class of bug because in the background iOS never calls setForegroundPresentationOptionsCallback — native iOS always shows the banner — so the Onyx-data race only exists in the narrow foreground window where the navigation check is reliable.

    What changes should we make

    1. Replace shouldShowPushNotification.ts with a navigation-only check.

    The only reliable signal available when the predicate fires (even before Onyx settles) is where the user is navigating. Mirror what the same-chat guard already does on line 4572 of shouldShowReportActionNotification — suppress only when the user is actively viewing the exact report that generated the push:

    // src/libs/Notification/PushNotification/shouldShowPushNotification.ts
    import type {PushPayload} from '@ua/react-native-airship';
    import Log from '@libs/Log';
    import Navigation from '@libs/Navigation/Navigation';
    import Visibility from '@libs/Visibility';
    import parsePushNotificationPayload from './parsePushNotificationPayload';
    
    export default function shouldShowPushNotification(pushPayload: PushPayload): boolean {
        Log.info('[PushNotification] push notification received', false, {pushPayload});
        const data = parsePushNotificationPayload(pushPayload.extras.payload);
    
        if (!data || data.reportID == null) {
            return true;
        }
    
        // Suppress the banner only when the user is already viewing this exact report.
        const topmostReportID = Navigation.getTopmostReportId();
        const shouldShow =
            String(data.reportID) !== topmostReportID ||
            !Visibility.isVisible() ||
            !Visibility.hasFocus();
    
        Log.info(`[PushNotification] ${shouldShow ? 'Showing' : 'Not showing'} notification`);
        return shouldShow;
    }

    This removes the currentUserAccountID Onyx subscription and the shouldShowReportActionNotification dependency entirely. When Visibility.isVisible() is false (app minimized / AppState !== 'active'), the expression short-circuits to true, so banners that arrive during the Airship transition window are also unblocked.

    2. Remove the now-unused currentUserAccountID Onyx subscription from the same file — the new body uses no Onyx reads.

    3. Add a unit test in tests/unit/shouldShowPushNotificationTest.ts mocking Navigation.getTopmostReportId, Visibility.isVisible, and Visibility.hasFocus to verify:

    • Banner shows when the user is on a different report.
    • Banner is suppressed when the user is on the same report and the app is visible + focused.
    • Banner shows when the app is not visible (minimized state), even if the report ID matches.

    No native code changes are required. The CustomNotificationProvider path and shouldShowReportActionNotification (still used by showReportActionNotification for Pusher-based local notifications) are untouched.

    What alternatives did you explore

    1. Guard only the Onyx-data paths inside shouldShowReportActionNotification for isRemote = true — e.g. return true when allReports is undefined. This is surgical but leaves the function with growing platform-specific branches. The notification-preference check is also semantically wrong here: the server already enforced preference when it decided to deliver the push; the foreground predicate's only job is "is the user already looking at this?"

    2. Keep shouldShowPushNotification unchanged and instead always return true from ForegroundNotifications/index.android.ts, relying on the native CustomNotificationProvider to suppress — this would show banners even when the user is in the originating chat, which is the one case where suppression is clearly correct.

    The proposed change is the smallest, most reliable fix: it removes all Onyx dependencies from the predicate and limits suppression to the one case (user is in the same chat, app in focus) where Airship foreground filtering is genuinely needed.

  3. changed the title [-][Exploratory] 92988 - PR 92497 - Android - Notification banner is not displayed if app is open or minimized[/-] [+][Exploratory] Android - Notification banner is not displayed if app is open or minimized[/+] on Jun 9, 2026
  4. melvin-bot commented on Jun 12, 2026

    @melvin-bot

    Whoops! This issue is 2 days overdue. Let's get this updated quick!

  5. melvin-bot commented on Jun 16, 2026

    @melvin-bot

    Still overdue 6 days?! Let's take care of this!

  6. melvin-bot commented on Jun 18, 2026

    @melvin-bot

    8 days overdue is a lot. Should this be a Weekly issue? If so, feel free to change it!

  7. melvin-bot commented on Jun 22, 2026

    @melvin-bot

    12 days overdue now... This issue's end is nigh!

  8. melvin-bot commented on Jun 25, 2026

    @melvin-bot

    This issue has not been updated in over 14 days. eroding to Weekly issue.

  9. melvin-bot commented on Jul 20, 2026

    @melvin-bot

    This issue has not been updated in over 15 days. eroding to Monthly issue.

    P.S. Is everyone reading this sure this is really a near-term priority? Be brave: if you disagree, go ahead and close it out. If someone disagrees, they'll reopen it, and if they don't: one less thing to do!

  10. melvin-bot commented on Oct 2, 2026

    @melvin-bot

    @applause-bot[bot], this Monthly task hasn't been acted upon in 6 weeks; closing.

    If you disagree, feel encouraged to reopen it -- but pick your least important issue to close instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions