Skip to content

Commit 87416e7

Browse files
Fix OnNavigatedTo not firing after PopModalAsync when tab is changed inside modal (#35803)
<!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root Cause When a modal is closed through `PopModalAsync`, the framework cascades `SendNavigatedTo(Pop)` down to the current child of the `TabbedPage` (for example, `Tab1`). Inside `SendNavigatedTo`, a guard (`if (HasNavigatedTo) return`) prevents duplicate `OnNavigatedTo` events from firing. This guard was introduced in PR #31931 to fix issue #23902 . The issue occurs when the user switches tabs while a modal is open. That tab switch triggers a normal `Replace` navigation within the `TabbedPage`, which sets `Tab1.HasNavigatedTo = true`. Later, when the modal is closed, `SendNavigatedFrom` is invoked only on the modal page itself — the tab child pages never receive `SendNavigatedFrom`, so their `HasNavigatedTo` flags are not reset. As a result, when the subsequent `Pop` cascade reaches `Tab1`, its `HasNavigatedTo` flag is still `true` from the earlier in-modal tab switch. The duplicate-event guard therefore incorrectly suppresses the `OnNavigatedTo` event that should fire when returning to the page after the modal closes. ### Description of Change Updated `Page.SendNavigatedTo` so that when navigation cascades into a container child page, the child’s `HasNavigatedTo` flag is reset before cascading — but only for `NavigationType.Pop`. `Pop` represents returning to an existing page and should always trigger a legitimate new `OnNavigatedTo` event. `Push` and `Replace` flows remain unchanged, preserving the original duplicate-event protection introduced for issue #23902. ### Regression details The regression was introduced by PR #[31931](#31931) ### Issues Fixed Fixes #35756 Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Screenshots | Before Issue Fix | After Issue Fix | |------------------|-----------------| | <video width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/d458c5ae-2ee5-4604-b61a-eb8846b90111" /> | <video width="350" alt="withfix" src="https://github.com/user-attachments/assets/93320bc6-1e24-4885-b964-f3bc0ad8aac5" /> |
1 parent 4ccd542 commit 87416e7

3 files changed

Lines changed: 174 additions & 1 deletion

File tree

src/Controls/src/Core/Page/Page.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,7 @@ internal Toolbar Toolbar
853853

854854
internal void SendNavigatedTo(NavigatedToEventArgs args)
855855
{
856+
// Prevent duplicate OnNavigatedTo during a single navigation burst (fixes #23902).
856857
if (HasNavigatedTo)
857858
{
858859
return;
@@ -861,7 +862,21 @@ internal void SendNavigatedTo(NavigatedToEventArgs args)
861862
HasNavigatedTo = true;
862863
NavigatedTo?.Invoke(this, args);
863864
OnNavigatedTo(args);
864-
(this as IPageContainer<Page>)?.CurrentPage?.SendNavigatedTo(args);
865+
866+
// Cascade to child page (e.g. TabbedPage → CurrentPage).
867+
// On Pop, reset the child flag first — a prior tab change while a modal was open
868+
// can leave it true, which would incorrectly block the pop-return (fixes #35756).
869+
// PopToRoot is excluded: SendNavigatedFrom already resets all flags before PopToRoot cascades.
870+
var containerChild = (this as IPageContainer<Page>)?.CurrentPage;
871+
if (containerChild is not null)
872+
{
873+
if (args.NavigationType == NavigationType.Pop)
874+
{
875+
containerChild.HasNavigatedTo = false;
876+
}
877+
878+
containerChild.SendNavigatedTo(args);
879+
}
865880
}
866881

867882
internal void SendNavigatingFrom(NavigatingFromEventArgs args)
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
using Microsoft.Maui.Controls;
2+
3+
namespace Maui.Controls.Sample.Issues
4+
{
5+
[Issue(IssueTracker.Github, 35756, "OnNavigatedTo does not fire after PopModalAsync when tab was changed from inside the modal", PlatformAffected.All)]
6+
public class Issue35756TabbedPage : TabbedPage
7+
{
8+
public Issue35756TabbedPage()
9+
{
10+
Title = "Issue35756";
11+
Children.Add(new Issue35756Tab1Page());
12+
Children.Add(new Issue35756Tab2Page(this));
13+
}
14+
}
15+
16+
public class Issue35756Tab1Page : ContentPage
17+
{
18+
readonly Label _navigatedToCountLabel;
19+
int _navigatedToCount;
20+
21+
public Issue35756Tab1Page()
22+
{
23+
Title = "Tab 1";
24+
_navigatedToCountLabel = new Label
25+
{
26+
AutomationId = "Tab1NavigatedToCount",
27+
Text = "NavigatedTo count: 0"
28+
};
29+
Content = new StackLayout
30+
{
31+
Padding = 20,
32+
Children =
33+
{
34+
new Label { Text = "Tab 1", FontSize = 18, AutomationId = "Tab1Content" },
35+
_navigatedToCountLabel
36+
}
37+
};
38+
}
39+
40+
protected override void OnNavigatedTo(NavigatedToEventArgs args)
41+
{
42+
base.OnNavigatedTo(args);
43+
_navigatedToCount++;
44+
_navigatedToCountLabel.Text = $"NavigatedTo count: {_navigatedToCount}";
45+
}
46+
}
47+
48+
public class Issue35756Tab2Page : ContentPage
49+
{
50+
readonly TabbedPage _tabbedPage;
51+
52+
public Issue35756Tab2Page(TabbedPage tabbedPage)
53+
{
54+
Title = "Tab 2";
55+
_tabbedPage = tabbedPage;
56+
57+
var pushModalButton = new Button
58+
{
59+
Text = "Push Modal",
60+
AutomationId = "PushModalButton"
61+
};
62+
pushModalButton.Clicked += OnPushModalClicked;
63+
64+
Content = new StackLayout
65+
{
66+
Padding = 20,
67+
Children =
68+
{
69+
new Label { Text = "Tab 2", FontSize = 18, AutomationId = "Tab2Content" },
70+
pushModalButton
71+
}
72+
};
73+
}
74+
75+
async void OnPushModalClicked(object sender, EventArgs e)
76+
{
77+
await Navigation.PushModalAsync(new Issue35756ModalPage(_tabbedPage));
78+
}
79+
}
80+
81+
public class Issue35756ModalPage : ContentPage
82+
{
83+
readonly TabbedPage _tabbedPage;
84+
85+
public Issue35756ModalPage(TabbedPage tabbedPage)
86+
{
87+
Title = "Modal";
88+
_tabbedPage = tabbedPage;
89+
90+
var switchTabButton = new Button
91+
{
92+
Text = "Switch to Tab 1",
93+
AutomationId = "SwitchToTab1Button"
94+
};
95+
switchTabButton.Clicked += OnSwitchToTab1Clicked;
96+
97+
var closeModalButton = new Button
98+
{
99+
Text = "Close Modal",
100+
AutomationId = "CloseModalButton"
101+
};
102+
closeModalButton.Clicked += OnCloseModalClicked;
103+
104+
Content = new StackLayout
105+
{
106+
Padding = 20,
107+
Children =
108+
{
109+
new Label { Text = "Modal Page", FontSize = 18, AutomationId = "ModalContent" },
110+
switchTabButton,
111+
closeModalButton
112+
}
113+
};
114+
}
115+
116+
void OnSwitchToTab1Clicked(object sender, EventArgs e)
117+
{
118+
_tabbedPage.CurrentPage = _tabbedPage.Children[0];
119+
}
120+
121+
async void OnCloseModalClicked(object sender, EventArgs e)
122+
{
123+
await Navigation.PopModalAsync();
124+
}
125+
}
126+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using NUnit.Framework;
2+
using UITest.Appium;
3+
using UITest.Core;
4+
5+
namespace Microsoft.Maui.TestCases.Tests.Issues;
6+
7+
public class Issue35756 : _IssuesUITest
8+
{
9+
public Issue35756(TestDevice device) : base(device) { }
10+
11+
public override string Issue => "OnNavigatedTo does not fire after PopModalAsync when tab was changed from inside the modal";
12+
13+
[Test]
14+
[Category(UITestCategories.TabbedPage)]
15+
public void OnNavigatedToFiresAfterPopModalWhenTabChangedFromInsideModal()
16+
{
17+
App.WaitForElement("Tab1Content");
18+
Assert.That(App.WaitForElement("Tab1NavigatedToCount").GetText(), Is.EqualTo("NavigatedTo count: 1"));
19+
20+
App.TapTab("Tab 2");
21+
App.WaitForElement("Tab2Content");
22+
App.Tap("PushModalButton");
23+
App.WaitForElement("ModalContent");
24+
25+
App.Tap("SwitchToTab1Button");
26+
27+
App.Tap("CloseModalButton");
28+
App.WaitForElement("Tab1Content");
29+
30+
Assert.That(App.WaitForElement("Tab1NavigatedToCount").GetText(), Is.EqualTo("NavigatedTo count: 3"));
31+
}
32+
}

0 commit comments

Comments
 (0)