Skip to content

[Android] Fix for Android 16 Back button is not working after command from FlyoutPage#35196

Merged
kubaflo merged 9 commits into
dotnet:inflight/currentfrom
BagavathiPerumal:fix-33508
May 14, 2026
Merged

[Android] Fix for Android 16 Back button is not working after command from FlyoutPage#35196
kubaflo merged 9 commits into
dotnet:inflight/currentfrom
BagavathiPerumal:fix-33508

Conversation

@BagavathiPerumal

@BagavathiPerumal BagavathiPerumal commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

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!

Issue details

When running on Android 16 (API 36), an issue occurs when Window.Page is replaced while a FlyoutPage is still open. In this scenario, the back navigation behavior becomes inconsistent—specifically, the overridden FlyoutPage.OnBackButtonPressed()method is not invoked as expected. This leads to incorrect or missing handling of the system back action after the page transition. This issue does not occur on Android versions 15, 14, and 13, where the back navigation works as expected.

Root Cause

The issue occurs because, on Android 16 predictive back, the FlyoutViewHandler does not properly clear its drawer and back-interception state when Window.Page is replaced from an open FlyoutPage. As a result, stale state persists across the page transition, preventing the final system back event from reaching the FlyoutPage.OnBackButtonPressed() override.

Description of Change

The fix involves introducing a new internal method, ReleaseDrawerCallbackBeforePageChange(), in FlyoutViewHandler (Android), called from Window.OnPageChanging before the page transition.

On Android 16 (API 36+), it releases the DrawerLayout system-back callback by disposing _pendingFragment, closing the drawer immediately (animated: false), and locking it in LockModeLockedClosed.

Additionally, DisconnectHandler disposes _pendingFragment at the start, preventing stale state and ensuring correct back navigation behavior.

Why Tests were not added:

Regarding the test case: Automated tests are currently executed only on Android API 30. Since this issue is specific to Android 16 (API 36), it cannot be fully validated through the existing automation setup.

Tested the behavior in the following platforms.

  • Android
  • Mac
  • iOS
  • Windows

Issues Fixed

Fixes #33508

Output

Before Issue Fix After Issue Fix
33508-BeforeFix.mov
33508-AfterFix.mov

@github-actions

github-actions Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35196

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35196"

@dotnet-policy-service dotnet-policy-service Bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Apr 28, 2026
@vishnumenon2684 vishnumenon2684 added the community ✨ Community Contribution label Apr 28, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 6 findings

See inline comments for details.

Comment thread src/Core/src/Handlers/FlyoutView/FlyoutViewHandler.Android.cs
Comment thread src/Core/src/Handlers/FlyoutView/FlyoutViewHandler.Android.cs Outdated
Comment thread src/Controls/src/Core/FlyoutPage/FlyoutPage.cs Outdated
Comment thread src/Controls/src/Core/Window/Window.cs Outdated
Comment thread src/Core/src/Handlers/FlyoutView/FlyoutViewHandler.Android.cs
Comment thread src/Controls/src/Core/FlyoutPage/FlyoutPage.cs
@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-win AI found a better alternative fix than the PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Apr 29, 2026
Comment thread src/Core/src/Handlers/FlyoutView/FlyoutViewHandler.Android.cs
Comment thread src/Core/src/Handlers/FlyoutView/FlyoutViewHandler.Android.cs Outdated
Comment thread src/Controls/src/Core/FlyoutPage/FlyoutPage.cs Outdated
Comment thread src/Controls/src/Core/Window/Window.cs Outdated
Comment thread src/Core/src/Handlers/FlyoutView/FlyoutViewHandler.Android.cs
Comment thread src/Controls/src/Core/FlyoutPage/FlyoutPage.cs
@dotnet dotnet deleted a comment from MauiBot Apr 30, 2026
@dotnet dotnet deleted a comment from MauiBot Apr 30, 2026
@dotnet dotnet deleted a comment from MauiBot Apr 30, 2026
@dotnet dotnet deleted a comment from MauiBot Apr 30, 2026
@dotnet dotnet deleted a comment from MauiBot Apr 30, 2026
@dotnet dotnet deleted a comment from MauiBot Apr 30, 2026
@dotnet dotnet deleted a comment from MauiBot Apr 30, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Automated review — alternative fix proposed

The expert-reviewer evaluation compared the PR fix against #1 automatically generated candidates and selected try-fix-1 as the strongest fix.

Why: try-fix-1 implements the same DrawerLayout callback release as the PR but entirely within FlyoutViewHandler.DisconnectHandler — eliminating the cross-layer Window→FlyoutPage→Handler call chain, widening the API guard from 36 to 33, and handling nested FlyoutPage topologies. It requires changes to only one file (FlyoutViewHandler.Android.cs) vs the PR's three files.

Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.

Candidate diff (`try-fix-1`)
diff --git a/src/Core/src/Handlers/FlyoutView/FlyoutViewHandler.Android.cs b/src/Core/src/Handlers/FlyoutView/FlyoutViewHandler.Android.cs
index fafb0d901c..2e408fd79b 100644
--- a/src/Core/src/Handlers/FlyoutView/FlyoutViewHandler.Android.cs
+++ b/src/Core/src/Handlers/FlyoutView/FlyoutViewHandler.Android.cs
@@ -311,6 +311,22 @@ namespace Microsoft.Maui.Handlers
 
 		protected override void DisconnectHandler(View platformView)
 		{
+			_pendingFragment?.Dispose();
+			_pendingFragment = null;
+
+			// On Android 13+ (API 33+), DrawerLayout registers a system OnBackInvokedCallback
+			// when it has an open or openable drawer. If the handler disconnects without closing
+			// the drawer and locking it first, that callback survives and shadows any new page's
+			// back-handling callbacks. Close and lock the drawer synchronously so the system
+			// callback is unregistered before the view hierarchy is torn down.
+			if (OperatingSystem.IsAndroidVersionAtLeast(33) && platformView is DrawerLayout drawerLayout)
+			{
+				if (_flyoutView is not null && _flyoutView.Parent == drawerLayout)
+					drawerLayout.CloseDrawer(_flyoutView, false);
+
+				drawerLayout.SetDrawerLockMode(DrawerLayout.LockModeLockedClosed);
+			}
+
 			MauiWindowInsetListener.UnregisterView(platformView);
 			if (_navigationRoot is CoordinatorLayout cl)
 			{

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you please try ai's suggestions?

@dotnet dotnet deleted a comment from MauiBot May 2, 2026
@kubaflo kubaflo dismissed MauiBot’s stale review May 2, 2026 23:37

Resetting for re-review

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 5 findings

See inline comments for details.

@MauiBot MauiBot added s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates and removed s/agent-fix-win AI found a better alternative fix than the PR labels May 3, 2026
@dotnet dotnet deleted a comment from MauiBot May 3, 2026
@dotnet dotnet deleted a comment from MauiBot May 3, 2026
@MauiBot MauiBot added s/agent-review-incomplete and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues labels May 8, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 7 findings

See inline comments for details.

Comment thread src/Controls/src/Core/Window/Window.cs Outdated
Comment thread src/Controls/src/Core/Window/Window.cs
Comment thread src/Core/src/Handlers/FlyoutView/FlyoutViewHandler.Android.cs
Comment thread src/Core/src/Handlers/FlyoutView/FlyoutViewHandler.Android.cs Outdated
Comment thread src/Controls/src/Core/FlyoutPage/FlyoutPage.cs Outdated
Comment thread src/Core/src/Handlers/FlyoutView/FlyoutViewHandler.Android.cs
Comment thread src/Core/src/Handlers/FlyoutView/FlyoutViewHandler.Android.cs
…View2 is not connected in Appium. (dotnet#35335)

### Description of Changes

- Recently, the Appium driver has not been connecting properly to the
native WebView2 control on Windows. While running locally using Appium
Inspector with the WebView control, the inspector is unable to recognize
the WebView and displays an error.

- Due to this Appium driver issue, the WebView lane in CI takes a long
time to run (approximately 3 hours) and eventually gets cancelled. As a
temporary workaround, the WebView lane has been temporarily removed from
the Windows CI pipeline to allow the CI process to complete more
quickly.
<img width="649" height="294" alt="image"
src="https://github.com/user-attachments/assets/68df006b-56d6-4bfa-870a-a4184f5b18b7"
/>
<img width="576" height="430" alt="image"
src="https://github.com/user-attachments/assets/40c222e8-4935-450d-be7e-5ee9245e9eb1"
/>


**Issue:** dotnet#35334
@dotnet dotnet deleted a comment from MauiBot May 11, 2026
@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-win AI found a better alternative fix than the PR and removed s/agent-review-incomplete s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates labels May 11, 2026
…ler state on Android 16 (API 36) when replacing Window.Page from an open FlyoutPage, preventing stale back-interception behavior.
@dotnet dotnet deleted a comment from MauiBot May 12, 2026
@MauiBot MauiBot added s/agent-review-incomplete and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues labels May 12, 2026
@kubaflo

kubaflo commented May 12, 2026

Copy link
Copy Markdown
Contributor

/azp run maui-pr-uitests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@MauiBot

MauiBot commented May 13, 2026

Copy link
Copy Markdown
Collaborator

🤖 AI Summary

👋 @BagavathiPerumal — new AI review results are available. Please review the latest session below.

📊 Review Session5ec60d7 · fix-33508-Optimized the code implementation. · 2026-05-13 20:20 UTC
🚦 Gate — Test Before & After Fix

Gate Result: ⚠️ SKIPPED

No tests were detected in this PR.

Recommendation: Add tests to verify the fix using the write-tests-agent.


🧪 UI Tests — FlyoutPage,ViewBaseTests,Window

Detected UI test categories: FlyoutPage,ViewBaseTests,Window

Deep UI tests — 266 passed, 12 failed across 3 categories on platform-pool agent (replaces in-process counts above).

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
controls-FlyoutPage 10/14 (4 ❌)
controls-ViewBaseTests 128/133 (4 ❌)
controls-Window 128/133 (4 ❌)
controls-FlyoutPage — 4 failed tests
VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover
Flyout did not open after multiple drag attempts
Assert.That(flyoutOpened, Is.True)
  Expected: True
  But was:  False
at Microsoft.Maui.TestCases.Tests.FlyoutPageFeatureTests.VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/FlyoutPageFeatureTests.cs:line 149

1)    at Microsoft.Maui.TestCases.Tests.FlyoutPageFeatureTests.VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/FlyoutPageFeatureTests.cs:line 149
ShouldKeepFlyoutLockedWhenSwitchingLandScapeToPortrait
OpenQA.Selenium.InvalidElementStateException : Screen rotation cannot be changed to ROTATION_0 after 2000ms. Is it locked programmatically?
at OpenQA.Selenium.WebDriver.UnpackAndThrowOnError(Response errorResponse, String commandToExecute)
   at OpenQA.Selenium.WebDriver.ExecuteAsync(String driverCommandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.Appium.AppiumDriver.Execute(String driverCommandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.Appium.AppiumDriver.set_Orientation(ScreenOrientation value)
   at UITest.Appium.AppiumOrientationActions.SetOrientationPortrait(IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumOrientationActions.cs:line 43
   at UITest.Appium.AppiumOrientationActions.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumOrientationActions.cs:line 34
   at UITest.Appium.AppiumCommandExecutor.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUti
...
VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover
Flyout did not open after multiple drag attempts
Assert.That(flyoutOpened, Is.True)
  Expected: True
  But was:  False
at Microsoft.Maui.TestCases.Tests.FlyoutPageFeatureTests.VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/FlyoutPageFeatureTests.cs:line 149

1)    at Microsoft.Maui.TestCases.Tests.FlyoutPageFeatureTests.VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/FlyoutPageFeatureTests.cs:line 149
ShouldKeepFlyoutLockedWhenSwitchingLandScapeToPortrait
OpenQA.Selenium.InvalidElementStateException : Screen rotation cannot be changed to ROTATION_0 after 2000ms. Is it locked programmatically?
at OpenQA.Selenium.WebDriver.UnpackAndThrowOnError(Response errorResponse, String commandToExecute)
   at OpenQA.Selenium.WebDriver.ExecuteAsync(String driverCommandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.Appium.AppiumDriver.Execute(String driverCommandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.Appium.AppiumDriver.set_Orientation(ScreenOrientation value)
   at UITest.Appium.AppiumOrientationActions.SetOrientationPortrait(IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumOrientationActions.cs:line 43
   at UITest.Appium.AppiumOrientationActions.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumOrientationActions.cs:line 34
   at UITest.Appium.AppiumCommandExecutor.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUti
...
controls-ViewBaseTests — 4 failed tests
VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover
Flyout did not open after multiple drag attempts
Assert.That(flyoutOpened, Is.True)
  Expected: True
  But was:  False
at Microsoft.Maui.TestCases.Tests.FlyoutPageFeatureTests.VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/FlyoutPageFeatureTests.cs:line 149

1)    at Microsoft.Maui.TestCases.Tests.FlyoutPageFeatureTests.VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/FlyoutPageFeatureTests.cs:line 149
ShouldKeepFlyoutLockedWhenSwitchingLandScapeToPortrait
OpenQA.Selenium.InvalidElementStateException : Screen rotation cannot be changed to ROTATION_0 after 2000ms. Is it locked programmatically?
at OpenQA.Selenium.WebDriver.UnpackAndThrowOnError(Response errorResponse, String commandToExecute)
   at OpenQA.Selenium.WebDriver.ExecuteAsync(String driverCommandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.Appium.AppiumDriver.Execute(String driverCommandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.Appium.AppiumDriver.set_Orientation(ScreenOrientation value)
   at UITest.Appium.AppiumOrientationActions.SetOrientationPortrait(IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumOrientationActions.cs:line 43
   at UITest.Appium.AppiumOrientationActions.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumOrientationActions.cs:line 34
   at UITest.Appium.AppiumCommandExecutor.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUti
...
VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover
Flyout did not open after multiple drag attempts
Assert.That(flyoutOpened, Is.True)
  Expected: True
  But was:  False
at Microsoft.Maui.TestCases.Tests.FlyoutPageFeatureTests.VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/FlyoutPageFeatureTests.cs:line 149

1)    at Microsoft.Maui.TestCases.Tests.FlyoutPageFeatureTests.VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/FlyoutPageFeatureTests.cs:line 149
ShouldKeepFlyoutLockedWhenSwitchingLandScapeToPortrait
OpenQA.Selenium.InvalidElementStateException : Screen rotation cannot be changed to ROTATION_0 after 2000ms. Is it locked programmatically?
at OpenQA.Selenium.WebDriver.UnpackAndThrowOnError(Response errorResponse, String commandToExecute)
   at OpenQA.Selenium.WebDriver.ExecuteAsync(String driverCommandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.Appium.AppiumDriver.Execute(String driverCommandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.Appium.AppiumDriver.set_Orientation(ScreenOrientation value)
   at UITest.Appium.AppiumOrientationActions.SetOrientationPortrait(IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumOrientationActions.cs:line 43
   at UITest.Appium.AppiumOrientationActions.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumOrientationActions.cs:line 34
   at UITest.Appium.AppiumCommandExecutor.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUti
...
controls-Window — 4 failed tests
VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover
Flyout did not open after multiple drag attempts
Assert.That(flyoutOpened, Is.True)
  Expected: True
  But was:  False
at Microsoft.Maui.TestCases.Tests.FlyoutPageFeatureTests.VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/FlyoutPageFeatureTests.cs:line 149

1)    at Microsoft.Maui.TestCases.Tests.FlyoutPageFeatureTests.VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/FlyoutPageFeatureTests.cs:line 149
ShouldKeepFlyoutLockedWhenSwitchingLandScapeToPortrait
OpenQA.Selenium.InvalidElementStateException : Screen rotation cannot be changed to ROTATION_0 after 2000ms. Is it locked programmatically?
at OpenQA.Selenium.WebDriver.UnpackAndThrowOnError(Response errorResponse, String commandToExecute)
   at OpenQA.Selenium.WebDriver.ExecuteAsync(String driverCommandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.Appium.AppiumDriver.Execute(String driverCommandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.Appium.AppiumDriver.set_Orientation(ScreenOrientation value)
   at UITest.Appium.AppiumOrientationActions.SetOrientationPortrait(IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumOrientationActions.cs:line 43
   at UITest.Appium.AppiumOrientationActions.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumOrientationActions.cs:line 34
   at UITest.Appium.AppiumCommandExecutor.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUti
...
VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover
Flyout did not open after multiple drag attempts
Assert.That(flyoutOpened, Is.True)
  Expected: True
  But was:  False
at Microsoft.Maui.TestCases.Tests.FlyoutPageFeatureTests.VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/FlyoutPageFeatureTests.cs:line 149

1)    at Microsoft.Maui.TestCases.Tests.FlyoutPageFeatureTests.VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/FlyoutPageFeatureTests.cs:line 149
ShouldKeepFlyoutLockedWhenSwitchingLandScapeToPortrait
OpenQA.Selenium.InvalidElementStateException : Screen rotation cannot be changed to ROTATION_0 after 2000ms. Is it locked programmatically?
at OpenQA.Selenium.WebDriver.UnpackAndThrowOnError(Response errorResponse, String commandToExecute)
   at OpenQA.Selenium.WebDriver.ExecuteAsync(String driverCommandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.Appium.AppiumDriver.Execute(String driverCommandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.Appium.AppiumDriver.set_Orientation(ScreenOrientation value)
   at UITest.Appium.AppiumOrientationActions.SetOrientationPortrait(IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumOrientationActions.cs:line 43
   at UITest.Appium.AppiumOrientationActions.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumOrientationActions.cs:line 34
   at UITest.Appium.AppiumCommandExecutor.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUti
...

📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)


🔍 Pre-Flight — Context & Validation

Pre-Flight: PR #35196

Issue

  • Android: BackButton on Android 16 not working after command from FlyOutPage #33508 — On Android 16 (API 36), after navigating from an open FlyoutPage (via flyout button that replaces Window.Page), the system back button no longer reaches Page.OnBackButtonPressed() on the new page.
  • Repro requires real device on API 36; works on 13/14/15.
  • Root cause: Android 16 made predictive back mandatory. DrawerLayout now registers its own callback directly with OnBackInvokedDispatcher, bypassing MAUI's OnBackPressedDispatcher interception. When Window.Page is swapped while the drawer's callback is still registered, the stale DrawerLayout callback shadows the new page's back handlers.

PR Approach (head: 5ec60d7d)

Three-file change:

  1. FlyoutViewHandler.Android.cs — New internal void ReleaseDrawerCallbackBeforePageChange() that, on API ≥36, disposes _pendingFragment, CloseDrawer(_flyoutView, false) (animated:false → synchronous), and SetDrawerLockMode(LockModeLockedClosed). A _releasing flag suppresses the spurious IsPresented write in OnDrawerStateChanged. A new helper CancelPendingFragment() deduplicates dispose pattern. DisconnectHandler also calls CancelPendingFragment() first.
  2. Window.csOnPageChanging now (Android-only) walks the outgoing page tree via ReleaseFlyoutDrawerCallbacks(oldPage), recursing through IPageContainer<Page>.CurrentPage, casting oldPage.Handler to concrete FlyoutViewHandler, and calling the release method before SendDisappearing().
  3. FlyoutPage.cs — Cosmetic blank-line addition only (the originally-proposed Android-only shim was removed in response to layering review).

Critical Constraints

  • Cleanup must be synchronous and BEFORE the new page registers its back callback.
    • Author confirmed in comments: any approach that relies on DisconnectHandler() is too late, because MAUI defers handler disconnect to OnUnloaded (async, after new page connects). This rules out the prior try-fix-1 (handler-self-cleanup in DisconnectHandler).
  • Cleanup must run for nested cases: NavigationPage(FlyoutPage), Shell(FlyoutPage), TabbedPage(FlyoutPage), etc. The recursive walk addresses one branch (CurrentPage) but does not cover sibling tabs or modal pages.
  • No CI test possible: API 36 not available on Helix; the team accepts no automated regression test here. Manual repro validated by author.

Prior Exploration (per PR's AI summary, history-trained agent)

# Approach Status
1 DisconnectHandler self-cleanup with event unsubscribe Rejected — disconnect is async, runs too late
2 FlyoutPage.OnDisappearing/OnAppearing + temp event unsub Passed CI but does it run synchronously before new page connects?
3 OnWindowChanged driving IsPresented=false via mapper Same timing concern
4 ViewDetachedFromWindow one-shot self-unsubscribing handler Same timing concern
5 Attachment-gated guard in OnDrawerStateChanged Workaround at invocation, not a release
6 Direct UnregisterOnBackInvokedCallback on dispatcher Blocked — DrawerLayout's internal callback not publicly accessible

The author's last comment (post-iteration) makes clear: the only timing window that works is Window.OnPageChanging (synchronous, before SendDisappearing kicks off teardown). So genuinely new approaches must either:

  • Use a different release mechanism invoked from the same OnPageChanging window, OR
  • Use a higher-priority Activity-level callback that can win even if DrawerLayout's stale callback persists, OR
  • Rework so the DrawerLayout never registers its system callback in the first place (subclass / opt-out).

Code Review Status (latest from history-trained AI bot)

  • 0 errors, 2 warnings (test absence + IsPresented race — both addressed in latest iteration via _releasing flag and TODO).
  • 2 suggestions (concrete cast, nested coverage — both addressed: cast is intra-handler, nested via recursive walk).

Files Touched

  • src/Controls/src/Core/FlyoutPage/FlyoutPage.cs (+1 LoC, blank line)
  • src/Controls/src/Core/Window/Window.cs (+30 LoC)
  • src/Core/src/Handlers/FlyoutView/FlyoutViewHandler.Android.cs (+67 LoC, -1 LoC)

Test Strategy for Candidates

  • Build verification only: dotnet build src/Core/src/Core.csproj -f net10.0-android and dotnet build src/Controls/src/Core/Controls.Core.csproj -f net10.0-android. CI device tests will not catch this regression (API 30 only). Expert reviewer must judge correctness via code review.

📋 Report — Final Recommendation

Comparative Report — PR #35196

Issue: Android 16 (API 36) — FlyoutPage.OnBackButtonPressed() not invoked after Window.Page is replaced from an open FlyoutPage.

Root cause: AndroidX DrawerLayout 1.2.0+ registers an OnBackInvokedCallback (PRIORITY_OVERLAY) directly with the system OnBackInvokedDispatcher. On API 36 predictive back is mandatory, so MAUI's OnBackPressedDispatcher interception is bypassed; a stale callback shadows the new page's handlers if the drawer is still "visible" at swap time.

Gate: SKIPPED (no tests in PR, accepted — Helix is API 30 only; API 36 emulator unavailable in CI).


Candidates

ID Description Build Regression test Coverage scope
pr The PR as submitted (3 files, +98/-1) Implicit ✓ (CI passing) n/a (no test) — manually verified by author NavigationPage/Shell CurrentPage only
pr-plus-reviewer pr + reviewer fix: Window.cs walks all MultiPage<Page>.Children (TabbedPage non-current tabs) ✓ Controls.Core net10.0-android36.0 n/a (no test) + non-current TabbedPage tabs
try-fix-3 Subclass DrawerLayout as MauiDrawerLayout for "ownership" of back-callback lifecycle Not applied (no diff produced) n/a n/a

Per-candidate evaluation

pr — PR as submitted

  • Strengths: Correct release mechanism (SetDrawerLockMode(LockModeLockedClosed) synchronously deregisters the OnBackInvokedCallback). Correct timing (OnPageChanging before SendDisappearing). _releasing re-entrancy guard is sound. internal keeps the contract out of public API. Symmetric CancelPendingFragment() extraction also closes a small leak in DisconnectHandler.
  • Weaknesses:
    • Recursive walk follows only IPageContainer<Page>.CurrentPage. Misses non-current TabbedPage tabs that may host a FlyoutPage with an open drawer.
    • API gate >=36 doesn't catch the API 33–35 opt-in case (enableOnBackInvokedCallback="true"). Real-world but rare.
    • Modal stack (Window.Navigation.ModalStack) not walked. Edge case.
    • Lock-mode override persists if the same FlyoutPage instance is reused without DisconnectHandler firing in between. Edge case.
  • Test status: None. Acceptable per CI constraint.

pr-plus-reviewer — PR + reviewer feedback

  • All of pr's strengths. Plus:
  • Closes the non-current TabbedPage children coverage gap with a MultiPage<Page> enumeration of Children, falling back to CurrentPage for NavigationPage/Shell. +12 LoC.
  • Build verified: dotnet build src/Controls/src/Core/Controls.Core.csproj -f net10.0-android36.0 → 0 warnings, 0 errors.
  • Reviewer also flagged 3 nits + 2 suggestions left as comments for the author (API gate widening, lifecycle reuse comment, threading contract comment, side-effect comment, layering note) — none required to ship.
  • Test status: None. Acceptable per CI constraint.

try-fix-3 — Subclass DrawerLayout

  • Verdict in the try-fix report: "Decline this approach. Use the PR (try-fix Update README.md #2) instead."
  • The investigation establishes (with AndroidX source citations) that AndroidX exposes no protected hook that a subclass could exploit — mBackInvokedCallback, mBackInvokedDispatcher, updateBackInvokedCallbackState are all private/package-private. Subclassing reduces to calling the same setDrawerLockMode / closeDrawers the PR already calls, wrapped in extra ceremony (Java [Register] binding, axml class-name change, R8/trim hints).
  • Diff: Empty — explicitly not applied.
  • Build / test: None.
  • Net effect: Same release mechanism as the PR, more surface area, no behavioural improvement. Worth recording as an explored-and-blocked finding, not as a viable competing fix.

Comparison matrix

Dimension pr pr-plus-reviewer try-fix-3
Regression test passes n/a n/a n/a
Build verified ✓ (CI) ✓ (local) ✗ (not produced)
Fixes the root regression n/a (no diff)
Coverage: NavigationPage(FlyoutPage)
Coverage: TabbedPage current tab
Coverage: TabbedPage non-current tabs
Coverage: ModalStack
Coverage: API 33–35 opt-in case
Layering quality Acceptable Acceptable Adds one more layer with no semantic value
Surface area 3 files / +98 3 files / +110 Would add 4 files + axml + JNI binding
Risk Low Low Higher (axml inflation, R8/trim)

Winner

pr-plus-reviewer — strict superset of pr's correctness with one real coverage improvement (non-current TabbedPage children) verified by build. try-fix-3 was explicitly declined by its own investigation as no-better-than-the-PR, so per the rule "candidates that failed regression tests rank lower than those that passed," try-fix-3 ranks lowest because it produced no working candidate diff at all.

The reviewer's remaining recommendations (API 33–35 opt-in case, modal-stack walk, comment expansions, threading-contract one-liner) are posted as inline PR comments and left to the author's discretion — not required for the winning candidate.


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-win AI found a better alternative fix than the PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Android: BackButton on Android 16 not working after command from FlyOutPage

8 participants