Skip to content
Open
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
18 changes: 18 additions & 0 deletions src/Controls/src/Core/Page/Page.cs
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,24 @@ internal void SendNavigatedFrom(NavigatedFromEventArgs args, bool disconnectHand
if (args.NavigationType == NavigationType.Pop ||
args.NavigationType == NavigationType.PopToRoot)
{
#if IOS
// Don't dispose Handlers too early on iOS!
// iOS aggressively cleans up page Handlers during navigation, but if the page
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only required on iOS? Because this is a cross platform code that will run same on other platforms.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jsuarezruiz , I've added the fix only for iOS platform

// is still in Shell's navigation stack, users can navigate back to it.
// Disposing the Handler makes the page invisible.
// So only dispose Handler if page not in current Shell stack
if (Shell.Current != null)
{
// Check if this page is still referenced in Shell's navigation stack
var currentStack = Shell.Current.CurrentItem?.CurrentItem?.Stack;
if (currentStack?.Contains(this) == true)
{
// Page is still in navigation stack, don't dispose Handler
return;
}
}
#endif

Copy link

Copilot AI Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing whitespace should be removed from the blank line.

Suggested change

Copilot uses AI. Check for mistakes.
if (!this.IsLoaded)
{
this.DisconnectHandlers();
Expand Down
194 changes: 194 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue31961.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
using System.Collections.ObjectModel;

namespace Maui.Controls.Sample.Issues;

// Interface for navigation awareness
public interface INavigationAware
{
void OnShellNavigated(ShellNavigatedEventArgs args);
}

[Issue(IssueTracker.Github, 31961, "[iOS] App crash with NullReferenceException in ShellSectionRenderer", PlatformAffected.iOS | PlatformAffected.macOS)]
public class Issue31961 : Shell
{
public Issue31961()
{
var mainPage = new ShellContent
{
Title = "Main",
Content = new Issue31961MainPage(),
Route = "MainPage"
};

var tabBar = new TabBar();
tabBar.Items.Add(mainPage);
Items.Add(tabBar);

// Register routes for navigation
Routing.RegisterRoute("Issue31961FirstPage", typeof(Issue31961FirstPage));
Routing.RegisterRoute("Issue31961SecondPage", typeof(Issue31961SecondPage));
Routing.RegisterRoute("Issue31961ThirdPage", typeof(Issue31961ThirdPage));
Routing.RegisterRoute("Issue31961ModalPage", typeof(Issue31961ModalPage));
}

protected override void OnNavigated(ShellNavigatedEventArgs args)
{
if (Current.CurrentPage?.BindingContext is INavigationAware bindingContext)
{
bindingContext.OnShellNavigated(args);
}

base.OnNavigated(args);
}
}

public partial class Issue31961MainPage : ContentPage
{
public Command NavigateToPage1Command { get; }

public Issue31961MainPage()
{
// Initialize command
NavigateToPage1Command = new Command(async () => await NavigateToPage1Async());

var btnNavigate = new Button
{
Text = "Go to Page 1",
FontSize = 20,
AutomationId = "MainPage",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
Command = NavigateToPage1Command
};

Content = btnNavigate;
}

async Task NavigateToPage1Async()
{
await Shell.Current.GoToAsync("Issue31961FirstPage");
}
}

public class Issue31961FirstPage : ContentPage, INavigationAware
{
bool _wasModalShown = false;

public Command OpenModalCommand { get; }
public Command NavigateToPage2Command { get; }

public Issue31961FirstPage()
{
Title = "Page 1";
BindingContext = this; // Set binding context to enable INavigationAware

// Initialize commands
OpenModalCommand = new Command(async () => await OpenModalAsync());
NavigateToPage2Command = new Command(async () => await NavigateToPage2Async());

var btnOpenModal = new Button { Text = "Open Modal Page", AutomationId = "OpenModalButton", Command = OpenModalCommand };
var btnGoToPage2 = new Button { Text = "Go to Page 2", Command = NavigateToPage2Command };

Content = new VerticalStackLayout
{
Padding = 20,
Spacing = 20,
Children = { btnOpenModal, btnGoToPage2 }
};
}

async Task OpenModalAsync()
{
_wasModalShown = true;
await Shell.Current.GoToAsync("Issue31961ModalPage");
}

async Task NavigateToPage2Async()
{
await Shell.Current.GoToAsync("Issue31961SecondPage");
}

public void OnShellNavigated(ShellNavigatedEventArgs args)
{
// This will be called when Shell navigation occurs
if (_wasModalShown)
{
_wasModalShown = false; // Reset the flag
// Navigate to Page2 only after modal action is completed
NavigateToPage2Command.Execute(null);
}
}
}

public class Issue31961ModalPage : ContentPage
{
public Command CloseModalCommand { get; }

public Issue31961ModalPage()
{
Title = "Modal Page";
BackgroundColor = Colors.LightGray;

// Initialize command
CloseModalCommand = new Command(async () => await CloseModalAsync());

var btnClose = new Button { Text = "Close Modal Page", AutomationId = "CloseModalButton", Command = CloseModalCommand };

Content = new VerticalStackLayout
{
Padding = 20,
Spacing = 20,
Children = { new Label { Text = "This is a Modal Page" }, btnClose }
};
}

async Task CloseModalAsync()
{
await Shell.Current.GoToAsync("..");
}
}

public class Issue31961SecondPage : ContentPage
{
public Command NavigateToPage3Command { get; }

public Issue31961SecondPage()
{
Title = "Page 2";

// Initialize command
NavigateToPage3Command = new Command(async () => await NavigateToPage3Async());

var btnGoToPage3 = new Button { Text = "Go to Page 3", AutomationId = "Page2" , Command = NavigateToPage3Command };

Content = new VerticalStackLayout
{
Padding = 20,
Spacing = 20,
Children = { btnGoToPage3 }
};
}

async Task NavigateToPage3Async()
{
await Shell.Current.GoToAsync("Issue31961ThirdPage");
}
}

public class Issue31961ThirdPage : ContentPage
{
public Issue31961ThirdPage()
{
Title = "Page 3";

var label = new Label
{
Text = "Welcome to Page 3",
AutomationId = "Page3",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
};

Content = label;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues
{
public class Issue31961 : _IssuesUITest
{
public Issue31961(TestDevice device) : base(device) { }

public override string Issue => "[iOS] App crash with NullReferenceException in ShellSectionRenderer";
[Test]
[Category(UITestCategories.Shell)]
public void VerifyShellNavigationWithModalNavigation()
{
App.WaitForElement("MainPage");
App.Tap("MainPage");
App.WaitForElement("OpenModalButton");
App.Tap("OpenModalButton");
App.WaitForElement("CloseModalButton");
App.Tap("CloseModalButton");
App.WaitForElement("Page2");
App.Tap("Page2");
App.WaitForElement("Page3");
}
}
}
Loading