[Android/iOS] Fix SwipeItem visibility change causing double command execution in Execute mode#35087
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35087Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35087" |
There was a problem hiding this comment.
Pull request overview
Fixes a SwipeView Execute-mode behavior on Android and iOS where changing a sibling SwipeItem’s visibility during command execution can cause multiple commands to run from a single swipe (Issue #7580).
Changes:
- Android/iOS: Execute only the first visible
SwipeItemin Execute mode instead of iterating and executing all visible items. - Adds a HostApp reproduction page (
Issue7580) that togglesSwipeItem.IsVisiblefrom the invoked command. - Adds an Appium/NUnit UI test to validate the command is invoked exactly once per swipe.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/Core/src/Platform/iOS/MauiSwipeView.cs | Changes Execute-mode invocation to execute only the first visible item (prevents double execution). |
| src/Core/src/Platform/Android/MauiSwipeView.cs | Same Execute-mode fix as iOS to align behavior and avoid side effects during enumeration. |
| src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue7580.cs | Adds UI test that swipes and asserts only one invocation / single state toggle. |
| src/Controls/tests/TestCases.HostApp/Issues/Issue7580.cs | Adds HostApp issue page reproducing visibility-toggled double execution scenario. |
| var startX = rect.X + 20; | ||
| var endX = startX + 600; |
There was a problem hiding this comment.
The drag gesture uses endX = startX + 600, which can exceed the viewport width on smaller devices/simulators (and DragCoordinates performs absolute viewport pointer moves). This can cause out-of-bounds pointer actions and flaky failures. Derive endX from the target element’s bounds (e.g., rect.X + rect.Width - padding) or otherwise clamp the end coordinate to the screen width while still exceeding the swipe threshold.
| var startX = rect.X + 20; | |
| var endX = startX + 600; | |
| var padding = 20; | |
| var startX = rect.X + padding; | |
| var endX = global::System.Math.Max(startX + 1, rect.X + rect.Width - padding); |
| }; | ||
| swipeContent.Add(new Label | ||
| { | ||
| Text = "Swipe right to toggle", | ||
| AutomationId = "SwipeTarget", |
There was a problem hiding this comment.
SwipeTarget AutomationId is attached to the centered label inside the SwipeView content, so GetRect() only covers the label’s small, centered bounds. That makes coordinate-based swipe gestures less reliable (start point is mid-screen and end point calculations can drift out of bounds). Put the AutomationId on the full swipe content container (e.g., the Grid assigned to SwipeView.Content) so the rect represents the intended swipe surface.
| }; | |
| swipeContent.Add(new Label | |
| { | |
| Text = "Swipe right to toggle", | |
| AutomationId = "SwipeTarget", | |
| AutomationId = "SwipeTarget", | |
| }; | |
| swipeContent.Add(new Label | |
| { | |
| Text = "Swipe right to toggle", |
kubaflo
left a comment
There was a problem hiding this comment.
Cold you please review the ai's summary?
🤖 AI Summary
📊 Review Session —
|
| Check | Expected | Actual | Result |
|---|---|---|---|
| Tests WITHOUT fix | FAIL | FAIL | ✅ |
| Tests WITH fix | PASS | PASS | ✅ |
✅ Final Verdict
VERIFICATION PASSED ✅
The tests correctly detect the issue:
- ✅ Tests FAIL without the fix (as expected - bug is present)
- ✅ Tests PASS with the fix (as expected - bug is fixed)
Conclusion: The tests properly validate the fix and catch the bug when it's present.
Configuration
Platform: android
Test Filter: Issue7580
Base Branch: main
Merge Base: a38e0bb
Fix Files
src/Core/src/Platform/Android/MauiSwipeView.cssrc/Core/src/Platform/iOS/MauiSwipeView.cs
Test Results Details
Test Run 1: WITHOUT Fix
Expected: Tests should FAIL (bug is present)
Actual: Tests FAILED ✅
View full test output (without fix)
Determining projects to restore...
Restored /home/vsts/work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 947 ms).
Restored /home/vsts/work/1/s/src/Essentials/src/Essentials.csproj (in 5.29 sec).
Restored /home/vsts/work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 9.12 sec).
Restored /home/vsts/work/1/s/src/Core/src/Core.csproj (in 2.51 sec).
Restored /home/vsts/work/1/s/src/Core/maps/src/Maps.csproj (in 1.57 sec).
Restored /home/vsts/work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 53 ms).
Restored /home/vsts/work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 34 ms).
Restored /home/vsts/work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 63 ms).
Restored /home/vsts/work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj (in 84 ms).
Restored /home/vsts/work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 63 ms).
Restored /home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj (in 2.85 sec).
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: Mono.AndroidTools.InstallFailedException: Unexpected install output: cmd: Failure calling service package: Broken pipe (32) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Mono.AndroidTools.Internal.AdbOutputParsing.CheckInstallSuccess(String output, String packageName) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Mono.AndroidTools.AndroidDevice.<>c__DisplayClass105_0.<InstallPackage>b__0(Task`1 t) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Xamarin.Android.Tasks.FastDeploy.RunInstall() [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
Build FAILED.
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: Mono.AndroidTools.InstallFailedException: Unexpected install output: cmd: Failure calling service package: Broken pipe (32) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Mono.AndroidTools.Internal.AdbOutputParsing.CheckInstallSuccess(String output, String packageName) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Mono.AndroidTools.AndroidDevice.<>c__DisplayClass105_0.<InstallPackage>b__0(Task`1 t) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Xamarin.Android.Tasks.FastDeploy.RunInstall() [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
0 Warning(s)
1 Error(s)
Time Elapsed 00:16:52.82
* daemon not running; starting now at tcp:5037
* daemon started successfully
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:09:25.62
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Test Run 2: WITH Fix
Expected: Tests should PASS (bug is fixed)
Actual: Tests PASSED ✅
View full test output (with fix)
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
##vso[build.updatebuildnumber]10.0.60-ci+azdo.13932964
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:07:02.77
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Logs
- Full verification log:
/home/vsts/work/1/s/CustomAgentLogsTmp/PRState/35087/PRAgent/gate/verify-tests-fail/verification-log.txt - Test output without fix:
/home/vsts/work/1/s/CustomAgentLogsTmp/PRState/35087/PRAgent/gate/verify-tests-fail/test-without-fix.log - Test output with fix:
/home/vsts/work/1/s/CustomAgentLogsTmp/PRState/35087/PRAgent/gate/verify-tests-fail/test-with-fix.log - UI test logs:
CustomAgentLogsTmp/UITests/
🧪 UI Tests — Category Detection
Detected UI test categories: SwipeView,ViewBaseTests
🔍 Pre-Flight — Context & Validation
Issue: #7580 - Changing visibility on a SwipeItem causes multiple items to be executed
PR: #35087 - [Android/iOS] Fix SwipeItem visibility change causing double command execution in Execute mode
Platforms Affected: Android, iOS (fix); Tizen (not updated — see code review)
Files Changed: 2 implementation, 2 test
Key Findings
- In
SwipeMode.Execute, iterating all visibleSwipeItemsand executing each creates a race: executing item A can toggle item B from hidden → visible mid-loop, causing item B to also execute unintentionally. - Fix replaces
foreachloop withFirstOrDefault(GetIsVisible)on both Android and iOSMauiSwipeView.cs, executing only the first visible item. - Tizen (
src/Core/src/Platform/Tizen/MauiSwipeView.cs) still has the oldforeachloop — not updated in this PR. - UI test uses hardcoded 600px drag offset which may exceed screen bounds on small devices.
- A prior Copilot inline review noted: (1) the drag
endX = startX + 600can exceed viewport width; (2)SwipeTargetAutomationId is on a centered Label inside the SwipeView rather than on the full swipe container.
Code Review Summary
Verdict: NEEDS_CHANGES
Confidence: medium
Errors: 0 | Warnings: 3 | Suggestions: 3
Key code review findings:
⚠️ src/Core/src/Platform/Tizen/MauiSwipeView.cs— Tizen still uses the oldforeachloop, creating a behavioral split (Android/iOS execute first-only; Tizen executes all)⚠️ Silent behavioral change: any app with multiple visibleSwipeItems in Execute mode expecting all to fire will break silently⚠️ src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue7580.cs:28— hardcodedendX = startX + 600may exceed screen width on small devices- 💡 Test only verifies one swipe; second swipe assertion would improve coverage
- 💡
Debug.WriteLineleft in HostApp (line 50) - 💡 Missing trailing newline in test file
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #35087 | Replace foreach loop with FirstOrDefault(GetIsVisible) on Android & iOS |
✅ PASSED (Gate) | Android/MauiSwipeView.cs, iOS/MauiSwipeView.cs |
Original PR; Tizen not updated |
🔬 Code Review — Deep Analysis
Code Review — PR #35087
Independent Assessment
What this changes: In Android/MauiSwipeView.cs and iOS/MauiSwipeView.cs, ValidateSwipeThreshold() is changed from iterating all visible SwipeItems and executing each, to calling FirstOrDefault(GetIsVisible) and executing only the first visible item. New UI test (Issue7580) and HostApp test page are also added.
Inferred motivation: The old foreach loop calls ExecuteSwipeItem() on each visible item sequentially. If executing item A causes item B's visibility to toggle from hidden → visible mid-loop, item B is picked up in the same iteration and also executed — leading to unintended double invocations.
Reconciliation with PR Narrative
Author claims: The fix aligns Android/iOS with WinUI behavior (where Execute mode fires only one item). Root cause correctly identified as dynamic visibility evaluation during iteration.
Agreement/disagreement: The root cause analysis is accurate, and the fix is effective for the reported scenario. The WinUI alignment claim is plausible — WinUI's SwipeItems in Execute mode fires only the triggered item, not all visible ones. One notable gap: the PR does not address Tizen, which contains the identical pre-fix loop.
Findings
⚠️ Warning — Tizen platform not updated — creates persistent cross-platform inconsistency
src/Core/src/Platform/Tizen/MauiSwipeView.cs still uses the old foreach (var swipeItem in swipeItems) { if (GetIsVisible(swipeItem)) ExecuteSwipeItem(swipeItem); } loop. After this PR, Android and iOS execute only the first visible item, but Tizen will continue to execute all visible items. This creates a three-way behavioral split: Windows (native, single-item), Android/iOS (fixed, single-item), Tizen (old, all-items).
⚠️ Warning — Silent behavioral change for legitimate multi-item Execute-mode SwipeViews
The fix changes the observable behavior of any app that intentionally places multiple visible SwipeItems in SwipeMode.Execute and expects all of them to fire on a swipe. This is a correct fix for the reported scenario, but it's a breaking behavioral change for apps relying on the old semantics. There's no deprecation notice or docs update.
⚠️ Warning — Test uses hardcoded pixel drag distance that may be fragile on low-density devices
In Issue7580.cs (shared tests), the drag gesture is computed as:
var endX = startX + 600;
App.DragCoordinates(startX, centerY, endX, centerY);The HostApp sets Threshold = 250, but the 600-pixel absolute offset may exceed the screen width on low-density or small-screen test devices, causing the drag to be clipped.
💡 Suggestion — Test only validates one swipe; consider verifying second swipe
The test verifies InvokeCount: 1 after one swipe but doesn't verify that a second swipe also invokes exactly once. Adding a second swipe assertion would make the test more robust.
💡 Suggestion — Debug.WriteLine left in HostApp test page
Issue7580.cs (HostApp) line 50 emits debug log on every swipe. Remove or guard with #if DEBUG.
💡 Suggestion — Missing newline at end of test file
TestCases.Shared.Tests/Tests/Issues/Issue7580.cs ends without a trailing newline. dotnet format will flag this.
Devil's Advocate
- Tizen: Tizen is a lower-priority platform in MAUI and is often left out of platform-specific fixes. However, the code is a direct copy of the iOS/Android pattern, so the fix is trivial. Worth flagging even if intentionally deferred.
- Behavioral change: The WinUI native SwipeView does not support multiple Execute-mode items at the platform level. The "breaking" scenario is niche, and the fix is arguably more correct than the previous behavior.
- Hardcoded offset: Many other UI tests in the repo use fixed pixel coordinates. The concern is real but may not manifest given current test device specs.
Verdict: NEEDS_CHANGES
Confidence: medium
Summary: The core fix is correct and the root cause analysis is accurate. However, Tizen is left with the unfixed behavior, creating a cross-platform inconsistency. The test's hardcoded 600px drag distance is a fragility risk. These issues are straightforward to address and should be resolved before merge.
🔧 Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix (claude-opus-4.6) | Snapshot visible items before execution — .Where(GetIsVisible).ToList() to freeze list before loop |
✅ PASS | Android + iOS + Tizen MauiSwipeView.cs | Preserves "all originally-visible items execute" semantics; also fixes Tizen |
| 2 | try-fix (claude-sonnet-4.6) | Reentrancy guard (_isExecutingSwipeItems flag) + snapshot (ToList) — both guard and snapshot combined | ✅ PASS | Android + iOS + Tizen MauiSwipeView.cs | Two-layer defense; also fixes Tizen |
| 3 | try-fix (gpt-5.3-codex) | Initial-visibility gating — snapshot HashSet before loop, check membership before each ExecuteSwipeItem call | ✅ PASS | Android + iOS + Tizen MauiSwipeView.cs | Per-item guard; also fixes Tizen |
| 4 | try-fix (gemini-3-pro-preview) | BLOCKED — model unavailable | BLOCKED | — | gemini-3-pro-preview not in available models list |
| PR | PR #35087 | Replace foreach loop with FirstOrDefault(GetIsVisible) — execute only first visible item |
✅ PASSED (Gate) | Android + iOS MauiSwipeView.cs | Original PR; Tizen not updated |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| claude-opus-4.6 | 2 | No | "NO NEW IDEAS" — four approaches cover the solution space |
Exhausted: Yes
Selected Fix: PR's fix — FirstOrDefault(GetIsVisible) is the semantically cleanest for Execute mode (one action per swipe), aligns with WinUI behavior, and Gate-confirmed working. All alternative approaches (attempts 1–3) also passed but with different semantics: they preserve "all originally-visible items execute" while also fixing Tizen. The PR's approach is simpler and more principled. Gap: all alternative approaches fixed Tizen which the PR missed — this gap should be noted in the report.
📋 Report — Final Recommendation
⚠️ Final Recommendation: REQUEST CHANGES
Phase Status
| Phase | Status | Notes |
|---|---|---|
| Pre-Flight | ✅ COMPLETE | Issue #7580 — SwipeItem visibility toggle causes double command execution in Execute mode |
| Code Review | NEEDS_CHANGES (medium) | 0 errors, 3 warnings, 3 suggestions |
| Gate | ✅ PASSED | android |
| Try-Fix | ✅ COMPLETE | 3 attempts passing, 1 blocked (model unavailable); PR's fix selected |
| Report | ✅ COMPLETE |
Code Review Impact on Try-Fix
Code review's primary warning about Tizen not being updated directly shaped all three try-fix attempts: every model independently fixed Tizen as part of their alternative approach, confirming the omission is real and easy to address. The hardcoded test drag distance warning was consistent with an existing Copilot inline review comment, providing corroborating evidence. No model raised a fundamentally new concern not already in the code review.
Summary
PR #35087 fixes a genuine bug (issue #7580) where SwipeMode.Execute on Android/iOS would execute multiple SwipeItem commands if a command callback changed item visibility mid-iteration. The fix is correct and Gate-verified. However, two changes needed: (1) Tizen has the identical bug and was not fixed; (2) the UI test has a hardcoded endX = startX + 600 drag coordinate that can exceed device screen width.
Root Cause
In ValidateSwipeThreshold() on Android and iOS, a foreach loop iterates over all SwipeItems, calling ExecuteSwipeItem() for each visible one. If executing item A's command toggles item B from hidden to visible (or vice versa), B's new visibility state is evaluated within the same loop iteration, causing B to also execute — double-firing the command.
Fix Quality
The PR's FirstOrDefault(GetIsVisible) fix is elegant and principled: in SwipeMode.Execute, semantically only one item should execute per swipe (matching WinUI's native behavior). Three try-fix models confirmed the fix space with alternative snapshot/guard approaches, but all reviewers (including the prior Copilot inline review) agree on the PR's core logic.
Changes needed before merge:
-
Apply the same fix to Tizen (
src/Core/src/Platform/Tizen/MauiSwipeView.cs) — the identicalforeachloop exists there and will cause the same double-execution bug on Tizen. All three passing try-fix attempts fixed Tizen as well. -
Fix the test drag distance in
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue7580.cs:28— replace the hardcodedendX = startX + 600with a device-relative calculation (e.g.,rect.X + rect.Width - padding) or clamp to screen width to prevent flaky failures on small devices. This was flagged both by this code review and by a prior Copilot inline review.
The fix for issue #1 is 3 lines and straightforward. Issue #2 is a test quality improvement. Both are low-risk and the PR is otherwise well-structured with proper HostApp page and UI test.
…execution in Execute mode (#35087) <!-- 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! ### Root Cause In `SwipeView` Execute mode, when a swipe crosses the threshold, the framework iterates through all `SwipeItems` and executes the command for each visible item. The visibility check is performed dynamically during iteration. If the first item’s command changes the visibility of a sibling item (for example, toggling a boolean that controls which `SwipeItem` is shown), the newly visible item is also picked up and executed within the same loop. This leads to a double invocation, effectively cancelling the user’s intended action. ### Description of Change Updated the Execute mode logic in `ValidateSwipeThreshold()` to execute only the first visible `SwipeItem` using `FirstOrDefault(GetIsVisible)`, instead of iterating through all items. This change aligns Android and iOS with WinUI, where only one `SwipeItem` is allowed in Execute mode at the platform level. It also removes timing-related issues caused by UI updates during iteration and prevents potential multiple executions when additional `SwipeItems` are introduced in the future. ### Issues Fixed Fixes #7580 Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Screenshots - Android | Before Issue Fix | After Issue Fix | |------------------|-----------------| | <video width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/39fdad1f-cc24-4cca-9521-8c99e541c37b" /> | <video width="350" alt="withfix" src="https://github.com/user-attachments/assets/c6112298-4723-49e6-84fe-cea1e5e8fe0a" /> | ### Screenshots - iOS | Before Issue Fix | After Issue Fix | |------------------|-----------------| | <video width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/f935ffa7-3b6e-4e06-8562-dc234d373b16" /> | <video width="350" alt="withfix" src="https://github.com/user-attachments/assets/15711245-4d0d-4c16-9de8-edc7e442f5d9" /> |
…execution in Execute mode (#35087) <!-- 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! ### Root Cause In `SwipeView` Execute mode, when a swipe crosses the threshold, the framework iterates through all `SwipeItems` and executes the command for each visible item. The visibility check is performed dynamically during iteration. If the first item’s command changes the visibility of a sibling item (for example, toggling a boolean that controls which `SwipeItem` is shown), the newly visible item is also picked up and executed within the same loop. This leads to a double invocation, effectively cancelling the user’s intended action. ### Description of Change Updated the Execute mode logic in `ValidateSwipeThreshold()` to execute only the first visible `SwipeItem` using `FirstOrDefault(GetIsVisible)`, instead of iterating through all items. This change aligns Android and iOS with WinUI, where only one `SwipeItem` is allowed in Execute mode at the platform level. It also removes timing-related issues caused by UI updates during iteration and prevents potential multiple executions when additional `SwipeItems` are introduced in the future. ### Issues Fixed Fixes #7580 Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Screenshots - Android | Before Issue Fix | After Issue Fix | |------------------|-----------------| | <video width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/39fdad1f-cc24-4cca-9521-8c99e541c37b" /> | <video width="350" alt="withfix" src="https://github.com/user-attachments/assets/c6112298-4723-49e6-84fe-cea1e5e8fe0a" /> | ### Screenshots - iOS | Before Issue Fix | After Issue Fix | |------------------|-----------------| | <video width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/f935ffa7-3b6e-4e06-8562-dc234d373b16" /> | <video width="350" alt="withfix" src="https://github.com/user-attachments/assets/15711245-4d0d-4c16-9de8-edc7e442f5d9" /> |
…execution in Execute mode (#35087) <!-- 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! ### Root Cause In `SwipeView` Execute mode, when a swipe crosses the threshold, the framework iterates through all `SwipeItems` and executes the command for each visible item. The visibility check is performed dynamically during iteration. If the first item’s command changes the visibility of a sibling item (for example, toggling a boolean that controls which `SwipeItem` is shown), the newly visible item is also picked up and executed within the same loop. This leads to a double invocation, effectively cancelling the user’s intended action. ### Description of Change Updated the Execute mode logic in `ValidateSwipeThreshold()` to execute only the first visible `SwipeItem` using `FirstOrDefault(GetIsVisible)`, instead of iterating through all items. This change aligns Android and iOS with WinUI, where only one `SwipeItem` is allowed in Execute mode at the platform level. It also removes timing-related issues caused by UI updates during iteration and prevents potential multiple executions when additional `SwipeItems` are introduced in the future. ### Issues Fixed Fixes #7580 Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Screenshots - Android | Before Issue Fix | After Issue Fix | |------------------|-----------------| | <video width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/39fdad1f-cc24-4cca-9521-8c99e541c37b" /> | <video width="350" alt="withfix" src="https://github.com/user-attachments/assets/c6112298-4723-49e6-84fe-cea1e5e8fe0a" /> | ### Screenshots - iOS | Before Issue Fix | After Issue Fix | |------------------|-----------------| | <video width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/f935ffa7-3b6e-4e06-8562-dc234d373b16" /> | <video width="350" alt="withfix" src="https://github.com/user-attachments/assets/15711245-4d0d-4c16-9de8-edc7e442f5d9" /> |
…execution in Execute mode (#35087) <!-- 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! ### Root Cause In `SwipeView` Execute mode, when a swipe crosses the threshold, the framework iterates through all `SwipeItems` and executes the command for each visible item. The visibility check is performed dynamically during iteration. If the first item’s command changes the visibility of a sibling item (for example, toggling a boolean that controls which `SwipeItem` is shown), the newly visible item is also picked up and executed within the same loop. This leads to a double invocation, effectively cancelling the user’s intended action. ### Description of Change Updated the Execute mode logic in `ValidateSwipeThreshold()` to execute only the first visible `SwipeItem` using `FirstOrDefault(GetIsVisible)`, instead of iterating through all items. This change aligns Android and iOS with WinUI, where only one `SwipeItem` is allowed in Execute mode at the platform level. It also removes timing-related issues caused by UI updates during iteration and prevents potential multiple executions when additional `SwipeItems` are introduced in the future. ### Issues Fixed Fixes #7580 Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Screenshots - Android | Before Issue Fix | After Issue Fix | |------------------|-----------------| | <video width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/39fdad1f-cc24-4cca-9521-8c99e541c37b" /> | <video width="350" alt="withfix" src="https://github.com/user-attachments/assets/c6112298-4723-49e6-84fe-cea1e5e8fe0a" /> | ### Screenshots - iOS | Before Issue Fix | After Issue Fix | |------------------|-----------------| | <video width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/f935ffa7-3b6e-4e06-8562-dc234d373b16" /> | <video width="350" alt="withfix" src="https://github.com/user-attachments/assets/15711245-4d0d-4c16-9de8-edc7e442f5d9" /> |
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!
Root Cause
In
SwipeViewExecute mode, when a swipe crosses the threshold, the framework iterates through allSwipeItemsand executes the command for each visible item. The visibility check is performed dynamically during iteration. If the first item’s command changes the visibility of a sibling item (for example, toggling a boolean that controls whichSwipeItemis shown), the newly visible item is also picked up and executed within the same loop. This leads to a double invocation, effectively cancelling the user’s intended action.Description of Change
Updated the Execute mode logic in
ValidateSwipeThreshold()to execute only the first visibleSwipeItemusingFirstOrDefault(GetIsVisible), instead of iterating through all items.This change aligns Android and iOS with WinUI, where only one
SwipeItemis allowed in Execute mode at the platform level. It also removes timing-related issues caused by UI updates during iteration and prevents potential multiple executions when additionalSwipeItemsare introduced in the future.Issues Fixed
Fixes #7580
Tested the behaviour in the following platforms
Screenshots - Android
BeforeFix.mov
AfterFix.mov
Screenshots - iOS
BeforeFix.mov
AfterFix.mov