Skip to content
Open
66 changes: 66 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue8680.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 8680, "Rework OnBackButtonPressed to use onBackPressedDispatcher", PlatformAffected.Android)]
public class Issue8680 : TestNavigationPage
{
protected override void Init()
{
PushAsync(new Issue8680MainPage());
}
}

public class Issue8680MainPage : ContentPage
{
public Issue8680MainPage()
{
var navigateButton = new Button
{
Text = "Go to Intercept Page",
AutomationId = "NavigateButton",
};
navigateButton.Clicked += async (s, e) =>
{
await Navigation.PushAsync(new Issue8680InterceptPage());
};

Content = new VerticalStackLayout
{
Children =
{
new Label { Text = "Main Page", AutomationId = "MainPageLabel" },
navigateButton,
}
};
}
}

public class Issue8680InterceptPage : ContentPage
{
int _backPressCount;
readonly Label _statusLabel;

public Issue8680InterceptPage()
{
_statusLabel = new Label
{
Text = "Back not pressed yet",
AutomationId = "StatusLabel",
};

Content = new VerticalStackLayout
{
Children =
{
_statusLabel,
new Label { Text = "Press device back button — it should be intercepted", AutomationId = "InterceptPageLabel" },
}
};
}

protected override bool OnBackButtonPressed()
{
_backPressCount++;
_statusLabel.Text = $"Back intercepted: {_backPressCount}";
return true; // true = handled, prevents navigation back
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#if ANDROID // Android-specific: OnBackButtonPressed uses onBackPressedDispatcher
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue8680 : _IssuesUITest
{
public Issue8680(TestDevice device) : base(device) { }

public override string Issue => "Rework OnBackButtonPressed to use onBackPressedDispatcher";

[Test]
[Category(UITestCategories.Navigation)]
public void BackButtonPressIsInterceptedByOnBackButtonPressed()
{
// Navigate to the intercept page
App.WaitForElement("NavigateButton");
App.Tap("NavigateButton");

// Confirm we are on the intercept page
App.WaitForElement("InterceptPageLabel");

// Press the device back button — should be intercepted (page stays)
App.Back();

// The page should still be visible because OnBackButtonPressed returned true
App.WaitForElement("StatusLabel");
Assert.That(
App.FindElement("StatusLabel").GetText(),
Is.EqualTo("Back intercepted: 1"),
"OnBackButtonPressed should have been called exactly once per back press (detects dual-fire regression on API 33+).");

// The intercept page should still be displayed (not popped)
App.WaitForElement("InterceptPageLabel");
}
}
#endif
20 changes: 5 additions & 15 deletions src/Core/src/Platform/Android/MauiAppCompatActivity.Lifecycle.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
using System;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Content.Res;
using Android.OS;
using Android.Views;
using Microsoft.Maui.Devices;
using Microsoft.Maui.LifecycleEvents;

namespace Microsoft.Maui
Expand All @@ -20,16 +18,6 @@ protected override void OnActivityResult(int requestCode, Result resultCode, Int
IPlatformApplication.Current?.Services?.InvokeLifecycleEvents<AndroidLifecycle.OnActivityResult>(del => del(this, requestCode, resultCode, data));
}

// TODO: Investigate whether the new AndroidX way is actually useful:
// https://developer.android.com/reference/android/app/Activity#onBackPressed()
[Obsolete]
#pragma warning disable 809
public override void OnBackPressed()
#pragma warning restore 809
{
HandleBackNavigation();
}

public override void OnConfigurationChanged(Configuration newConfig)
{
base.OnConfigurationChanged(newConfig);
Expand Down Expand Up @@ -136,10 +124,12 @@ public override bool OnKeyUp(Keycode keyCode, KeyEvent? e)
}

/// <summary>
/// Central handler used by both legacy <see cref="OnBackPressed"/> and the Android 13+ predictive back gesture callback.
/// Implements lifecycle event invocation and default back stack propagation unless explicitly prevented.
/// Central handler invoked by <see cref="MauiOnBackPressedCallback"/> when the back button is pressed.
/// This method only checks whether MAUI lifecycle handlers intercept the back press.
/// Callers are responsible for invoking <see cref="AndroidX.Activity.OnBackPressedDispatcher.OnBackPressed()"/> as a
/// fallback when this method returns <see langword="false"/>.
/// </summary>
void HandleBackNavigation()
internal void HandleBackNavigation()
{
var preventBackPropagation = false;
IPlatformApplication.Current?.Services?.InvokeLifecycleEvents<AndroidLifecycle.OnBackPressed>(del =>
Expand Down
1 change: 0 additions & 1 deletion src/Core/src/Platform/Android/MauiAppCompatActivity.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using Android.OS;
using Android.Views;
using AndroidX.Activity;
Expand Down
Loading