Fix crash when displaying alerts on unloaded pages#33288
Fix crash when displaying alerts on unloaded pages#33288kubaflo wants to merge 5 commits intodotnet:mainfrom
Conversation
src/Controls/src/Core/Page/Page.cs
Outdated
| if (Window is null) | ||
| { | ||
| // Complete the task with cancel result | ||
| args.SetResult(cancel); |
There was a problem hiding this comment.
Could we add a log here? As a maui developer would be great to know why the Alert didn't show
There was a problem hiding this comment.
Good call! Something like this?
if (Window is null)
{
args.SetResult("DisplayActionSheetAsync: Window is null, action sheet will not be shown. This can happen if the page is not attached to a window.");
return args.Result.Task;
}There was a problem hiding this comment.
Not sure how that will be displayed. But yeah, the message looks good. I was thinking in something like Trace.WriteLine to be shown into the logs/output window
PureWeen
left a comment
There was a problem hiding this comment.
From Copilot
UITest Validation Issue
Great fix - the Page.cs changes look solid! However, I found an issue with the UITest.
**Problem**: I reverted your Page.cs fix and ran the test twice - it still passed both times even though the app crashed (device logs showed "app died, no saved state").
**Root cause**: The test asserts `status.Does.Not.Contain("NullReferenceException")`, but the fatal crash kills the app before the exception can be written to the
StatusLabel. The test reads the last status before the crash ("Showing alert from unloaded page...") which doesn't contain that text.
**Suggested fix**: Check for positive success instead of absence of error:
```csharp
// Current (doesn't catch regression):
Assert.That(status, Does.Not.Contain("NullReferenceException"), ...);
// Suggested (will fail without fix):
Assert.That(status, Does.Contain("✅"),
"App should show success status after DisplayAlertAsync completes");
To verify yourself:
- Revert src/Controls/src/Core/Page/Page.cs to before your fix
- Run: pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform android -TestFilter "Issue33287"
- Test should fail but currently passes
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 33288Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 33288" |
There was a problem hiding this comment.
Pull request overview
Fixes a crash in Microsoft.Maui.Controls.Page when alert-related APIs are invoked after a page has been navigated away/unloaded (i.e., Window becomes null), and adds a UI test reproduction to prevent regressions.
Changes:
- Add
Window == nullhandling forDisplayActionSheetAsync,DisplayAlertAsync, andDisplayPromptAsyncinPage.cs. - Add a HostApp issue page to reproduce the unload + delayed dialog scenario.
- Add an Appium/NUnit UI test validating the app doesn’t crash and no
NullReferenceExceptionis surfaced.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
| src/Controls/src/Core/Page/Page.cs | Adds Window null handling and result completion defaults for alert/action sheet/prompt APIs. |
| src/Controls/tests/TestCases.HostApp/Issues/Issue33287.xaml.cs | Adds a navigation-based repro page which triggers a delayed DisplayAlertAsync after navigating away. |
| src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33287.cs | Adds an Appium UI test to exercise the scenario and detect crashes/exceptions. |
src/Controls/src/Core/Page/Page.cs
Outdated
| if (IsPlatformEnabled) | ||
| Window.AlertManager.RequestPrompt(this, args); | ||
| else | ||
| _pendingActions.Add(() => Window.AlertManager.RequestPrompt(this, args)); | ||
| _pendingActions.Add(() => | ||
| { | ||
| // Check again in case window was detached while waiting | ||
| if (Window is not null) | ||
| Window.AlertManager.RequestPrompt(this, args); | ||
| else | ||
| args.SetResult(null); | ||
| }); |
| public partial class Issue33287 : NavigationPage | ||
| { | ||
| public Issue33287() : base(new Issue33287MainPage()) | ||
| { | ||
| } | ||
| } | ||
|
|
||
| public partial class Issue33287MainPage : ContentPage |
src/Controls/src/Core/Page/Page.cs
Outdated
| // If page is no longer attached to a window (e.g., navigated away), ignore the action sheet request | ||
| if (Window is null) | ||
| { | ||
| Trace.WriteLine("DisplayActionSheetAsync: Window is null, action sheet will not be shown. This can happen if the page is not attached to a window."); | ||
| args.SetResult(cancel); | ||
| return args.Result.Task; | ||
| } |
src/Controls/src/Core/Page/Page.cs
Outdated
| if (IsPlatformEnabled) | ||
| Window.AlertManager.RequestActionSheet(this, args); | ||
| else | ||
| _pendingActions.Add(() => Window.AlertManager.RequestActionSheet(this, args)); | ||
| _pendingActions.Add(() => | ||
| { | ||
| // Check again in case window was detached while waiting | ||
| if (Window is not null) | ||
| Window.AlertManager.RequestActionSheet(this, args); | ||
| else |
src/Controls/src/Core/Page/Page.cs
Outdated
| if (IsPlatformEnabled) | ||
| Window.AlertManager.RequestAlert(this, args); | ||
| else | ||
| _pendingActions.Add(() => Window.AlertManager.RequestAlert(this, args)); | ||
| _pendingActions.Add(() => | ||
| { | ||
| // Check again in case window was detached while waiting | ||
| if (Window is not null) | ||
| Window.AlertManager.RequestAlert(this, args); | ||
| else | ||
| args.SetResult(false); | ||
| }); |
src/Controls/src/Core/Page/Page.cs
Outdated
| // If page is no longer attached to a window (e.g., navigated away), ignore the prompt request | ||
| if (Window is null) | ||
| { | ||
| // Complete the task with null result | ||
| args.SetResult(null); | ||
| return args.Result.Task; | ||
| } |
| // Wait for the delayed DisplayAlertAsync to be triggered (5 seconds + buffer) | ||
| System.Threading.Thread.Sleep(6000); | ||
|
|
src/Controls/src/Core/Page/Page.cs
Outdated
| // If page is no longer attached to a window (e.g., navigated away), ignore the action sheet request | ||
| if (Window is null) | ||
| { | ||
| Trace.WriteLine("DisplayActionSheetAsync: Window is null, action sheet will not be shown. This can happen if the page is not attached to a window."); |
src/Controls/src/Core/Page/Page.cs
Outdated
| // If page is no longer attached to a window (e.g., navigated away), ignore the alert request | ||
| if (Window is null) | ||
| { | ||
| // Complete the task with default result (cancel) | ||
| args.SetResult(false); | ||
| return args.Result.Task; | ||
| } |
|
/rebase |
8a80beb to
43f5750
Compare
Added null checks for the Window property in DisplayAlertAsync, DisplayActionSheetAsync, and DisplayPromptAsync methods in Page.cs to prevent NullReferenceException when the page is no longer attached to a window. New UI tests verify that async alert requests do not crash the app after navigating away from the page.
Added a Trace.WriteLine statement to log when DisplayActionSheetAsync is called but the page is not attached to a window. This helps with debugging scenarios where the action sheet is not shown due to the page being detached.
…e tests - Capture Window in local variable to fix TOCTOU race (Copilot review) - Only early-return when IsPlatformEnabled && Window is null to preserve pending action queuing for not-yet-attached pages (Copilot review) - Add consistent Trace.WriteLine logging across all three methods (pictos) - Assert positive success (contains) instead of absence of error (PureWeen) - Replace Thread.Sleep with polling loop for deterministic wait (Copilot review) - Rename .xaml.cs to .cs and remove partial keywords (no XAML file) (Copilot review) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace hand-rolled Thread.Sleep polling loop with the built-in WaitForTextToBePresentInElement helper (review feedback) - Reduce Task.Delay from 5s to 3s for faster CI runs while still allowing enough time to navigate back before the alert fires Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous test updated a MainPage label from a detached SecondPage's async on iOS the UI update never reaches the visiblecontinuation label. Simplified to verify the app remains responsive after the delayed DisplayAlertAsync fires on the unloaded page. Without the fix the NRE crashes the app and the assertion fails (positive test). Also reduced delay from 5s to 2s for faster test execution. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fix for Issue #33287 - DisplayAlertAsync NullReferenceException
Issue Summary
Reporter: @mfeingol
Platforms Affected: All (Android reported, likely all)
Version: 10.0.20
Problem: Calling
DisplayAlertAsync(orDisplayActionSheetAsync,DisplayPromptAsync) on a page that has been navigated away from results in aNullReferenceException, crashing the app.Reproduction Scenario:
OnAppearing()DisplayAlertAsyncWindowproperty is nullRoot Cause
Location:
src/Controls/src/Core/Page/Page.cslines 388, 390When a page is unloaded (removed from navigation stack), its
Windowproperty becomesnull. TheDisplayAlertAsync,DisplayActionSheetAsync, andDisplayPromptAsyncmethods accessedWindow.AlertManagerwithout null checking:Stack Trace (from issue report):
Solution
Added null checks for
Windowproperty in three methods. WhenWindowis null (page unloaded), complete the task gracefully with sensible defaults instead of crashing.Files Modified
src/Controls/src/Core/Page/Page.csDisplayAlertAsync (lines 376-407)
Window.AlertManagerfalse(cancel) when window is nullDisplayActionSheetAsync (lines 321-347)
Window.AlertManagercancelbutton text when window is nullDisplayPromptAsync (lines 422-463)
Window.AlertManagernullwhen window is nullImplementation
Why this approach:
Testing
Reproduction Test Created
Files:
src/Controls/tests/TestCases.HostApp/Issues/Issue33287.xaml.cs- Test page with navigationsrc/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33287.cs- NUnit UI testTest Scenario:
DisplayAlertAsyncafter 5-second delayTest Results
Before Fix:
After Fix:
Platform Tested: Android API 36 (Pixel 9 emulator)
Edge Cases Verified
Behavior Changes
Before Fix
NullReferenceExceptionAfter Fix
DisplayAlertAsync→false(cancel)DisplayActionSheetAsync→ cancel button textDisplayPromptAsync→nullRationale: If user navigated away, they didn't see the alert, so returning "cancel" is semantically correct.
Breaking Changes
None. This is purely a bug fix that prevents crashes.
Impact:
Additional Notes
Why This Wasn't Caught Earlier
This is a timing/race condition issue:
Workaround (Before Fix)
Users had to manually check
IsLoadedproperty:With this fix, this workaround is no longer necessary.
Files Changed Summary
Related Issues
WindowpropertyWindow.accesses in Page class for similar issuesPR Checklist