Skip to content

[$175] [Sentry: APP-M66] Android: ReactModalHostView updateSystemAppearance "dialog must exist" #103778

Open

Description

@mountiny

Sentry

https://expensify.sentry.io/issues/APP-M66

Impact (snapshot at filing, 2026-10-12)

  • Users (total since first seen): 19
  • Users (last 30d, releases 9.5.5 to 9.5.7 only): 19
  • Events (30d window): 19
  • First seen: 2026-09-21
  • Last seen: ongoing
  • Platform: Android hybrid app
  • App version(s): 9.5.5
  • Affected route(s): Report screen
  • Mechanism: UncaughtExceptionHandler

Stack trace (top frames, first-party only)

ReactModalHostView.showOrUpdate$lambda$5 (ReactModalHostView.kt:348)
ReactModalHostView.updateSystemAppearance (ReactModalHostView.kt:440)

Suspected cause

A posted updateSystemAppearance runs after the modal dialog is already dismissed, so React Native throws 'dialog must exist when we call updateProperties'.

Reproduction

Unknown. See the events linked from the Sentry issue.

Related

  • Sibling Sentry IDs: none
  • Prior GH issues: none found
Upwork Automation - Do Not Edit
Issue OwnerCurrent Issue Owner: @cretadn22

Activity

  1. self-assigned this
    on Oct 12, 2026
  2. added
    ExternalAdded to denote the issue can be worked on by a contributor
    BugSomething is broken. Auto assigns a BugZero manager.
    on Oct 12, 2026
  3. melvin-bot commented on Oct 12, 2026

    @melvin-bot

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

  4. changed the title [-][Sentry: APP-M66] Android: ReactModalHostView updateSystemAppearance "dialog must exist"[/-] [+][$175] [Sentry: APP-M66] Android: ReactModalHostView updateSystemAppearance "dialog must exist"[/+] on Oct 12, 2026
  5. melvin-bot commented on Oct 12, 2026

    @melvin-bot
  6. haiquywork commented on Oct 12, 2026

    @haiquywork

    Proposal

    What is the root cause of that problem?

    Our own React Native patch react-native+0.86.0+019+fix-modal-transparent-navigation-bar.patch (added in #69004) adds window.decorView.post { updateSystemAppearance() } inside ReactModalHostView.showOrUpdate, right after newDialog.show().

    updateSystemAppearance()
    + // Activity and dialog insets can settle after attach; re-sync so nav icon style matches.
    + window.decorView.post { updateSystemAppearance() }
    window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)

    The post runs on a later UI-thread tick. If the modal is dismissed in between (common with quick-closing Report screen modals), dismiss() has already set the dialog field to null. updateSystemAppearance then does a checkNotNull on dialog and throws, which matches the trace (showOrUpdate$lambda$5 -> updateSystemAppearance). The unpatched code only called it synchronously right after show(), so it could not hit this.

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

    • Update patch 019 so the deferred callback is safe once the modal is gone, and regenerate the patch file with patch-package.
    • Heart of the change:
    window.decorView.post {
      val d = dialog
      if (d != null && d.isShowing) updateSystemAppearance()
    }
    • Optionally also have the post callback skip if the decorView is no longer attached (window.decorView.isAttachedToWindow).
    • Update the 019 entry in patches/react-native/details.md to mention the guard.
    • Testing: there's no JS unit test for this native patch. Verify manually on Android by opening a modal on a report and dismissing it immediately (rapid open/close), and check there's no crash.

    What alternative solutions did you explore? (Optional)

    • Wrap the call in try/catch: hides the error rather than checking the state, so worse.
    • Make updateSystemAppearance itself return early when dialog is null: also fine, but it changes more upstream code than needed, and the posted lambda is the only caller that runs late.
    • Remove the post: it would bring back the nav bar icon style bug that patch 019 was fixing.
  7. MelvinBot commented on Oct 12, 2026

    @MelvinBot
    Contributor

    Proposal

    What is the root cause of that problem?

    Our react-native patch schedules a delayed updateSystemAppearance() call that can run after the modal has closed.

    Fix: Navigation bar translucency on Android added this line to ReactModalHostView.showOrUpdate() in react-native+0.86.0+019+fix-modal-transparent-navigation-bar.patch:18:

    window.decorView.post { updateSystemAppearance() }

    The crash happens in this order:

    1. A modal opens, and showOrUpdate() posts the runnable to the dialog's decor view.
    2. The modal closes before the next frame, for example on a quick open and close or on unmount. dismiss() sets dialog = null.
    3. The posted runnable runs. updateSystemAppearance() starts with checkNotNull(dialog) { "dialog must exist when we call updateProperties" }, which throws IllegalStateException and crashes the app.
    Evidence
    • The Sentry frame showOrUpdate$lambda$5 (ReactModalHostView.kt:348) matches line 348 of the patched file, which holds the posted call. The crash is in the lambda, not in the direct updateSystemAppearance() call one line above it.
    • In the patched file, dismiss() sets dialog = null at line 210, and updateSystemAppearance() runs checkNotNull(dialog) at line 440. Nothing cancels the posted runnable.
    • The PR merged on 2026-09-08. Sentry first saw the crash on 2026-09-21 in 9.5.5, the first release that shipped it.
    • The other call the patch adds, if (dialog.isShowing) updateSystemAppearance() in updateProperties(), is safe because it runs synchronously while dialog is non-null.

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

    In patch 019, update the posted runnable so it calls updateSystemAppearance() only if dialog still exists and is showing when the runnable runs. Otherwise, it does nothing. Make this a small change to the patch itself, and keep the patch header in patches/react-native/details.md accurate.

    This keeps the navigation bar fix from the PR above and removes the crash.

    What alternative solutions did you explore? (Optional)

    • Make updateSystemAppearance() return early when dialog is null, instead of using checkNotNull. This also works, but it changes upstream behavior for every caller and could hide real bugs.
    • Keep a reference to the Runnable and call removeCallbacks in dismiss(). This is more precise but adds more code to the patch for the same result.

    Next Steps for Contributor+ team:
    To accept: @MelvinBot implement [this](https://github.com/Expensify/App/issues/103778) to create a draft PR.
    To refine: @MelvinBot <your feedback>
    To reject: Explain why you are rejecting Melvin's proposal.


    view run

  8. mountiny commented on Oct 12, 2026

    @mountiny
    ContributorAuthor

    (Vit's agent) @cretadn22, can you review the proposal from MelvinBot on this issue? Please tell us which of these fits:

    1. This is ready to open for contributors as it stands.
    2. We should ask for expert contributor help, because it needs deeper native, navigation or FlashList knowledge.
    3. The proposal needs changes first.
  9. jloa-dev commented on Oct 13, 2026

    @jloa-dev

    Proposal

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

    On Android, a Sentry crash (APP-M66) occurs with java.lang.IllegalStateException: dialog must exist when we call updateProperties inside ReactModalHostView.updateSystemAppearance.

    What is the root cause of that problem?

    In our React Native patch patches/react-native/react-native+0.86.0+019+fix-modal-transparent-navigation-bar.patch#L18, we schedule a deferred UI tick to update system bar appearances:

    window.decorView.post { updateSystemAppearance() }

    When a modal is rapidly closed or unmounted immediately after opening, dismiss() runs before the posted runnable executes on the looper queue, setting dialog = null. When the posted runnable subsequently runs, updateSystemAppearance() executes:

    checkNotNull(dialog) { "dialog must exist when we call updateProperties" }

    which throws an IllegalStateException and crashes the application because dialog is null.

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

    1. In patches/react-native/react-native+0.86.0+019+fix-modal-transparent-navigation-bar.patch, safeguard the deferred runnable by verifying that dialog still exists and is currently showing:
    window.decorView.post {
        val currentDialog = dialog
        if (currentDialog != null && currentDialog.isShowing) {
            updateSystemAppearance()
        }
    }
    1. Re-apply and verify the patch with npx patch-package react-native.
    2. Update patches/react-native/details.md with the null-safety rationale for patch 019.

    What alternative solutions did you explore? (Optional)

    • Wrapping updateSystemAppearance() in a try-catch block: rejected because silently swallowing exceptions is defensive anti-pattern compared to explicit null/lifecycle checks.
    • Cancelling the runnable via removeCallbacks: adds unnecessary runnable reference tracking when a simple guard check achieves the exact same protection cleanly.
  10. cretadn22 commented on Oct 14, 2026

    @cretadn22
    Contributor

    @mountiny We should ask for expert contributor help, because it needs deeper native

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 contributor

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions