Skip to content
Closed
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
16 changes: 16 additions & 0 deletions src/Controls/src/Core/VisualElement/VisualElement.Platform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,23 @@ partial void HandlePlatformUnloadedLoaded()
{
if (view.IsLoaded())
{
#if ANDROID
// On Android, during navigation, fragments can be reused which means
// the normal ViewAttachedToWindow events might not fire. This fixes
// issue #29414 where navigating back to a page doesn't trigger the Loaded event.
// If we have subscribers to the loaded event and the platform view is loaded,
// we should force the loaded event to fire.
if (_loaded is not null)
{
SendLoaded(false, true);
}
else
{
SendLoaded(false);
}
#else
SendLoaded(false);
#endif

// If SendLoaded caused the unloaded tokens to wire up
_loadedUnloadedToken?.Dispose();
Expand Down
16 changes: 15 additions & 1 deletion src/Controls/src/Core/VisualElement/VisualElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2265,7 +2265,12 @@ event EventHandler? IControlsVisualElement.PlatformContainerViewChanged
void SendLoaded() => SendLoaded(true);
void SendLoaded(bool updateWiring)
{
if (_isLoadedFired)
SendLoaded(updateWiring, false);
}

void SendLoaded(bool updateWiring, bool force)
{
if (!force && _isLoadedFired)
return;

_isLoadedFired = true;
Expand Down Expand Up @@ -2295,6 +2300,15 @@ void SendUnloaded(bool updateWiring)
UpdatePlatformUnloadedLoadedWiring(Window);
}

// Internal method to force the Loaded event to fire again.
// This is used by platform-specific navigation code when a page becomes visible
// again but the normal platform lifecycle events don't fire (e.g., Android fragment reuse).
internal void ForceLoadedEvent()
{
// Force the loaded event to fire even if it has already fired before
SendLoaded(false, true);
}

static void OnWindowChanged(BindableObject bindable, object? oldValue, object? newValue)
{
if (bindable is not VisualElement visualElement)
Expand Down
125 changes: 125 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue29414.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 29414, "[Android] Loaded event not triggered when navigating back to a previous page", PlatformAffected.Android)]
public partial class Issue29414 : ContentPage
{
private int _mainPageLoadedCount = 0;
private Label _mainPageLoadedLabel;
private Label _secondPageLoadedLabel;

public Issue29414()
{
InitializeComponent();
}

private void InitializeComponent()
{
var layout = new StackLayout
{
Spacing = 10,
Padding = 20
};

var titleLabel = new Label
{
Text = "Main Page - Loaded Event Test",
FontSize = 18,
FontAttributes = FontAttributes.Bold,
AutomationId = "MainPageTitle"
};

_mainPageLoadedLabel = new Label
{
Text = "Main Page Loaded Count: 0",
AutomationId = "MainPageLoadedCount"
};

var navigateButton = new Button
{
Text = "Navigate to Second Page",
AutomationId = "NavigateToSecondPageButton"
};
navigateButton.Clicked += OnNavigateToSecondPageClicked;

layout.Children.Add(titleLabel);
layout.Children.Add(_mainPageLoadedLabel);
layout.Children.Add(navigateButton);

Content = layout;

// Subscribe to the Loaded event
Loaded += OnMainPageLoaded;
}

private void OnMainPageLoaded(object sender, EventArgs e)
{
_mainPageLoadedCount++;
_mainPageLoadedLabel.Text = $"Main Page Loaded Count: {_mainPageLoadedCount}";
}

private async void OnNavigateToSecondPageClicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new SecondPage());
}
}

public partial class SecondPage : ContentPage
{
private int _secondPageLoadedCount = 0;
private Label _secondPageLoadedLabel;

public SecondPage()
{
InitializeComponent();
}

private void InitializeComponent()
{
var layout = new StackLayout
{
Spacing = 10,
Padding = 20
};

var titleLabel = new Label
{
Text = "Second Page",
FontSize = 18,
FontAttributes = FontAttributes.Bold,
AutomationId = "SecondPageTitle"
};

_secondPageLoadedLabel = new Label
{
Text = "Second Page Loaded Count: 0",
AutomationId = "SecondPageLoadedCount"
};

var navigateBackButton = new Button
{
Text = "Navigate Back to Main Page",
AutomationId = "NavigateBackToMainPageButton"
};
navigateBackButton.Clicked += OnNavigateBackClicked;

layout.Children.Add(titleLabel);
layout.Children.Add(_secondPageLoadedLabel);
layout.Children.Add(navigateBackButton);

Content = layout;

// Subscribe to the Loaded event
Loaded += OnSecondPageLoaded;
}

private void OnSecondPageLoaded(object sender, EventArgs e)
{
_secondPageLoadedCount++;
_secondPageLoadedLabel.Text = $"Second Page Loaded Count: {_secondPageLoadedCount}";
}

private async void OnNavigateBackClicked(object sender, EventArgs e)
{
await Navigation.PopAsync();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

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

public override string Issue => "[Android] Loaded event not triggered when navigating back to a previous page";

[Test]
[Category(UITestCategories.Page)]
[Category(UITestCategories.Navigation)]
public void LoadedEventShouldFireWhenNavigatingBackToPage()
{
// Step 1: Verify the main page loaded event fired initially
App.WaitForElement("MainPageLoadedCount");
var initialLoadedText = App.FindElement("MainPageLoadedCount").GetText();
Assert.That(initialLoadedText, Is.EqualTo("Main Page Loaded Count: 1"),
"Main page should have loaded count of 1 initially");

// Step 2: Navigate to second page
App.WaitForElement("NavigateToSecondPageButton");
App.Tap("NavigateToSecondPageButton");

// Step 3: Verify second page loads
App.WaitForElement("SecondPageLoadedCount");
var secondPageLoadedText = App.FindElement("SecondPageLoadedCount").GetText();
Assert.That(secondPageLoadedText, Is.EqualTo("Second Page Loaded Count: 1"),
"Second page should have loaded count of 1");

// Step 4: Navigate back to main page
App.WaitForElement("NavigateBackToMainPageButton");
App.Tap("NavigateBackToMainPageButton");

// Step 5: Verify main page loaded event fired again (this is the failing test case)
App.WaitForElement("MainPageLoadedCount");
var finalLoadedText = App.FindElement("MainPageLoadedCount").GetText();
Assert.That(finalLoadedText, Is.EqualTo("Main Page Loaded Count: 2"),
"Main page should have loaded count of 2 after navigating back - THIS IS THE BUG ON ANDROID");
}

[Test]
[Category(UITestCategories.Page)]
[Category(UITestCategories.Navigation)]
public void LoadedEventShouldFireMultipleTimesWhenNavigatingBackAndForth()
{
// Navigate back and forth multiple times to ensure loaded events keep firing
App.WaitForElement("MainPageLoadedCount");

// Initial state
var mainPageText = App.FindElement("MainPageLoadedCount").GetText();
Assert.That(mainPageText, Is.EqualTo("Main Page Loaded Count: 1"));

// Navigate to second page and back - first time
App.Tap("NavigateToSecondPageButton");
App.WaitForElement("NavigateBackToMainPageButton");
App.Tap("NavigateBackToMainPageButton");

App.WaitForElement("MainPageLoadedCount");
mainPageText = App.FindElement("MainPageLoadedCount").GetText();
Assert.That(mainPageText, Is.EqualTo("Main Page Loaded Count: 2"),
"Main page should have loaded count of 2 after first back navigation");

// Navigate to second page and back - second time
App.Tap("NavigateToSecondPageButton");
App.WaitForElement("NavigateBackToMainPageButton");
App.Tap("NavigateBackToMainPageButton");

App.WaitForElement("MainPageLoadedCount");
mainPageText = App.FindElement("MainPageLoadedCount").GetText();
Assert.That(mainPageText, Is.EqualTo("Main Page Loaded Count: 3"),
"Main page should have loaded count of 3 after second back navigation");
}
}
}