diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue31731.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue31731.cs new file mode 100644 index 000000000000..6a2100d681dd --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue31731.cs @@ -0,0 +1,89 @@ +namespace Maui.Controls.Sample.Issues +{ + [Issue(IssueTracker.Github, 31731, "Picker dialog causes crash when page is popped while dialog is open", PlatformAffected.Android)] + public class Issue31731 : NavigationPage + { + public Issue31731() : base(new MainPage()) + { + } + + public class MainPage : ContentPage + { + public MainPage() + { + var button = new Button + { + Text = "Navigate to Picker Page", + AutomationId = "navigateButton" + }; + + button.Clicked += OnNavigateClicked; + + var statusLabel = new Label + { + Text = "Status: Ready", + AutomationId = "statusLabel" + }; + + Content = new StackLayout + { + Padding = new Thickness(20), + Children = { statusLabel, button } + }; + } + + private void OnNavigateClicked(object sender, EventArgs e) + { + Navigation.PushAsync(new PickerPage()); + } + } + + public class PickerPage : ContentPage + { + public PickerPage() + { + var picker = new Picker + { + Title = "Select a color", + ItemsSource = new List { "Red", "Green", "Blue", "Yellow", "Purple" }, + AutomationId = "colorPicker" + }; + + var instructionsLabel = new Label + { + Text = "Tap the picker to open the dialog, then wait for auto navigation back (3 seconds). The app should not crash.", + AutomationId = "instructionsLabel", + Margin = new Thickness(0, 0, 0, 20) + }; + + var statusLabel = new Label + { + Text = "Status: Page loaded", + AutomationId = "pageStatusLabel" + }; + + Content = new StackLayout + { + Padding = new Thickness(20), + Children = { instructionsLabel, statusLabel, picker } + }; + } + + protected override void OnNavigatedTo(NavigatedToEventArgs args) + { + base.OnNavigatedTo(args); + + // Simulate the scenario: navigate back after 3 seconds + // This can cause a crash if the picker dialog is open + _ = Task.Run(async () => + { + await Task.Delay(3000); + await Dispatcher.DispatchAsync(async () => + { + await Navigation.PopToRootAsync(); + }); + }); + } + } + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31731.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31731.cs new file mode 100644 index 000000000000..ce1747520015 --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31731.cs @@ -0,0 +1,43 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue31731 : _IssuesUITest +{ + public Issue31731(TestDevice testDevice) : base(testDevice) + { + } + + public override string Issue => "Picker dialog causes crash when page is popped while dialog is open"; + + [Test] + [Category(UITestCategories.Picker)] + public void PickerDialogDoesNotCrashWhenPagePoppedWhileDialogOpen() + { + // Wait for the main page to load + App.WaitForElement("statusLabel"); + App.WaitForElement("navigateButton"); + + // Navigate to the picker page + App.Tap("navigateButton"); + + // Wait for picker page to load + App.WaitForElement("colorPicker"); + App.WaitForElement("pageStatusLabel"); + + // Open the picker dialog + App.Tap("colorPicker"); + + // Wait for a moment to ensure dialog is open, then wait for auto navigation + // The page will automatically pop after 3 seconds + // If the bug exists, this would cause a crash + System.Threading.Thread.Sleep(4000); // Wait longer than the 3-second delay + + // If we reach this point without crashing, the test passes + // Verify we're back on the main page + App.WaitForElement("statusLabel"); + App.WaitForElement("navigateButton"); + } +} \ No newline at end of file diff --git a/src/Core/src/Handlers/DatePicker/DatePickerHandler.Android.cs b/src/Core/src/Handlers/DatePicker/DatePickerHandler.Android.cs index e277fd74c626..7b8a858c753d 100644 --- a/src/Core/src/Handlers/DatePicker/DatePickerHandler.Android.cs +++ b/src/Core/src/Handlers/DatePicker/DatePickerHandler.Android.cs @@ -52,8 +52,7 @@ protected override void DisconnectHandler(MauiDatePicker platformView) { if (_dialog != null) { - _dialog.Hide(); - _dialog.Dispose(); + _dialog.Dismiss(); _dialog = null; } diff --git a/src/Core/src/Handlers/Picker/PickerHandler.Android.cs b/src/Core/src/Handlers/Picker/PickerHandler.Android.cs index db22061c4260..bdde910d0491 100644 --- a/src/Core/src/Handlers/Picker/PickerHandler.Android.cs +++ b/src/Core/src/Handlers/Picker/PickerHandler.Android.cs @@ -28,6 +28,14 @@ protected override void DisconnectHandler(MauiPicker platformView) { platformView.Click -= OnClick; + if (_dialog != null) + { + _dialog.ShowEvent -= OnDialogShown; + _dialog.DismissEvent -= OnDialogDismiss; + _dialog.Dismiss(); + _dialog = null; + } + base.DisconnectHandler(platformView); } diff --git a/src/Core/src/Handlers/TimePicker/TimePickerHandler.Android.cs b/src/Core/src/Handlers/TimePicker/TimePickerHandler.Android.cs index 8d148d3c5cbb..2c670ddfab69 100644 --- a/src/Core/src/Handlers/TimePicker/TimePickerHandler.Android.cs +++ b/src/Core/src/Handlers/TimePicker/TimePickerHandler.Android.cs @@ -26,7 +26,7 @@ protected override void DisconnectHandler(MauiTimePicker platformView) { if (_dialog != null) { - _dialog.Hide(); + _dialog.Dismiss(); _dialog = null; } }