You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[$175] [Sentry: APP-M66] Android: ReactModalHostView updateSystemAppearance "dialog must exist" #103778
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.
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
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().
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.
A modal opens, and showOrUpdate() posts the runnable to the dialog's decor view.
The modal closes before the next frame, for example on a quick open and close or on unmount. dismiss() sets dialog = null.
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.
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.
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?
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()
}
}
Re-apply and verify the patch with npx patch-package react-native.
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.
Activity
melvin-bot commentedon Oct 12, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @cretadn22 (
External)[-][Sentry: APP-M66] Android: ReactModalHostView updateSystemAppearance "dialog must exist"[/-][+][$175] [Sentry: APP-M66] Android: ReactModalHostView updateSystemAppearance "dialog must exist"[/+]melvin-bot commentedon Oct 12, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022109653602347544408
haiquywork commentedon Oct 12, 2026
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) addswindow.decorView.post { updateSystemAppearance() }insideReactModalHostView.showOrUpdate, right afternewDialog.show().App/patches/react-native/react-native+0.86.0+019+fix-modal-transparent-navigation-bar.patch
Lines 16 to 19 in 6502fec
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 thedialogfield to null.updateSystemAppearancethen does a checkNotNull ondialogand 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?
window.decorView.post { val d = dialog if (d != null && d.isShowing) updateSystemAppearance() }window.decorView.isAttachedToWindow).patches/react-native/details.mdto mention the guard.What alternative solutions did you explore? (Optional)
updateSystemAppearanceitself return early whendialogis null: also fine, but it changes more upstream code than needed, and the posted lambda is the only caller that runs late.MelvinBot commentedon Oct 12, 2026
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()inreact-native+0.86.0+019+fix-modal-transparent-navigation-bar.patch:18:window.decorView.post { updateSystemAppearance() }The crash happens in this order:
showOrUpdate()posts the runnable to the dialog's decor view.dismiss()setsdialog = null.updateSystemAppearance()starts withcheckNotNull(dialog) { "dialog must exist when we call updateProperties" }, which throwsIllegalStateExceptionand crashes the app.Evidence
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 directupdateSystemAppearance()call one line above it.dismiss()setsdialog = nullat line 210, andupdateSystemAppearance()runscheckNotNull(dialog)at line 440. Nothing cancels the posted runnable.if (dialog.isShowing) updateSystemAppearance()inupdateProperties(), is safe because it runs synchronously whiledialogis 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 callsupdateSystemAppearance()only ifdialogstill 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 inpatches/react-native/details.mdaccurate.This keeps the navigation bar fix from the PR above and removes the crash.
What alternative solutions did you explore? (Optional)
updateSystemAppearance()return early whendialogis null, instead of usingcheckNotNull. This also works, but it changes upstream behavior for every caller and could hide real bugs.Runnableand callremoveCallbacksindismiss(). 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
mountiny commentedon Oct 12, 2026
(Vit's agent) @cretadn22, can you review the proposal from MelvinBot on this issue? Please tell us which of these fits:
jloa-dev commentedon Oct 13, 2026
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 updatePropertiesinsideReactModalHostView.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, settingdialog = null. When the posted runnable subsequently runs,updateSystemAppearance()executes:checkNotNull(dialog) { "dialog must exist when we call updateProperties" }which throws an
IllegalStateExceptionand crashes the application becausedialogis null.What changes do you think we should make in order to solve the problem?
patches/react-native/react-native+0.86.0+019+fix-modal-transparent-navigation-bar.patch, safeguard the deferred runnable by verifying thatdialogstill exists and is currently showing:window.decorView.post { val currentDialog = dialog if (currentDialog != null && currentDialog.isShowing) { updateSystemAppearance() } }npx patch-package react-native.patches/react-native/details.mdwith the null-safety rationale for patch 019.What alternative solutions did you explore? (Optional)
updateSystemAppearance()in atry-catchblock: rejected because silently swallowing exceptions is defensive anti-pattern compared to explicit null/lifecycle checks.removeCallbacks: adds unnecessary runnable reference tracking when a simple guard check achieves the exact same protection cleanly.cretadn22 commentedon Oct 14, 2026
@mountiny We should ask for expert contributor help, because it needs deeper native