maui icon indicating copy to clipboard operation
maui copied to clipboard

[Android] [Shell] replace navigation leaks current page

Open albyrock87 opened this issue 4 months ago • 4 comments

Description

Shell is leaking the page on android upon replace on Android.

  • from a shell content
  • push a page GoToAsync("child")
  • from the child, replace it GoToAsync("../other-child")
  • now check if child page is still alive

Steps to Reproduce

This is the UITest to prove the leak.

namespace Maui.Controls.Sample.Issues
{
	[Issue(IssueTracker.Github, 99999, "Shell leaks when replacing a page", PlatformAffected.Android)]
	public class Issue99999 : Shell
	{
		public Issue99999()
		{
			var mainContent = new ShellContent
			{
				ContentTemplate = new DataTemplate(() => new InitialPage()),
				Title = "Initial",
				Route = "initial"
			};
			
			Items.Add(mainContent);
			Routing.RegisterRoute("Issue99999_child", typeof(ChildPage));
			Routing.RegisterRoute("Issue99999_replace", typeof(ReplacePage));
		}
	}
	
	file class InitialPage : ContentPage
	{
		public WeakReference<Page> ChildPageReference { get; set; }

		public InitialPage()
		{
			Padding = 24;
			Content = new Button
			{
				Text = "Go to child page",
				AutomationId = "goToChildPage",
				VerticalOptions = LayoutOptions.Start,
				Command = new Command(() => Shell.Current.GoToAsync("Issue99999_child"))
			};
		}
	}
	
	file class ChildPage : ContentPage
	{
		public ChildPage()
		{
			Padding = 24;
			Content = new Button
			{
				Text = "Replace",
				AutomationId = "replace",
				VerticalOptions = LayoutOptions.Start,
				Command = new Command(() => Shell.Current.GoToAsync("../Issue99999_replace"))
			};
		}

		protected override void OnParentSet()
		{
			base.OnParentSet();
			
			if (Parent is ShellSection section)
			{
				var initialPage = (section.CurrentItem as IShellContentController).Page as InitialPage;
				initialPage!.ChildPageReference = new WeakReference<Page>(this);
			}
		}
	}
	
	file class ReplacePage : ContentPage
	{
		public ReplacePage()
		{
			Padding = 24;

			Button checkRefButton = null;
			checkRefButton = new Button
			{
				Text = "Check reference",
				AutomationId = "checkReference",
				VerticalOptions = LayoutOptions.Start,
				Command = new Command(async () =>
				{
					GC.Collect();
					GC.WaitForPendingFinalizers();
					await Task.Yield();

					var section = (ShellSection)Parent;
					var initialPage = (InitialPage)(section.CurrentItem as IShellContentController).Page;
					checkRefButton.Text = initialPage.ChildPageReference.TryGetTarget(out var page) ? "alive" : "gone";
				})
			};

			var backButton = new Button
			{
				Text = "Go back",
				AutomationId = "goBack",
				VerticalOptions = LayoutOptions.Start,
				Command = new Command(() => Shell.Current.GoToAsync(".."))
			};

			Content = new VerticalStackLayout
			{
				checkRefButton,
				backButton
			};
		}
	}
}

Link to public reproduction project repository

No response

Version with bug

8.0.91 SR9.1

Is this a regression from previous behavior?

Not sure, did not test other versions

Last version that worked well

Unknown/Other

Affected platforms

Android

Affected platform versions

No response

Did you find any workaround?

Navigate back. Then push the new page. But obviously the user experience is impacted by that.

Relevant log output

No response

albyrock87 avatar Oct 08 '24 15:10 albyrock87