Skip to content

HybridApp Android crash: getActivePolicy() returns null in SlidingMenuController Trips handler #82550

Description

@mountiny

Crash Description

https://console.firebase.google.com/u/0/project/expensify-mobile-app/crashlytics/app/android:org.me.mobiexpensifyg/issues/af3985b17bad9e9e418b98b7168b33f1?time=7d&types=crash&versions=9.3.19-4%20(509031904);9.3.19-3%20(509031903);9.3.19-0%20(509031900)&sessionEventKey=699301EE027500015D088E24447AF092_2185872096679185833

On HybridApp Android, a fatal RuntimeException occurs in SlidingMenuController.js when the user navigates to the Trips page. The crash is caused by calling .getID() on a null return value from getActivePolicy().

Crash location: Mobile-Expensify/app/Controllers/SlidingMenuController.js line 397:

policyID: self.app.getActivePolicy().getID()

Root Cause Analysis

This is inside the TRIPS page handler, which first checks self.app.isTravelEnabled() (line 395). If that returns true, it proceeds to call self.app.getActivePolicy().getID() — but getActivePolicy() can return null.

Why it returns null: In Mobile-Expensify/app/expensify.js (line 6077), getActivePolicy() returns null when no valid policy is found, and also triggers a sign-out:

if (!_.isPolicy(policy)) {
    yaplShowGrowl('Something went wrong, please sign in and try again');
    this.signOut();
    return null;
}

The race condition: isTravelEnabled() calls getActivePolicy() internally (line 7707) and succeeds. But by the time the code calls getActivePolicy() again on line 397, the policy may have been invalidated (e.g., sign-out started, or policy data cleared), causing null to be returned and .getID() to crash with a TypeError: Cannot read properties of null.

Proposed Fix

Add a null guard before calling .getID(), or cache the activePolicy result from the isTravelEnabled() check:

if (pageName === SlidingMenuController.PAGES.TRIPS) {
    var activePolicy = self.app.getActivePolicy();
    if (activePolicy && self.app.isTravelEnabled()) {
        API.Travel_GenerateSpotnanaToken({
            policyID: activePolicy.getID()
        })
        // ... rest of handler
    }
}

This avoids the double call to getActivePolicy() and eliminates the window for the race condition.

Related Issues

Issue OwnerCurrent Issue Owner: @QichenZhu

Activity

  1. added
    InternalRequires API changes or must be handled by Expensify staff
    BugSomething is broken. Auto assigns a BugZero manager.
    on Feb 16, 2026
  2. melvin-bot commented on Feb 16, 2026

    @melvin-bot

    Triggered auto assignment to @greg-schroeder (Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details. Please add this bug to a GH project, as outlined in the SO.

  3. added
    ExternalAdded to denote the issue can be worked on by a contributor
    and removed
    InternalRequires API changes or must be handled by Expensify staff
    on Feb 16, 2026
  4. added
    Help WantedApply this label when an issue is open to proposals by contributors
    on Feb 16, 2026
  5. melvin-bot commented on Feb 16, 2026

    @melvin-bot

    Triggered auto assignment to Contributor-plus team member for initial proposal review - @Ollyws (External)

  6. melvin-bot commented on Feb 16, 2026

    @melvin-bot

    Unable to auto-create job on Upwork. The BZ team member should create it manually for this issue.

  7. MelvinBot commented on Feb 18, 2026

    @MelvinBot
    Contributor

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    On HybridApp Android, a fatal RuntimeException occurs when the user taps any row in the sliding menu (not just Trips). The crash is caused by calling .getID() on a null return value from getActivePolicy() in SlidingMenuController.js. The getActivePolicy() call is at the top of the onRowTappedCallbacks handler — before any page-specific branching — so it crashes regardless of which menu item is tapped whenever getActivePolicy() returns null.

    What is the root cause of that problem?

    In _viewSetOnRowTappedCallback (Mobile-Expensify/app/Controllers/SlidingMenuController.js), the very first line of the callback unconditionally calls self.app.getActivePolicy().getID() with no null guard:

    var onRowTappedCallbacks = function (rowIndex) {
        var row = self._navigationList[rowIndex];
        var pageName = row.id;
        var activePolicyID = self.app.getActivePolicy().getID(); // crashes here
        ...

    getActivePolicy() (Mobile-Expensify/app/expensify.js:6076) explicitly returns null when no valid policy is found and also triggers this.signOut() as a side effect. When the user taps a menu item while no valid policy exists (e.g., during app restore, before policies fully load, or during sign-out), .getID() is called on null and the app crashes.

    There is a secondary unguarded call site: isTravelEnabled() (Mobile-Expensify/app/expensify.js:7706) also calls getActivePolicy() and immediately accesses activePolicy.getTravelSettings() and activePolicy.isPolicyProvisioned() without null checks, which would crash if getActivePolicy() returns null there.

    Note: The issue description frames this as a race condition between two sequential getActivePolicy() calls. While there technically are two calls (one at the top of the handler, one inside isTravelEnabled()), the primary crash happens at the very first call — before any TRIPS-specific code runs. It's a missing null guard, not a race between the two calls.

    What changes do you think we should make in order to solve the problem?

    Two changes in Mobile-Expensify:

    1. In SlidingMenuController.js — Move the activePolicyID assignment inside the TRIPS branch (since only the TRIPS path uses it), and add a null guard following the established codebase pattern:
    if (pageName === SlidingMenuController.PAGES.TRIPS) {
        var activePolicy = self.app.getActivePolicy();
        if (!_.isPolicy(activePolicy)) {
            return;
        }
        var activePolicyID = activePolicy.getID();
        if (self.app.isTravelEnabled()) {
            // ... existing Travel_GenerateSpotnanaToken logic using activePolicyID
        }
        // ... existing fallback navigateToNewDot logic using activePolicyID
        return;
    }
    1. In expensify.js — Add a null guard in isTravelEnabled() to return false if getActivePolicy() returns null:
    var activePolicy = this.getActivePolicy();
    if (!activePolicy) {
        return false;
    }

    Both changes follow the established pattern used in SettingsController.updateListItems(), CreateExpenseMenuController, and DropDownMenuController where the result of getActivePolicy() is stored in a variable and checked with _.isPolicy() before use.

    What alternative solutions did you explore? (Optional)

    An alternative would be to make getActivePolicy() itself never return null (e.g., return a default/empty policy object). However, this would mask a legitimate error state and the existing signOut() side effect is intentional. The null guard approach is consistent with how this has been fixed elsewhere in the codebase (PR #13663 for #69370).


    Next Steps for Contributor+ team: Reply with @MelvinBot implement this to create a draft PR, or @MelvinBot <your feedback> to refine this analysis.

  8. Ollyws commented on Feb 19, 2026

    @Ollyws
    Contributor

    I can't seem to reproduce this by navigating the the trips page.

  9. mountiny commented on Feb 19, 2026

    @mountiny
    ContributorAuthor

    It might not be easily reproducible, is it happening for more users?

  10. 115 remaining items

  11. melvin-bot commented on Jun 11, 2026

    @melvin-bot

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

  12. melvin-bot commented on Jun 15, 2026

    @melvin-bot

    @QichenZhu Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!

  13. QichenZhu commented on Jun 16, 2026

    @QichenZhu
    Contributor

    I think we can close this.

  14. mountiny commented on Jun 16, 2026

    @mountiny
    ContributorAuthor

    Sounds like we are done

  15. moved this from Done to SUBISSUE in [#whatsnext] #qualityon Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributorMelvin TriageAutomatic AI Analysis of an issue

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions