Skip to content

Commit e202fa8

Browse files
committed
fix(Maui): pop dialog from stack before ShowDialogAsync callback
Remove the dialog from IDialogContainer.DialogStack before invoking the completion callback so callers can navigate immediately after the dialog closes (GitHub #3395). Add DryIoc regression test covering GoBackAsync after ShowDialogAsync.
1 parent c0000fe commit e202fa8

4 files changed

Lines changed: 104 additions & 0 deletions

File tree

src/Maui/Prism.Maui/Dialogs/DialogServiceBase.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ async Task DialogAware_RequestClose(IDialogResult outResult)
4646
return;
4747
}
4848

49+
// Remove before callbacks so ShowDialogAsync's synchronous TrySetResult cannot resume
50+
// callers that run GoBackAsync while the stack still shows this modal (#3395).
51+
if (dialogModal is not null)
52+
IDialogContainer.DialogStack.Remove(dialogModal);
53+
4954
await callback.Invoke(result);
5055
GC.Collect();
5156
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using Prism.Dialogs;
2+
using Prism.DryIoc.Maui.Tests.Mocks.ViewModels;
3+
using Prism.DryIoc.Maui.Tests.Mocks.Views;
4+
using Prism.Ioc;
5+
using Prism.Navigation.Xaml;
6+
7+
namespace Prism.DryIoc.Maui.Tests.Fixtures.Navigation;
8+
9+
/// <summary>Regression tests for dialog + navigation interactions (GitHub #3395).</summary>
10+
public class DialogNavigationRegressionFixture : TestBase
11+
{
12+
public DialogNavigationRegressionFixture(ITestOutputHelper testOutputHelper)
13+
: base(testOutputHelper)
14+
{
15+
}
16+
17+
[Fact]
18+
public async Task Issue3395_GoBackAsync_After_ShowDialogAsync_Completes_Pops_Navigation_Page()
19+
{
20+
var mauiApp = CreateBuilder(prism => prism
21+
.RegisterTypes(c =>
22+
{
23+
c.RegisterDialog<TestDialogView, TestDialogViewModel>("TestDialog");
24+
})
25+
.CreateWindow("NavigationPage/MockViewA/MockViewB"))
26+
.Build();
27+
var window = GetWindow(mauiApp);
28+
var navPage = Assert.IsAssignableFrom<Microsoft.Maui.Controls.NavigationPage>(window.Page);
29+
Assert.IsType<MockViewB>(navPage.CurrentPage);
30+
Assert.Equal(2, navPage.Navigation.NavigationStack.Count);
31+
32+
var container = navPage.CurrentPage.GetContainerProvider();
33+
var dialogService = container.Resolve<IDialogService>();
34+
var navigationService = Prism.Navigation.Xaml.Navigation.GetNavigationService(navPage.CurrentPage);
35+
36+
var dialogResult = await dialogService.ShowDialogAsync("TestDialog");
37+
Assert.NotNull(dialogResult);
38+
Assert.Null(dialogResult.Exception);
39+
Assert.Equal(ButtonResult.OK, dialogResult.Result);
40+
Assert.Empty(IDialogContainer.DialogStack);
41+
42+
var goBackResult = await navigationService.GoBackAsync();
43+
Assert.True(goBackResult.Success);
44+
Assert.Null(goBackResult.Exception);
45+
Assert.IsType<MockViewA>(navPage.CurrentPage);
46+
Assert.Single(navPage.Navigation.NavigationStack);
47+
}
48+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using Prism.Dialogs;
2+
using Prism.Mvvm;
3+
4+
namespace Prism.DryIoc.Maui.Tests.Mocks.ViewModels;
5+
6+
/// <summary>Minimal <see cref="IDialogAware"/> for dialog regression tests; view closes itself on <see cref="VisualElement.Loaded"/>.</summary>
7+
public class TestDialogViewModel : BindableBase, IDialogAware
8+
{
9+
private DialogCloseListener _requestClose;
10+
11+
public TestDialogViewModel()
12+
{
13+
_requestClose = new DialogCloseListener();
14+
}
15+
16+
public DialogCloseListener RequestClose
17+
{
18+
get => _requestClose;
19+
set => SetProperty(ref _requestClose, value);
20+
}
21+
22+
public bool CanCloseDialog() => true;
23+
24+
public void OnDialogClosed()
25+
{
26+
}
27+
28+
public void OnDialogOpened(IDialogParameters parameters)
29+
{
30+
}
31+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Prism.Dialogs;
2+
3+
namespace Prism.DryIoc.Maui.Tests.Mocks.Views;
4+
5+
/// <summary>Border-based dialog; auto-closes on <see cref="VisualElement.Loaded"/> after <see cref="IDialogContainer.DialogView"/> is wired.</summary>
6+
public class TestDialogView : Border
7+
{
8+
public TestDialogView()
9+
{
10+
Content = new Label { Text = "TestDialog" };
11+
Loaded += OnLoaded;
12+
}
13+
14+
private void OnLoaded(object? sender, EventArgs e)
15+
{
16+
Loaded -= OnLoaded;
17+
if (BindingContext is IDialogAware aware)
18+
aware.RequestClose.Invoke(ButtonResult.OK);
19+
}
20+
}

0 commit comments

Comments
 (0)