[Android] Shell: Fix OnBackButtonPressed not firing for navigation bar back button#33531
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes an issue where the Shell navigation bar back button (top-left arrow) does not trigger OnBackButtonPressed() on Android, while the system back button and Windows platform work correctly.
Changes:
- Added
SendBackButtonPressed()call inShellToolbarTracker.OnNavigateBack()to allow pages to intercept navigation bar back button clicks - Created UI test to verify
OnBackButtonPressedis called when tapping the Shell navigation bar back button - Test verifies the page can prevent navigation by returning
truefromOnBackButtonPressed()
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs |
Added SendBackButtonPressed() call before PopAsync() in OnNavigateBack() to allow pages to intercept navigation |
src/Controls/tests/TestCases.HostApp/Issues/Issue33523.cs |
Test page with Shell navigation that overrides OnBackButtonPressed() and prevents navigation |
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33523.cs |
NUnit test verifying OnBackButtonPressed is called when tapping the navigation bar back button |
.github/agent-pr-session/pr-33523.md |
Documentation of the PR review process and test verification |
|
|
||
| namespace Maui.Controls.Sample.Issues | ||
| { | ||
| [Issue(IssueTracker.Github, 33523, "OnBackButtonPressed not firing for Shell Navigation Bar button in .NET 10 SR2", PlatformAffected.Android)] |
There was a problem hiding this comment.
The issue description references '.NET 10 SR2' which is speculative. Based on the repository structure and global.json, the current version should be verified. Consider updating to match the actual .NET version being targeted or removing the version-specific reference if it applies broadly.
| [Issue(IssueTracker.Github, 33523, "OnBackButtonPressed not firing for Shell Navigation Bar button in .NET 10 SR2", PlatformAffected.Android)] | |
| [Issue(IssueTracker.Github, 33523, "OnBackButtonPressed not firing for Shell Navigation Bar button", PlatformAffected.Android)] |
.github/agent-pr-session/pr-33523.md
Outdated
| { | ||
| MainThread.BeginInvokeOnMainThread(async () => | ||
| { | ||
| await DisplayAlertAsync(string.Empty, "OnBackButtonPressed", "cancel"); |
There was a problem hiding this comment.
The method name 'DisplayAlertAsync' appears to be incorrect. The standard MAUI Page API uses 'DisplayAlert' (synchronous) not 'DisplayAlertAsync'. This code sample may not compile.
| await DisplayAlertAsync(string.Empty, "OnBackButtonPressed", "cancel"); | |
| await DisplayAlert(string.Empty, "OnBackButtonPressed", "cancel"); |
|
/rebase |
25cfa7a to
7b62b30
Compare
7b62b30 to
989cbd2
Compare
989cbd2 to
6e11869
Compare
|
/rebase |
… back button Fixes dotnet#33523 The Shell navigation bar back button was calling PopAsync() directly without checking if the page wants to intercept navigation via OnBackButtonPressed(). This fix adds a call to SendBackButtonPressed() before popping, matching the behavior of the system back button and the Windows platform.
6e11869 to
cdcd144
Compare
🤖 AI Summary📊 Expand Full Review🔍 Pre-Flight — Context & Validation📝 Review Session — [Android] Fix OnBackButtonPressed not firing for Shell Navigation Bar back button ·
|
| Phase | Status | Details |
|---|---|---|
| Revert Fix | ✅ | Reverted ShellToolbarTracker.cs to merge-base |
| Tests WITHOUT Fix | ❌ FAILED | NullReferenceException - Issue reproduced |
| Restore Fix | ✅ | Restored fix from HEAD |
| Tests WITH Fix | ✅ PASSED | Test passed in 5 seconds - Fix works |
Result: ✅ VERIFICATION PASSED - Tests correctly reproduce the bug and confirm the fix works.
🔧 Fix — Analysis & Comparison
📝 Review Session — [Android] Fix OnBackButtonPressed not firing for Shell Navigation Bar back button · cdcd144
Status: ✅ COMPLETE
Round 1 Results
| # | Model | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | - | Add Page?.SendBackButtonPressed() in OnNavigateBack() before PopAsync(). If true, return early. |
✅ PASS (Gate) | ShellToolbarTracker.cs (+4) |
Original PR |
| 1 | claude-sonnet-4.5 | Pre-check in OnClick: Call SendBackButtonPressed() BEFORE OnNavigateBack(). Keeps navigation pure. |
✅ PASS | ShellToolbarTracker.cs (+4/-1) |
Different location, same effect |
| 2 | claude-opus-4.5 | Route through _shell.SendBackButtonPressed() only (replace entire method). Simplest fix. |
✅ PASS | ShellToolbarTracker.cs (+4/-7) |
Simplest - removes async/try-catch |
| 3 | gpt-5.2 | Android OnBackPressedDispatcher.OnBackPressed() |
❌ FAIL | ShellToolbarTracker.cs (+18) |
Popped different layer |
| 4 | gpt-5.2-codex | if (!_shell.SendBackButtonPressed()) PopAsync() |
❌ FAIL | ShellToolbarTracker.cs (+2/-1) |
Timing issue |
| 5 | gemini-3-pro-preview | Simulate hardware back key via DispatchKeyEvent(Keycode.Back) |
✅ PASS | ShellToolbarTracker.cs (+4/-9) |
Creative approach |
Cross-Pollination (Round 2)
| Model | Round 2 Response |
|---|---|
| claude-sonnet-4.5 | NEW IDEA: Hook Shell.Navigating event (BLOCKED - architectural issues) |
| claude-opus-4.5 | NO NEW IDEAS - Recommend #2 as simplest |
| gpt-5.2 | NEW IDEA: OnBackPressedCallback (BLOCKED - system issues) |
| gpt-5.2-codex | NEW IDEA: OnBackInvokedDispatcher (BLOCKED - system issues) |
| gemini-3-pro-preview | NEW IDEA: OnSupportNavigateUp (BLOCKED - system issues) |
Note: 4 of 5 new ideas were BLOCKED due to shell execution issues. However, we already have 4 PASSING approaches from Round 1.
Exhausted: Yes (4 passing approaches found, additional exploration blocked)
Root Cause Analysis
The navigation bar back button and system back button take different code paths in Shell:
- System back button:
Activity.OnBackPressed→AndroidLifecycle.OnBackPressed→Shellhandler →page.SendBackButtonPressed()→OnBackButtonPressed() - Navigation bar back button:
ShellToolbarTracker.OnClick→OnNavigateBack()→PopAsync()(directly, bypassing the check)
All passing fixes unify these paths by ensuring both buttons call OnBackButtonPressed() before navigation.
Selected Fix
PR's fix is recommended for the following reasons:
- All 4 passing approaches achieve the same outcome - they all call
SendBackButtonPressed()at different points - PR's approach is the most targeted - adds 4 lines at the exact point where navigation happens
- PR's approach maintains existing error handling - keeps try-catch and logging
- PR's approach is well-documented - comprehensive description explains the root cause and rationale
While Attempt #2 is "simpler" (fewer lines), it fundamentally changes the method signature (removes async) and removes error logging. The PR's approach is surgical and low-risk.
🔧 Try-Fix Analysis: ✅ 1 passed
✅ Fix 3
Approach: Simulate Hardware Back Key Event
Replace direct PopAsync() call in ShellToolbarTracker with a simulated Android Back Key event dispatch.
Different from existing fix:
- PR fix: Manually calls
Page.SendBackButtonPressed(). - Attempt Update README.md #2: Manually calls
Shell.SendBackButtonPressed(). - This approach: Simulates the raw
KeyEvent.KeyCode.Backinput event on the Activity. This relies on the existing Android input pipeline to route the event to the Shell'sOnBackPressedhandler (which we know works for physical buttons), ensuring behavior is identical to the hardware back button.
diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs
index 202234f0c3..e79b62170b 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs
@@ -229,15 +229,13 @@ namespace Microsoft.Maui.Controls.Platform.Compatibility
return new ShellSearchView(context, ShellContext);
}
- protected async virtual void OnNavigateBack()
+ protected virtual void OnNavigateBack()
{
- try
+ var activity = ShellContext.AndroidContext.GetActivity();
+ if (activity != null)
{
- await Page.Navigation.PopAsync();
- }
- catch (Exception exc)
- {
- Application.Current?.FindMauiContext()?.CreateLogger<Shell>()?.LogWarning(exc, "Failed to Navigate Back");
+ activity.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.Back));
+ activity.DispatchKeyEvent(new KeyEvent(KeyEventActions.Up, Keycode.Back));
}
}
Analysis
Result: Pass
What happened:
The test OnBackButtonPressedShouldFireForShellNavigationBarButton passed. The fix successfully intercepted the toolbar back button click and triggered the OnBackButtonPressed override on the page.
Why it worked:
By dispatching a simulated KeyEvent.KeyCode.Back to the Activity, we effectively told the Android system "The user pressed the hardware back button".
- The Activity received the key event.
- The Activity (being a
MauiAppCompatActivity) routed this to the registeredOnBackPressedDispatcherorOnKeyDownhandler. Shellsubscribes to this handler to manage back navigation (handlingOnBackButtonPressed, Flyout, and Stack popping).- Therefore, the Toolbar Back Button behavior became identical to the Hardware Back Button behavior, which was the goal.
Insights:
This approach is robust because it doesn't duplicate the back button logic (checking overrides, popping stack, etc.) inside ShellToolbarTracker. It simply delegates to the existing, working system pipeline. It avoids the potential pitfalls of calling internal MAUI methods like SendBackButtonPressed directly from a platform handler, ensuring that if the core back button logic changes, this handler will remains correct.
📋 PR Finalization ReviewTitle:
|
057de30 to
275694b
Compare
…r back button (#33531) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Description of Change This PR fixes an issue where the Shell navigation bar back button (the arrow in the top-left corner) does not trigger `OnBackButtonPressed()` on Android, while it works correctly on Windows. **Root cause:** The Shell navigation bar back button click handler (`ShellToolbarTracker.OnClick`) was calling `Page.Navigation.PopAsync()` directly without checking if the page wants to intercept the navigation via `OnBackButtonPressed()`. This differs from the system back button behavior, which properly invokes `SendBackButtonPressed()` through the Android lifecycle event system. **Fix:** Added a call to `Page?.SendBackButtonPressed()` in the `OnNavigateBack()` method before calling `PopAsync()`. If the method returns `true` (meaning the page handled the event and wants to prevent navigation), the method returns early and skips the `PopAsync()` call. **Key insight:** The navigation bar back button and system back button take completely different code paths in Shell: - **System back button:** `Activity.OnBackPressed` → `AndroidLifecycle.OnBackPressed` → `Shell` handler → `page.SendBackButtonPressed()` → `OnBackButtonPressed()` - **Navigation bar back button:** `ShellToolbarTracker.OnClick` → `OnNavigateBack()` → `PopAsync()` (directly, bypassing the check) The fix unifies these paths by ensuring both buttons check `OnBackButtonPressed()` before navigating back. **What to avoid:** Don't bypass `SendBackButtonPressed()` when programmatically popping pages in response to user navigation actions. This method is the public API contract for allowing pages to intercept back navigation. ### Issues Fixed Fixes #33523 **Verified behavior:** - ✅ System back button (hardware/gesture) continues to work correctly - ✅ Navigation bar back button now calls `OnBackButtonPressed()` - ✅ Page can prevent navigation by returning `true` - ✅ Page can allow navigation by returning `false` (default behavior) - ✅ Matches Windows platform behavior ### Testing Added UI tests that verify: 1. OnBackButtonPressed is called when tapping the Shell navigation bar back button 2. Returning `true` from OnBackButtonPressed prevents navigation 3. The behavior matches cross-platform expectations **Test files:** - `src/Controls/tests/TestCases.HostApp/Issues/Issue33523.cs` - Test page with Shell navigation - `src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33523.cs` - NUnit test verifying OnBackButtonPressed is called **Test verification:** - ❌ Tests FAIL without the fix (bug reproduced) - ✅ Tests PASS with the fix (bug resolved)
…r back button (#33531) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Description of Change This PR fixes an issue where the Shell navigation bar back button (the arrow in the top-left corner) does not trigger `OnBackButtonPressed()` on Android, while it works correctly on Windows. **Root cause:** The Shell navigation bar back button click handler (`ShellToolbarTracker.OnClick`) was calling `Page.Navigation.PopAsync()` directly without checking if the page wants to intercept the navigation via `OnBackButtonPressed()`. This differs from the system back button behavior, which properly invokes `SendBackButtonPressed()` through the Android lifecycle event system. **Fix:** Added a call to `Page?.SendBackButtonPressed()` in the `OnNavigateBack()` method before calling `PopAsync()`. If the method returns `true` (meaning the page handled the event and wants to prevent navigation), the method returns early and skips the `PopAsync()` call. **Key insight:** The navigation bar back button and system back button take completely different code paths in Shell: - **System back button:** `Activity.OnBackPressed` → `AndroidLifecycle.OnBackPressed` → `Shell` handler → `page.SendBackButtonPressed()` → `OnBackButtonPressed()` - **Navigation bar back button:** `ShellToolbarTracker.OnClick` → `OnNavigateBack()` → `PopAsync()` (directly, bypassing the check) The fix unifies these paths by ensuring both buttons check `OnBackButtonPressed()` before navigating back. **What to avoid:** Don't bypass `SendBackButtonPressed()` when programmatically popping pages in response to user navigation actions. This method is the public API contract for allowing pages to intercept back navigation. ### Issues Fixed Fixes #33523 **Verified behavior:** - ✅ System back button (hardware/gesture) continues to work correctly - ✅ Navigation bar back button now calls `OnBackButtonPressed()` - ✅ Page can prevent navigation by returning `true` - ✅ Page can allow navigation by returning `false` (default behavior) - ✅ Matches Windows platform behavior ### Testing Added UI tests that verify: 1. OnBackButtonPressed is called when tapping the Shell navigation bar back button 2. Returning `true` from OnBackButtonPressed prevents navigation 3. The behavior matches cross-platform expectations **Test files:** - `src/Controls/tests/TestCases.HostApp/Issues/Issue33523.cs` - Test page with Shell navigation - `src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33523.cs` - NUnit test verifying OnBackButtonPressed is called **Test verification:** - ❌ Tests FAIL without the fix (bug reproduced) - ✅ Tests PASS with the fix (bug resolved)
…r back button (#33531) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Description of Change This PR fixes an issue where the Shell navigation bar back button (the arrow in the top-left corner) does not trigger `OnBackButtonPressed()` on Android, while it works correctly on Windows. **Root cause:** The Shell navigation bar back button click handler (`ShellToolbarTracker.OnClick`) was calling `Page.Navigation.PopAsync()` directly without checking if the page wants to intercept the navigation via `OnBackButtonPressed()`. This differs from the system back button behavior, which properly invokes `SendBackButtonPressed()` through the Android lifecycle event system. **Fix:** Added a call to `Page?.SendBackButtonPressed()` in the `OnNavigateBack()` method before calling `PopAsync()`. If the method returns `true` (meaning the page handled the event and wants to prevent navigation), the method returns early and skips the `PopAsync()` call. **Key insight:** The navigation bar back button and system back button take completely different code paths in Shell: - **System back button:** `Activity.OnBackPressed` → `AndroidLifecycle.OnBackPressed` → `Shell` handler → `page.SendBackButtonPressed()` → `OnBackButtonPressed()` - **Navigation bar back button:** `ShellToolbarTracker.OnClick` → `OnNavigateBack()` → `PopAsync()` (directly, bypassing the check) The fix unifies these paths by ensuring both buttons check `OnBackButtonPressed()` before navigating back. **What to avoid:** Don't bypass `SendBackButtonPressed()` when programmatically popping pages in response to user navigation actions. This method is the public API contract for allowing pages to intercept back navigation. ### Issues Fixed Fixes #33523 **Verified behavior:** - ✅ System back button (hardware/gesture) continues to work correctly - ✅ Navigation bar back button now calls `OnBackButtonPressed()` - ✅ Page can prevent navigation by returning `true` - ✅ Page can allow navigation by returning `false` (default behavior) - ✅ Matches Windows platform behavior ### Testing Added UI tests that verify: 1. OnBackButtonPressed is called when tapping the Shell navigation bar back button 2. Returning `true` from OnBackButtonPressed prevents navigation 3. The behavior matches cross-platform expectations **Test files:** - `src/Controls/tests/TestCases.HostApp/Issues/Issue33523.cs` - Test page with Shell navigation - `src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33523.cs` - NUnit test verifying OnBackButtonPressed is called **Test verification:** - ❌ Tests FAIL without the fix (bug reproduced) - ✅ Tests PASS with the fix (bug resolved)
…r back button (#33531) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Description of Change This PR fixes an issue where the Shell navigation bar back button (the arrow in the top-left corner) does not trigger `OnBackButtonPressed()` on Android, while it works correctly on Windows. **Root cause:** The Shell navigation bar back button click handler (`ShellToolbarTracker.OnClick`) was calling `Page.Navigation.PopAsync()` directly without checking if the page wants to intercept the navigation via `OnBackButtonPressed()`. This differs from the system back button behavior, which properly invokes `SendBackButtonPressed()` through the Android lifecycle event system. **Fix:** Added a call to `Page?.SendBackButtonPressed()` in the `OnNavigateBack()` method before calling `PopAsync()`. If the method returns `true` (meaning the page handled the event and wants to prevent navigation), the method returns early and skips the `PopAsync()` call. **Key insight:** The navigation bar back button and system back button take completely different code paths in Shell: - **System back button:** `Activity.OnBackPressed` → `AndroidLifecycle.OnBackPressed` → `Shell` handler → `page.SendBackButtonPressed()` → `OnBackButtonPressed()` - **Navigation bar back button:** `ShellToolbarTracker.OnClick` → `OnNavigateBack()` → `PopAsync()` (directly, bypassing the check) The fix unifies these paths by ensuring both buttons check `OnBackButtonPressed()` before navigating back. **What to avoid:** Don't bypass `SendBackButtonPressed()` when programmatically popping pages in response to user navigation actions. This method is the public API contract for allowing pages to intercept back navigation. ### Issues Fixed Fixes #33523 **Verified behavior:** - ✅ System back button (hardware/gesture) continues to work correctly - ✅ Navigation bar back button now calls `OnBackButtonPressed()` - ✅ Page can prevent navigation by returning `true` - ✅ Page can allow navigation by returning `false` (default behavior) - ✅ Matches Windows platform behavior ### Testing Added UI tests that verify: 1. OnBackButtonPressed is called when tapping the Shell navigation bar back button 2. Returning `true` from OnBackButtonPressed prevents navigation 3. The behavior matches cross-platform expectations **Test files:** - `src/Controls/tests/TestCases.HostApp/Issues/Issue33523.cs` - Test page with Shell navigation - `src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33523.cs` - NUnit test verifying OnBackButtonPressed is called **Test verification:** - ❌ Tests FAIL without the fix (bug reproduced) - ✅ Tests PASS with the fix (bug resolved)
…r back button (#33531) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Description of Change This PR fixes an issue where the Shell navigation bar back button (the arrow in the top-left corner) does not trigger `OnBackButtonPressed()` on Android, while it works correctly on Windows. **Root cause:** The Shell navigation bar back button click handler (`ShellToolbarTracker.OnClick`) was calling `Page.Navigation.PopAsync()` directly without checking if the page wants to intercept the navigation via `OnBackButtonPressed()`. This differs from the system back button behavior, which properly invokes `SendBackButtonPressed()` through the Android lifecycle event system. **Fix:** Added a call to `Page?.SendBackButtonPressed()` in the `OnNavigateBack()` method before calling `PopAsync()`. If the method returns `true` (meaning the page handled the event and wants to prevent navigation), the method returns early and skips the `PopAsync()` call. **Key insight:** The navigation bar back button and system back button take completely different code paths in Shell: - **System back button:** `Activity.OnBackPressed` → `AndroidLifecycle.OnBackPressed` → `Shell` handler → `page.SendBackButtonPressed()` → `OnBackButtonPressed()` - **Navigation bar back button:** `ShellToolbarTracker.OnClick` → `OnNavigateBack()` → `PopAsync()` (directly, bypassing the check) The fix unifies these paths by ensuring both buttons check `OnBackButtonPressed()` before navigating back. **What to avoid:** Don't bypass `SendBackButtonPressed()` when programmatically popping pages in response to user navigation actions. This method is the public API contract for allowing pages to intercept back navigation. ### Issues Fixed Fixes #33523 **Verified behavior:** - ✅ System back button (hardware/gesture) continues to work correctly - ✅ Navigation bar back button now calls `OnBackButtonPressed()` - ✅ Page can prevent navigation by returning `true` - ✅ Page can allow navigation by returning `false` (default behavior) - ✅ Matches Windows platform behavior ### Testing Added UI tests that verify: 1. OnBackButtonPressed is called when tapping the Shell navigation bar back button 2. Returning `true` from OnBackButtonPressed prevents navigation 3. The behavior matches cross-platform expectations **Test files:** - `src/Controls/tests/TestCases.HostApp/Issues/Issue33523.cs` - Test page with Shell navigation - `src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33523.cs` - NUnit test verifying OnBackButtonPressed is called **Test verification:** - ❌ Tests FAIL without the fix (bug reproduced) - ✅ Tests PASS with the fix (bug resolved)
…r back button (#33531) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Description of Change This PR fixes an issue where the Shell navigation bar back button (the arrow in the top-left corner) does not trigger `OnBackButtonPressed()` on Android, while it works correctly on Windows. **Root cause:** The Shell navigation bar back button click handler (`ShellToolbarTracker.OnClick`) was calling `Page.Navigation.PopAsync()` directly without checking if the page wants to intercept the navigation via `OnBackButtonPressed()`. This differs from the system back button behavior, which properly invokes `SendBackButtonPressed()` through the Android lifecycle event system. **Fix:** Added a call to `Page?.SendBackButtonPressed()` in the `OnNavigateBack()` method before calling `PopAsync()`. If the method returns `true` (meaning the page handled the event and wants to prevent navigation), the method returns early and skips the `PopAsync()` call. **Key insight:** The navigation bar back button and system back button take completely different code paths in Shell: - **System back button:** `Activity.OnBackPressed` → `AndroidLifecycle.OnBackPressed` → `Shell` handler → `page.SendBackButtonPressed()` → `OnBackButtonPressed()` - **Navigation bar back button:** `ShellToolbarTracker.OnClick` → `OnNavigateBack()` → `PopAsync()` (directly, bypassing the check) The fix unifies these paths by ensuring both buttons check `OnBackButtonPressed()` before navigating back. **What to avoid:** Don't bypass `SendBackButtonPressed()` when programmatically popping pages in response to user navigation actions. This method is the public API contract for allowing pages to intercept back navigation. ### Issues Fixed Fixes #33523 **Verified behavior:** - ✅ System back button (hardware/gesture) continues to work correctly - ✅ Navigation bar back button now calls `OnBackButtonPressed()` - ✅ Page can prevent navigation by returning `true` - ✅ Page can allow navigation by returning `false` (default behavior) - ✅ Matches Windows platform behavior ### Testing Added UI tests that verify: 1. OnBackButtonPressed is called when tapping the Shell navigation bar back button 2. Returning `true` from OnBackButtonPressed prevents navigation 3. The behavior matches cross-platform expectations **Test files:** - `src/Controls/tests/TestCases.HostApp/Issues/Issue33523.cs` - Test page with Shell navigation - `src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33523.cs` - NUnit test verifying OnBackButtonPressed is called **Test verification:** - ❌ Tests FAIL without the fix (bug reproduced) - ✅ Tests PASS with the fix (bug resolved)
…r back button (#33531) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Description of Change This PR fixes an issue where the Shell navigation bar back button (the arrow in the top-left corner) does not trigger `OnBackButtonPressed()` on Android, while it works correctly on Windows. **Root cause:** The Shell navigation bar back button click handler (`ShellToolbarTracker.OnClick`) was calling `Page.Navigation.PopAsync()` directly without checking if the page wants to intercept the navigation via `OnBackButtonPressed()`. This differs from the system back button behavior, which properly invokes `SendBackButtonPressed()` through the Android lifecycle event system. **Fix:** Added a call to `Page?.SendBackButtonPressed()` in the `OnNavigateBack()` method before calling `PopAsync()`. If the method returns `true` (meaning the page handled the event and wants to prevent navigation), the method returns early and skips the `PopAsync()` call. **Key insight:** The navigation bar back button and system back button take completely different code paths in Shell: - **System back button:** `Activity.OnBackPressed` → `AndroidLifecycle.OnBackPressed` → `Shell` handler → `page.SendBackButtonPressed()` → `OnBackButtonPressed()` - **Navigation bar back button:** `ShellToolbarTracker.OnClick` → `OnNavigateBack()` → `PopAsync()` (directly, bypassing the check) The fix unifies these paths by ensuring both buttons check `OnBackButtonPressed()` before navigating back. **What to avoid:** Don't bypass `SendBackButtonPressed()` when programmatically popping pages in response to user navigation actions. This method is the public API contract for allowing pages to intercept back navigation. ### Issues Fixed Fixes #33523 **Verified behavior:** - ✅ System back button (hardware/gesture) continues to work correctly - ✅ Navigation bar back button now calls `OnBackButtonPressed()` - ✅ Page can prevent navigation by returning `true` - ✅ Page can allow navigation by returning `false` (default behavior) - ✅ Matches Windows platform behavior ### Testing Added UI tests that verify: 1. OnBackButtonPressed is called when tapping the Shell navigation bar back button 2. Returning `true` from OnBackButtonPressed prevents navigation 3. The behavior matches cross-platform expectations **Test files:** - `src/Controls/tests/TestCases.HostApp/Issues/Issue33523.cs` - Test page with Shell navigation - `src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33523.cs` - NUnit test verifying OnBackButtonPressed is called **Test verification:** - ❌ Tests FAIL without the fix (bug reproduced) - ✅ Tests PASS with the fix (bug resolved)
…r back button (#33531) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Description of Change This PR fixes an issue where the Shell navigation bar back button (the arrow in the top-left corner) does not trigger `OnBackButtonPressed()` on Android, while it works correctly on Windows. **Root cause:** The Shell navigation bar back button click handler (`ShellToolbarTracker.OnClick`) was calling `Page.Navigation.PopAsync()` directly without checking if the page wants to intercept the navigation via `OnBackButtonPressed()`. This differs from the system back button behavior, which properly invokes `SendBackButtonPressed()` through the Android lifecycle event system. **Fix:** Added a call to `Page?.SendBackButtonPressed()` in the `OnNavigateBack()` method before calling `PopAsync()`. If the method returns `true` (meaning the page handled the event and wants to prevent navigation), the method returns early and skips the `PopAsync()` call. **Key insight:** The navigation bar back button and system back button take completely different code paths in Shell: - **System back button:** `Activity.OnBackPressed` → `AndroidLifecycle.OnBackPressed` → `Shell` handler → `page.SendBackButtonPressed()` → `OnBackButtonPressed()` - **Navigation bar back button:** `ShellToolbarTracker.OnClick` → `OnNavigateBack()` → `PopAsync()` (directly, bypassing the check) The fix unifies these paths by ensuring both buttons check `OnBackButtonPressed()` before navigating back. **What to avoid:** Don't bypass `SendBackButtonPressed()` when programmatically popping pages in response to user navigation actions. This method is the public API contract for allowing pages to intercept back navigation. ### Issues Fixed Fixes #33523 **Verified behavior:** - ✅ System back button (hardware/gesture) continues to work correctly - ✅ Navigation bar back button now calls `OnBackButtonPressed()` - ✅ Page can prevent navigation by returning `true` - ✅ Page can allow navigation by returning `false` (default behavior) - ✅ Matches Windows platform behavior ### Testing Added UI tests that verify: 1. OnBackButtonPressed is called when tapping the Shell navigation bar back button 2. Returning `true` from OnBackButtonPressed prevents navigation 3. The behavior matches cross-platform expectations **Test files:** - `src/Controls/tests/TestCases.HostApp/Issues/Issue33523.cs` - Test page with Shell navigation - `src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33523.cs` - NUnit test verifying OnBackButtonPressed is called **Test verification:** - ❌ Tests FAIL without the fix (bug reproduced) - ✅ Tests PASS with the fix (bug resolved)
## What's Coming .NET MAUI inflight/candidate introduces significant improvements across all platforms with focus on quality, performance, and developer experience. This release includes 24 commits with various improvements, bug fixes, and enhancements. ## Animation - [Android] Fixed TransformProperties issue when a wrapper view is present by @Ahamed-Ali in #29228 <details> <summary>🔧 Fixes</summary> - [Android Image.Scale produces wrong layout](#7432) </details> ## Button - Fix ImageButton not rendering correctly based on its bounds by @Shalini-Ashokan in #28309 <details> <summary>🔧 Fixes</summary> - [ImageButton dosen't scale Image correctly](#25558) - [ButtonImage width not sizing correctly](#14346) </details> ## CollectionView - [Android] Fixed issue where group Header/Footer template was applied to all items when IsGrouped was true for an ObservableCollection by @Tamilarasan-Paranthaman in #28886 <details> <summary>🔧 Fixes</summary> - [[Android] Group Header/Footer Repeated for All Items When IsGrouped is True for ObservableCollection](#28827) </details> - [Android] CollectionView: Fix reordering when using DataTemplateSelector by @NanthiniMahalingam in #32349 <details> <summary>🔧 Fixes</summary> - [[Android][.NET9] CollectionView Reorderer doesn't work when using TemplateSelector](#32223) </details> - [Android] Fix for incorrect scroll position when using ScrollTo with a header in CollectionView by @SyedAbdulAzeemSF4852 in #30966 <details> <summary>🔧 Fixes</summary> - [Potential off-by-one error when using ScrollTo in CollectionView with a header.](#18389) </details> - Fix Incorrect Scrolling Behavior in CollectionView ScrollTo Method Using Index Value by @Shalini-Ashokan in #27246 <details> <summary>🔧 Fixes</summary> - [CollectionView ScrollTo not working under android](#27117) </details> - [Android] Fix System.IndexOutOfRangeException when scrolling CollectionView with image CarouselView by @devanathan-vaithiyanathan in #31722 <details> <summary>🔧 Fixes</summary> - [System.IndexOutOfRangeException when scrolling CollectionView with image CarouselView](#31680) </details> - [Android] Fix VerticalOffset Update When Modifying CollectionView.ItemsSource While Scrolled by @devanathan-vaithiyanathan in #26782 <details> <summary>🔧 Fixes</summary> - [CollectionView.Scrolled event offset isn't correctly reset when items change on Android](#21708) </details> ## Editor - Fixed Editor vertical text alignment not working after toggling IsVisible by @NanthiniMahalingam in #26194 <details> <summary>🔧 Fixes</summary> - [Editor vertical text alignment not working after toggling IsVisible](#25973) </details> ## Entry - [Android] Fix Numeric Entry not accepting the appropriate Decimal Separator by @devanathan-vaithiyanathan in #27376 <details> <summary>🔧 Fixes</summary> - [Numeric Entry uses wrong decimal separator in MAUI app running on Android](#17152) </details> - [Android & iOS] Entry/Editor: Dismiss keyboard when control becomes invisible by @prakashKannanSf3972 in #27340 <details> <summary>🔧 Fixes</summary> - [android allows type into hidden Entry control](#27236) </details> ## Gestures - [Android] Fixed PointerGestureRecognizer not triggering PointerMoved event by @KarthikRajaKalaimani in #33889 <details> <summary>🔧 Fixes</summary> - [PointerGestureRecognizer does not fire off PointerMove event on Android](#33690) </details> - [Android] Fix PointerMoved and PointerReleased not firing in PointerGestureRecognizer by @KarthikRajaKalaimani in #34209 <details> <summary>🔧 Fixes</summary> - [PointerGestureRecognizer does not fire off PointerMove event on Android](#33690) </details> ## Navigation - [Android] Shell: Fix OnBackButtonPressed not firing for navigation bar back button by @kubaflo in #33531 <details> <summary>🔧 Fixes</summary> - [OnBackButtonPressed not firing for Shell Navigation Bar button in .NET 10 SR2](#33523) </details> ## Shell - [iOS] Fixed Shell Navigating event showing same current and target values by @Vignesh-SF3580 in #25749 <details> <summary>🔧 Fixes</summary> - [OnNavigating wrong target when tapping the same tab](#25599) </details> - [iOS, macOS] Fixed Shell Flyout Icon is always black in iOS 26 by @Dhivya-SF4094 in #32997 <details> <summary>🔧 Fixes</summary> - [Shell Flyout Icon is always black](#32867) - [[iOS] Color Not Applied to Flyout Icon or Title on iOS 26](#33971) </details> ## TitleView - [Android] Fixed duplicate title icon when setting TitleIconImageSource Multiple times by @SubhikshaSf4851 in #31487 <details> <summary>🔧 Fixes</summary> - [[Android] Duplicate Title Icon Appears When Setting NavigationPage.TitleIconImageSource Multiple Times](#31445) </details> ## WebView - Fixed the crash on iOS when setting HeightRequest on WebView inside a ScrollView with IsVisible set to false by @Ahamed-Ali in #29022 <details> <summary>🔧 Fixes</summary> - [Specifying HeightRequest in Webview when wrapped by ScrollView set "invisible" causes crash in iOS](#26795) </details> <details> <summary>🧪 Testing (3)</summary> - [Testing] Fix for enable uitests ios26 by @TamilarasanSF4853 in #33686 - [Testing] Fixed Test case failure in PR 34173 - [02/21/2026] Candidate - 1 by @TamilarasanSF4853 in #34192 - [Testing] Fixed Test case failure in PR 34173 - [02/21/2026] Candidate - 2 by @TamilarasanSF4853 in #34233 </details> <details> <summary>📦 Other (3)</summary> - Fix Glide IllegalArgumentException in PlatformInterop for destroyed activities by @jonathanpeppers via @Copilot in #33805 - [iOS] Fix MauiCALayer and StaticCAShapeLayer crash on finalizer thread by @pshoey in #33818 <details> <summary>🔧 Fixes</summary> - [[iOS] MauiCALayer and StaticCAShapeLayer crash on finalizer thread in Release/AOT builds](#33800) </details> - Merge branch 'main' into inflight/candidate in 1a00f12 </details> **Full Changelog**: main...inflight/candidate --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com> Co-authored-by: pshoey <pshoey@users.noreply.github.com> Co-authored-by: Subhiksha Chandrasekaran <subhiksha.c@syncfusion.com> Co-authored-by: devanathan-vaithiyanathan <114395405+devanathan-vaithiyanathan@users.noreply.github.com> Co-authored-by: prakashKannanSf3972 <127308739+prakashKannanSf3972@users.noreply.github.com> Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com> Co-authored-by: KarthikRajaKalaimani <92777139+KarthikRajaKalaimani@users.noreply.github.com> Co-authored-by: NanthiniMahalingam <105482474+NanthiniMahalingam@users.noreply.github.com> Co-authored-by: BagavathiPerumal <bagavathiperumal.a@syncfusion.com> Co-authored-by: Vignesh-SF3580 <102575140+Vignesh-SF3580@users.noreply.github.com> Co-authored-by: Shalini-Ashokan <shalini.ashokan@syncfusion.com> Co-authored-by: Tamilarasan Paranthaman <93904422+Tamilarasan-Paranthaman@users.noreply.github.com> Co-authored-by: SyedAbdulAzeemSF4852 <syedabdulazeem.a@syncfusion.com> Co-authored-by: Ahamed-Ali <102580874+Ahamed-Ali@users.noreply.github.com> Co-authored-by: Dhivya-SF4094 <127717131+Dhivya-SF4094@users.noreply.github.com> Co-authored-by: Jakub Florkowski <kubaflo123@gmail.com> Co-authored-by: Matthew Leibowitz <mattleibow@live.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: TamilarasanSF4853 <tamilarasan.velu@syncfusion.com>
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Description of Change
This PR fixes an issue where the Shell navigation bar back button (the arrow in the top-left corner) does not trigger
OnBackButtonPressed()on Android, while it works correctly on Windows.Root cause: The Shell navigation bar back button click handler (
ShellToolbarTracker.OnClick) was callingPage.Navigation.PopAsync()directly without checking if the page wants to intercept the navigation viaOnBackButtonPressed(). This differs from the system back button behavior, which properly invokesSendBackButtonPressed()through the Android lifecycle event system.Fix: Added a call to
Page?.SendBackButtonPressed()in theOnNavigateBack()method before callingPopAsync(). If the method returnstrue(meaning the page handled the event and wants to prevent navigation), the method returns early and skips thePopAsync()call.Key insight: The navigation bar back button and system back button take completely different code paths in Shell:
Activity.OnBackPressed→AndroidLifecycle.OnBackPressed→Shellhandler →page.SendBackButtonPressed()→OnBackButtonPressed()ShellToolbarTracker.OnClick→OnNavigateBack()→PopAsync()(directly, bypassing the check)The fix unifies these paths by ensuring both buttons check
OnBackButtonPressed()before navigating back.What to avoid: Don't bypass
SendBackButtonPressed()when programmatically popping pages in response to user navigation actions. This method is the public API contract for allowing pages to intercept back navigation.Issues Fixed
Fixes #33523
Verified behavior:
OnBackButtonPressed()truefalse(default behavior)Testing
Added UI tests that verify:
truefrom OnBackButtonPressed prevents navigationTest files:
src/Controls/tests/TestCases.HostApp/Issues/Issue33523.cs- Test page with Shell navigationsrc/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33523.cs- NUnit test verifying OnBackButtonPressed is calledTest verification: