Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -406,25 +406,27 @@ void RemoveAllButCurrent(Fragment skip)

void RemoveAllPushedPages(ShellSection shellSection, bool keepCurrent)
{
if (shellSection.Stack.Count <= 1 || (keepCurrent && shellSection.Stack.Count == 2))
return;

var t = ChildFragmentManager.BeginTransactionEx();
FragmentTransaction t = null;

foreach (var kvp in _fragmentMap.ToList())
{
if (kvp.Key.Parent != shellSection)
{
continue;
}

_fragmentMap.Remove(kvp.Key);

if (keepCurrent && kvp.Value.Fragment == _currentFragment)
{
continue;
}

t ??= ChildFragmentManager.BeginTransactionEx();
t.RemoveEx(kvp.Value.Fragment);
}

t.CommitAllowingStateLossEx();
t?.CommitAllowingStateLossEx();
}

void RemoveFragment(Fragment fragment)
Expand Down
111 changes: 111 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue36853.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
namespace Maui.Controls.Sample.Issues;

// Reproduces the bug: Root → SecondPage (singleton) → ThirdPage → ///Root → SecondPage again → BLANK
[Issue(IssueTracker.Github, 36853, "Shell singleton page renders blank when re-pushed after absolute route PopToRoot on Android", PlatformAffected.Android)]
public class Issue36853 : TestShell
{
protected override void Init()
{
Routing.RegisterRoute("Issue36853Second", typeof(Issue36853SecondPage));
Routing.RegisterRoute("Issue36853Third", typeof(Issue36853ThirdPage));

var mainPage = new ContentPage
{
Title = "Main",
Content = new VerticalStackLayout
{
Spacing = 20,
Padding = 20,
Children =
{
new Label
{
Text = "Main Page",
AutomationId = "Issue36853MainLabel",
FontSize = 24
},
new Button
{
Text = "Go to Second Page",
AutomationId = "Issue36853GoToSecond",
Command = new Command(async () =>
await Shell.Current.GoToAsync("Issue36853Second"))
}
}
}
};

AddContentPage(mainPage, "Issue36853Main");
}
}

// Registered as Singleton in DI — same instance returned every time the route resolves
public class Issue36853SecondPage : ContentPage
{
public Issue36853SecondPage()
{
Title = "Second";
Content = new VerticalStackLayout
{
Spacing = 20,
Padding = 20,
Children =
{
new Label
{
Text = "Second Page Content",
AutomationId = "Issue36853SecondLabel",
FontSize = 24
},
new Button
{
Text = "Go to Third Page",
AutomationId = "Issue36853GoToThird",
Command = new Command(async () =>
await Shell.Current.GoToAsync("Issue36853Third"))
}
}
};
}
}

public class Issue36853ThirdPage : ContentPage
{
public Issue36853ThirdPage()
{
Title = "Third";
Content = new VerticalStackLayout
{
Spacing = 20,
Padding = 20,
Children =
{
new Label
{
Text = "Third Page Content",
AutomationId = "Issue36853ThirdLabel",
FontSize = 24
},
new Button
{
Text = "Reset to Root (///)",
AutomationId = "Issue36853ResetToRoot",
Command = new Command(async () =>
await Shell.Current.GoToAsync("///Issue36853Main"))
}
}
};
}
}

static class Issue36853Extensions
{
public static MauiAppBuilder Issue36853RegisterServices(this MauiAppBuilder builder)
{
// SecondPage is singleton — same instance reused across navigations (the bug scenario)
builder.Services.AddSingleton<Issue36853SecondPage>();
// ThirdPage is transient — new instance each time (normal behavior)
builder.Services.AddTransient<Issue36853ThirdPage>();
return builder;
}
}
3 changes: 2 additions & 1 deletion src/Controls/tests/TestCases.HostApp/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public static MauiApp CreateMauiApp()
.Issue18720DatePickerAddMappers()
.Issue18720TimePickerAddMappers()
.Issue28945AddMappers()
.Issue25436RegisterNavigationService();
.Issue25436RegisterNavigationService()
.Issue36853RegisterServices();

#if IOS || MACCATALYST
appBuilder.ConfigureCollectionViewHandlers();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue36853 : _IssuesUITest
{
public override string Issue => "Shell singleton page renders blank when re-pushed after absolute route PopToRoot on Android";

public Issue36853(TestDevice testDevice) : base(testDevice)
{
}

[Test]
[Category(UITestCategories.Shell)]
public void SingletonPageShouldRenderAfterPopToRootAndRePush()
{
// Step 1: From root, push SecondPage (singleton)
App.WaitForElement("Issue36853GoToSecond");
App.Tap("Issue36853GoToSecond");

// Step 2: Verify SecondPage renders
App.WaitForElement("Issue36853SecondLabel");

// Step 3: Push ThirdPage on top (so stack is Root → Second → Third)
App.Tap("Issue36853GoToThird");
App.WaitForElement("Issue36853ThirdLabel");

// Step 4: PopToRoot via absolute route ///
App.Tap("Issue36853ResetToRoot");
App.WaitForElement("Issue36853MainLabel");

// Step 5: Re-push SecondPage (same singleton instance)
App.Tap("Issue36853GoToSecond");

// Step 6: SecondPage content must be visible — not blank
// Without the fix, WaitForElement will timeout because the stale fragment
// has a disconnected handler and OnCreateView never fires — page is blank.
App.WaitForElement("Issue36853SecondLabel");
}
}
Loading