[iOS] Fix ArgumentOutOfRangeException in ObservableGroupedSource when removing a group from grouped CollectionView#34692
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34692Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34692" |
|
@dotnet-policy-service agree company="audius GmbH" |
bf07aeb to
aacbd45
Compare
…hen removing a group on iOS Skip the pre-processing UpdateSection call for Remove actions. Standard INCC semantics require the item to already be removed from the backing collection before CollectionChanged fires. This means _groupSource has N-1 items when MAUI receives CollectionChanged(Remove), but UIKit still has N sections. Calling UpdateSection at this point iterates all N of UIKit's sections and calls GetGroupCount(N-1) -> _groupSource[N-1] -> ArgumentOutOfRangeException. The post-processing UpdateSection call (after DeleteSections) already handles reconciliation. Also add a defensive bounds check in GetGroupCount to guard against other re-entrancy scenarios where UIKit calls back synchronously with a stale section index during DeleteSections. Fixes: dotnet#34691
… on iOS Adds three device tests to CollectionViewTests.iOS.cs verifying that removing sections from a grouped CollectionView does not throw ArgumentOutOfRangeException (issue dotnet#34691): - ItemsSourceGroupedRemoveLastSectionDoesNotCrash: removes the last group, which was the classic crash case where _groupSource[N-1] was accessed with only N-1 items. - ItemsSourceGroupedRemoveFirstSectionDoesNotCrash: removes from the beginning/middle. - ItemsSourceGroupedRemoveAllSectionsOneByOneDoesNotCrash: removes all groups one by one, exercising the full range of section indices.
aacbd45 to
de54a54
Compare
|
If I understand correctly, a change is optional, and conservative code is preferable. So no change is necessary? By the way, the issue and pull request are generated automatically by Copilot (CLI) from a frequently occurring exception in Sentry. I think it's great that it works so well. Use Sentry MCP, check if it's our own code or Maui, and fix it directly in Maui. |
kubaflo
left a comment
There was a problem hiding this comment.
The test couldn't catch a bug before the fix
|
Unfortunately, we couldn't reproduce the issue ourselves, and therefore couldn't create a reproduction project. What we do have are numerous entries in Sentry and direct reports from the customer, or rather, the customer's users. But without reproduction. It's possibly a timing issue. |
kubaflo
left a comment
There was a problem hiding this comment.
Could you please review the AI's summary and try to implement its suggestions?
I can help if you want :)
…oval on iOS - Switch from CollectionViewHandler (deprecated Items/) to CollectionViewHandler2 (default iOS handler, Items2/) which matches the production crash path from issue dotnet#34691. GroupableItemsViewController2 calls ItemsSourceFactory.CreateGrouped() which creates ObservableGroupedSource (Items/), so both handlers share the same source and the fix covers both. - Replace Task.Delay(1000) for initial load with WaitForUIUpdate(initialFrame, ...). - Replace Task.Delay(500/300) after each RemoveAt() with synchronous layout forcing via SetNeedsLayout() + LayoutIfNeeded(). This deterministically triggers the crash path (UpdateSection → NumberOfItemsInSection(N-1) → GetGroupCount(N-1) → ArgumentOutOfRangeException) without the fix, making the tests gate-worthy. - Add Assert.Equal(groupData.Count, handler.PlatformView.NumberOfSections()) to verify UIKit's section count is consistent with the .NET collection after removal. Fixes: dotnet#34691 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Thank you for the detailed AI review — I'd like to address the main concern about the fix targeting the "deprecated" handler. The fix does cover The crash stack trace from issue #34691 confirms this:
// GroupableItemsViewController2.cs
if (ItemsView.IsGrouped)
return Items.ItemsSourceFactory.CreateGrouped(ItemsView.ItemsSource, this);Both I've also updated the tests to address the Gate failure and the handler concern (pushed to the branch):
|
- Remove handler.PlatformView.NumberOfSections() assertions: for CollectionViewHandler2, PlatformView is a UICollectionViewController subclass where NumberOfSections(UICollectionView) is the data source delegate callback requiring a UICollectionView argument, not a no-arg section count query. The crash-free execution of the test body is the meaningful gate assertion; a redundant UIKit section count check is not required. - Fix xUnit2013: replace Assert.Equal(1, groupData.Count) with Assert.Single(groupData). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Can you help on this @kubaflo? |
Address AI review feedback: - Replace trivial groupData.Count assertions with handler.PlatformView.NumberOfSections() to verify UIKit state - Trim 14-line block comment to concise 4-line summary - Remove verbose inline comments from test methods Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Hi @baaaaif, I've pushed a commit addressing the AI review feedback: Changes (a7a523a):
The production fix in /azp run maui-pr-devicetests |
handler.PlatformView for CollectionViewHandler2 is UIView, not UICollectionView. Access the actual UICollectionView through handler.Controller.CollectionView which has the NumberOfSections() method and SetNeedsLayout/LayoutIfNeeded for forcing UIKit layout. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/azp run maui-pr-devicetests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/azp run maui-pr-uitests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/review -b feature/refactor-copilot-yml |
kubaflo
left a comment
There was a problem hiding this comment.
Could you please check the ai's suggestions?
|
/review -b feature/refactor-copilot-yml -p ios |
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 1 findings
See inline comments for details.
|
|
||
| var initialFrame = collectionView.Frame; | ||
|
|
||
| await CreateHandlerAndAddToWindow<CollectionViewHandler2>(collectionView, async handler => |
There was a problem hiding this comment.
[major] Regression Prevention - These new tests request CreateHandlerAndAddToWindow<CollectionViewHandler2>(), but the test setup only calls SetupBuilder() and never registers CollectionViewHandler2 for CollectionView. The handler lookup therefore throws I can't work with Microsoft.Maui.Controls.Handlers.Items2.CollectionViewHandler2 before the remove scenario is exercised, matching the supplied gate failure. Please register Handler2 for these tests, e.g. via EnsureHandlerCreated/ConfigureMauiHandlers(handlers => handlers.AddHandler<CollectionView, CollectionViewHandler2>()), or use the actually registered handler type. The same issue applies to the two other new tests using CollectionViewHandler2.
|
/review -b feature/enhanced-reviewer |
MauiBot
left a comment
There was a problem hiding this comment.
AI Review Summary
@baaaaif — new AI review results are available based on this last commit:
1f00297.
Fix test build: use Controller.CollectionView for NumberOfSections
Review Sessions — click to expand
Gate — Test Before & After Fix
Gate Result: ❌ FAILED
Platform: ANDROID · Base: main · Merge base: b0ea772f
🩺 Test does not reproduce the bug — ran the same in both states (PASS without fix, PASS with fix). The repro test is not exercising the issue. Strengthen the test before reviewing the fix.
| Test | Without Fix (expect FAIL) | With Fix (expect PASS) |
|---|---|---|
📱 CollectionViewTests (ItemsSourceGroupedRemoveLastSectionDoesNotCrash, ItemsSourceGroupedRemoveFirstSectionDoesNotCrash, ItemsSourceGroupedRemoveAllSectionsOneByOneDoesNotCrash) Category=CollectionView |
❌ PASS — 646s | ❌ FAIL — 740s |
🔴 Without fix — 📱 CollectionViewTests (ItemsSourceGroupedRemoveLastSectionDoesNotCrash, ItemsSourceGroupedRemoveFirstSectionDoesNotCrash, ItemsSourceGroupedRemoveAllSectionsOneByOneDoesNotCrash): PASS ❌ · 646s
(truncated to last 15,000 chars)
03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Application'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Behavior'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Border'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'BoxView'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Button'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'CarouselView'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'CheckBox'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Compatibility'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'ContentView'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'DatePicker'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Dispatcher'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Editor'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Element'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Entry'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'FlexLayout'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'FlyoutPage'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Frame'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Gesture'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'HybridWebView'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Image'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Label'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Layout'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Lifecycle'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'ListView'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Map'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'MenuFlyout'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Mapper'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Excluded test (filtered by Trait; 'Category':'Memory'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Modal'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'NavigationPage'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Page'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Path'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Picker'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'RadioButton'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'RefreshView'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'ScrollView'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'SearchBar'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Shape'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Shell'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Slider'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'SwipeView'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'TabbedPage'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'TextInput'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Toolbar'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'TemplatedView'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'View'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'VisualElement'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'VisualElementTree'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'WebView'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Window'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'WindowOverlay'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.438 9182 9249 I DOTNET : [FILTER] Included test (filtered by Trait; 'Category':'Xaml'): [Memory] TweenersWillNotLeakDuringInfiniteAnimation
05-31 08:03:49.533 9182 9249 I DOTNET : [Test environment: 64-bit .NET .NET 10.0 [collection-per-class, non-parallel]]
05-31 08:03:49.533 9182 9249 I DOTNET : [Test framework: xUnit.net 2.9.0.0]
05-31 08:03:49.571 9182 9249 I DOTNET : Test collection for Microsoft.Maui.DeviceTests.AlertDialogTests
05-31 08:03:50.154 9182 9249 I DOTNET : [PASS] AlertDialogButtonColorDarkTheme
05-31 08:03:50.355 9182 9249 I DOTNET : [PASS] AlertDialogButtonColorLightTheme
05-31 08:03:50.374 9182 9249 I DOTNET : Microsoft.Maui.DeviceTests.AlertDialogTests 0.5947299 ms
05-31 08:03:50.377 9182 9249 I DOTNET : Serialize test because it has to add itself to the main window
05-31 08:03:50.475 9182 9249 I DOTNET : [PASS] CollectionView SelectionMode None → Single attaches click listeners
05-31 08:03:51.343 9182 9343 I DOTNET : [PASS] SafeAreaBehaviorConsistency
05-31 08:03:51.833 9182 9355 I DOTNET : [PASS] SafeAreaBehaviorConsistency
05-31 08:03:51.925 9182 9355 I DOTNET : [PASS] ObservableCollection modifications are reflected after UI thread processes them
05-31 08:03:56.071 9182 9384 I DOTNET : [PASS] PushAndPopPageWithCollectionView
05-31 08:03:56.073 9182 9384 I DOTNET : [IGNORED] CellSizeAccountsForMargin
05-31 08:03:57.453 9182 9428 I DOTNET : [PASS] SwipeView in CollectionView does not crash
05-31 08:03:57.775 9182 9433 I DOTNET : [PASS] NullItemsSourceDisplaysHeaderFooterAndEmptyView
05-31 08:03:57.791 9182 9438 I DOTNET : [PASS] CollectionView with SnapPointsType set should not crash
05-31 08:04:11.882 9182 9523 I DOTNET : [PASS] ItemsSourceDoesNotLeak
05-31 08:04:11.887 9182 9523 I DOTNET : [PASS] CollectionView SelectionMode Single → None removes click listeners
05-31 08:04:11.892 9182 9523 I DOTNET : [PASS] CollectionView SelectionMode Single → Multiple keeps click listeners
05-31 08:04:12.595 9182 9528 I DOTNET : [PASS] CollectionViewCanSizeToContent
05-31 08:04:13.281 9182 9533 I DOTNET : [PASS] CollectionViewCanSizeToContent
05-31 08:04:13.987 9182 9538 I DOTNET : [PASS] CollectionViewCanSizeToContent
05-31 08:04:14.735 9182 9543 I DOTNET : [PASS] CollectionViewCanSizeToContent
05-31 08:04:15.422 9182 9548 I DOTNET : [PASS] CollectionViewCanSizeToContent
05-31 08:04:16.131 9182 9553 I DOTNET : [PASS] CollectionViewCanSizeToContent
05-31 08:04:16.843 9182 9558 I DOTNET : [PASS] CollectionViewCanSizeToContent
05-31 08:04:17.492 9182 9563 I DOTNET : [PASS] CollectionViewCanSizeToContent
05-31 08:04:18.231 9182 9568 I DOTNET : [PASS] CollectionViewCanSizeToContent
05-31 08:04:18.975 9182 9573 I DOTNET : [PASS] CollectionViewCanSizeToContent
05-31 08:04:19.681 9182 9578 I DOTNET : [PASS] CollectionViewCanSizeToContent
05-31 08:04:20.400 9182 9583 I DOTNET : [PASS] CollectionViewCanSizeToContent
05-31 08:04:24.143 9182 9588 I DOTNET : [PASS] CollectionViewCanSizeToContent
05-31 08:04:27.845 9182 9604 I DOTNET : [PASS] CollectionViewCanSizeToContent
05-31 08:04:31.531 9182 9610 I DOTNET : [PASS] CollectionViewCanSizeToContent
05-31 08:04:35.161 9182 9625 I DOTNET : [PASS] CollectionViewCanSizeToContent
05-31 08:04:35.461 9182 9630 I DOTNET : [PASS] SettingSelectedItemAfterModifyingCollectionDoesntCrash
05-31 08:04:36.327 9182 9635 I DOTNET : [PASS] CollectionScrollToUngroupedWorks
05-31 08:04:36.565 9182 9640 I DOTNET : [PASS] CollectionViewStructuralItems
05-31 08:04:36.800 9182 9645 I DOTNET : [PASS] CollectionViewStructuralItems
05-31 08:04:37.030 9182 9650 I DOTNET : [PASS] CollectionViewStructuralItems
05-31 08:04:37.226 9182 9655 I DOTNET : [PASS] CollectionViewStructuralItems
05-31 08:04:37.448 9182 9660 I DOTNET : [PASS] CollectionViewStructuralItems
05-31 08:04:37.723 9182 9665 I DOTNET : [PASS] CollectionViewStructuralItems
05-31 08:04:37.921 9182 9670 I DOTNET : [PASS] CollectionViewStructuralItems
05-31 08:04:38.145 9182 9675 I DOTNET : [PASS] CollectionViewStructuralItems
05-31 08:04:38.162 9182 9675 I DOTNET : [PASS] CollectionView with SelectionMode None should not have click listeners
05-31 08:04:39.304 9182 9680 I DOTNET : [PASS] CollectionViewItemsWithFixedWidthAndDifferentHeight
05-31 08:04:39.304 9182 9680 I DOTNET : [PASS] EmptySource should have a count of zero
05-31 08:04:40.200 9182 9686 I DOTNET : [PASS] CollectionScrollToGroupWorks
05-31 08:04:40.738 9182 9691 I DOTNET : [PASS] ClearingItemsSourceClearsBindingContext
05-31 08:04:40.742 9182 9691 I DOTNET : Microsoft.Maui.DeviceTests.CollectionViewTests 49.7650636 ms
05-31 08:04:40.794 9182 9236 I DOTNET : Xml file was written to the provided writer.
05-31 08:04:40.794 9182 9236 I DOTNET : Tests run: 582 Passed: 44 Inconclusive: 0 Failed: 0 Ignored: 537
�[40m�[32minfo�[39m�[22m�[49m: <<XHARNESS_RESULT_START>>
{
"version": 1,
"machineName": "runnervmm79r7",
"exitCode": 0,
"exitCodeName": "SUCCESS",
"platform": "android",
"instrumentationExitCode": 0,
"device": "emulator-5554",
"deviceOsVersion": "API 30",
"architecture": "x86_64",
"files": [
{
"name": "testResults.xml",
"type": "test-results"
},
{
"name": "adb-logcat-com.microsoft.maui.controls.devicetests-default.log",
"type": "logcat"
}
]
}
<<XHARNESS_RESULT_END>>
�[40m�[32minfo�[39m�[22m�[49m: Attempting to remove apk 'com.microsoft.maui.controls.devicetests'..
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 uninstall com.microsoft.maui.controls.devicetests'
�[40m�[32minfo�[39m�[22m�[49m: Successfully uninstalled com.microsoft.maui.controls.devicetests
XHarness exit code: 0
Tests completed successfully
🟢 With fix — 📱 CollectionViewTests (ItemsSourceGroupedRemoveLastSectionDoesNotCrash, ItemsSourceGroupedRemoveFirstSectionDoesNotCrash, ItemsSourceGroupedRemoveAllSectionsOneByOneDoesNotCrash): FAIL ❌ · 740s
(truncated to last 15,000 chars)
.dll.so
[41/133] Xamarin.AndroidX.DrawerLayout.dll -> Xamarin.AndroidX.DrawerLayout.dll.so
[42/133] Xamarin.AndroidX.Fragment.dll -> Xamarin.AndroidX.Fragment.dll.so
[43/133] Xamarin.AndroidX.Lifecycle.Common.Jvm.dll -> Xamarin.AndroidX.Lifecycle.Common.Jvm.dll.so
[44/133] Xamarin.AndroidX.Lifecycle.LiveData.Core.dll -> Xamarin.AndroidX.Lifecycle.LiveData.Core.dll.so
[119/133] System.Text.RegularExpressions.dll -> System.Text.RegularExpressions.dll.so
[45/133] Xamarin.AndroidX.Lifecycle.ViewModel.Android.dll -> Xamarin.AndroidX.Lifecycle.ViewModel.Android.dll.so
[120/133] System.Threading.Tasks.dll -> System.Threading.Tasks.dll.so
[46/133] Xamarin.AndroidX.Lifecycle.ViewModelSavedState.Android.dll -> Xamarin.AndroidX.Lifecycle.ViewModelSavedState.Android.dll.so
[121/133] System.Threading.Thread.dll -> System.Threading.Thread.dll.so
[47/133] Xamarin.AndroidX.Loader.dll -> Xamarin.AndroidX.Loader.dll.so
[122/133] System.Text.Json.dll -> System.Text.Json.dll.so
[123/133] System.Threading.ThreadPool.dll -> System.Threading.ThreadPool.dll.so
[124/133] System.Threading.dll -> System.Threading.dll.so
[48/133] Xamarin.AndroidX.Navigation.Common.Android.dll -> Xamarin.AndroidX.Navigation.Common.Android.dll.so
[125/133] System.Xml.Linq.dll -> System.Xml.Linq.dll.so
[126/133] System.Xml.ReaderWriter.dll -> System.Xml.ReaderWriter.dll.so
[127/133] System.Xml.XDocument.dll -> System.Xml.XDocument.dll.so
[49/133] Xamarin.AndroidX.Navigation.Fragment.dll -> Xamarin.AndroidX.Navigation.Fragment.dll.so
[128/133] System.dll -> System.dll.so
[129/133] netstandard.dll -> netstandard.dll.so
[50/133] Xamarin.AndroidX.Navigation.Runtime.Android.dll -> Xamarin.AndroidX.Navigation.Runtime.Android.dll.so
[130/133] Mono.Android.Runtime.dll -> Mono.Android.Runtime.dll.so
[51/133] Xamarin.AndroidX.Navigation.UI.dll -> Xamarin.AndroidX.Navigation.UI.dll.so
[52/133] Xamarin.AndroidX.RecyclerView.dll -> Xamarin.AndroidX.RecyclerView.dll.so
[131/133] Java.Interop.dll -> Java.Interop.dll.so
[53/133] Xamarin.AndroidX.SavedState.SavedState.Android.dll -> Xamarin.AndroidX.SavedState.SavedState.Android.dll.so
[54/133] Xamarin.AndroidX.SwipeRefreshLayout.dll -> Xamarin.AndroidX.SwipeRefreshLayout.dll.so
[55/133] Xamarin.AndroidX.ViewPager.dll -> Xamarin.AndroidX.ViewPager.dll.so
[56/133] Xamarin.AndroidX.ViewPager2.dll -> Xamarin.AndroidX.ViewPager2.dll.so
[57/133] Xamarin.Google.Android.Material.dll -> Xamarin.Google.Android.Material.dll.so
[58/133] Xamarin.GooglePlayServices.Base.dll -> Xamarin.GooglePlayServices.Base.dll.so
[59/133] Xamarin.GooglePlayServices.Basement.dll -> Xamarin.GooglePlayServices.Basement.dll.so
[132/133] Mono.Android.dll -> Mono.Android.dll.so
[60/133] Xamarin.GooglePlayServices.Maps.dll -> Xamarin.GooglePlayServices.Maps.dll.so
[61/133] Xamarin.GooglePlayServices.Tasks.dll -> Xamarin.GooglePlayServices.Tasks.dll.so
[62/133] Xamarin.Kotlin.StdLib.dll -> Xamarin.Kotlin.StdLib.dll.so
[63/133] Xamarin.KotlinX.Coroutines.Core.Jvm.dll -> Xamarin.KotlinX.Coroutines.Core.Jvm.dll.so
[64/133] Xamarin.KotlinX.Serialization.Core.Jvm.dll -> Xamarin.KotlinX.Serialization.Core.Jvm.dll.so
[65/133] xunit.abstractions.dll -> xunit.abstractions.dll.so
[66/133] xunit.assert.dll -> xunit.assert.dll.so
[67/133] xunit.core.dll -> xunit.core.dll.so
[68/133] xunit.execution.dotnet.dll -> xunit.execution.dotnet.dll.so
[69/133] xunit.runner.utility.netcoreapp10.dll -> xunit.runner.utility.netcoreapp10.dll.so
[70/133] System.Collections.Concurrent.dll -> System.Collections.Concurrent.dll.so
[71/133] System.Collections.Immutable.dll -> System.Collections.Immutable.dll.so
[72/133] System.Collections.NonGeneric.dll -> System.Collections.NonGeneric.dll.so
[73/133] System.Collections.Specialized.dll -> System.Collections.Specialized.dll.so
[74/133] System.Collections.dll -> System.Collections.dll.so
[75/133] System.ComponentModel.Primitives.dll -> System.ComponentModel.Primitives.dll.so
[133/133] System.Private.CoreLib.dll -> System.Private.CoreLib.dll.so
[76/133] System.ComponentModel.TypeConverter.dll -> System.ComponentModel.TypeConverter.dll.so
[77/133] System.ComponentModel.dll -> System.ComponentModel.dll.so
[78/133] System.Console.dll -> System.Console.dll.so
[79/133] System.Diagnostics.Debug.dll -> System.Diagnostics.Debug.dll.so
[80/133] System.Diagnostics.DiagnosticSource.dll -> System.Diagnostics.DiagnosticSource.dll.so
[81/133] System.Diagnostics.Process.dll -> System.Diagnostics.Process.dll.so
[82/133] System.Diagnostics.Tools.dll -> System.Diagnostics.Tools.dll.so
[83/133] System.Diagnostics.TraceSource.dll -> System.Diagnostics.TraceSource.dll.so
[84/133] System.Diagnostics.Tracing.dll -> System.Diagnostics.Tracing.dll.so
[85/133] System.Drawing.Primitives.dll -> System.Drawing.Primitives.dll.so
[86/133] System.Drawing.dll -> System.Drawing.dll.so
[87/133] System.Formats.Asn1.dll -> System.Formats.Asn1.dll.so
[88/133] System.Globalization.dll -> System.Globalization.dll.so
[89/133] System.IO.Compression.Brotli.dll -> System.IO.Compression.Brotli.dll.so
[90/133] System.IO.Compression.dll -> System.IO.Compression.dll.so
[91/133] System.IO.FileSystem.dll -> System.IO.FileSystem.dll.so
[92/133] System.IO.Pipelines.dll -> System.IO.Pipelines.dll.so
[93/133] System.IO.dll -> System.IO.dll.so
[94/133] System.Linq.Expressions.dll -> System.Linq.Expressions.dll.so
[95/133] System.Linq.dll -> System.Linq.dll.so
[96/133] System.Memory.dll -> System.Memory.dll.so
[97/133] System.Net.Http.dll -> System.Net.Http.dll.so
[98/133] System.Net.NameResolution.dll -> System.Net.NameResolution.dll.so
[99/133] System.Net.Primitives.dll -> System.Net.Primitives.dll.so
[100/133] System.Net.Requests.dll -> System.Net.Requests.dll.so
[101/133] System.Net.Sockets.dll -> System.Net.Sockets.dll.so
[102/133] System.Numerics.Vectors.dll -> System.Numerics.Vectors.dll.so
[103/133] System.ObjectModel.dll -> System.ObjectModel.dll.so
[104/133] System.Private.Uri.dll -> System.Private.Uri.dll.so
[105/133] System.Private.Xml.Linq.dll -> System.Private.Xml.Linq.dll.so
[106/133] System.Private.Xml.dll -> System.Private.Xml.dll.so
[107/133] System.Reflection.Extensions.dll -> System.Reflection.Extensions.dll.so
[108/133] System.Reflection.TypeExtensions.dll -> System.Reflection.TypeExtensions.dll.so
[109/133] System.Reflection.dll -> System.Reflection.dll.so
[110/133] System.Runtime.Extensions.dll -> System.Runtime.Extensions.dll.so
[111/133] System.Runtime.InteropServices.RuntimeInformation.dll -> System.Runtime.InteropServices.RuntimeInformation.dll.so
[112/133] System.Runtime.InteropServices.dll -> System.Runtime.InteropServices.dll.so
[113/133] System.Runtime.Loader.dll -> System.Runtime.Loader.dll.so
[114/133] System.Runtime.Numerics.dll -> System.Runtime.Numerics.dll.so
[115/133] System.Runtime.dll -> System.Runtime.dll.so
[116/133] System.Security.Cryptography.dll -> System.Security.Cryptography.dll.so
[117/133] System.Text.Encoding.dll -> System.Text.Encoding.dll.so
[118/133] System.Text.Encodings.Web.dll -> System.Text.Encodings.Web.dll.so
[119/133] System.Text.Json.dll -> System.Text.Json.dll.so
[120/133] System.Text.RegularExpressions.dll -> System.Text.RegularExpressions.dll.so
[121/133] System.Threading.Tasks.dll -> System.Threading.Tasks.dll.so
[122/133] System.Threading.Thread.dll -> System.Threading.Thread.dll.so
[123/133] System.Threading.ThreadPool.dll -> System.Threading.ThreadPool.dll.so
[124/133] System.Threading.dll -> System.Threading.dll.so
[125/133] System.Xml.Linq.dll -> System.Xml.Linq.dll.so
[126/133] System.Xml.ReaderWriter.dll -> System.Xml.ReaderWriter.dll.so
[127/133] System.Xml.XDocument.dll -> System.Xml.XDocument.dll.so
[128/133] System.dll -> System.dll.so
[129/133] netstandard.dll -> netstandard.dll.so
[130/133] Java.Interop.dll -> Java.Interop.dll.so
[131/133] Mono.Android.Runtime.dll -> Mono.Android.Runtime.dll.so
[132/133] Mono.Android.dll -> Mono.Android.dll.so
[133/133] System.Private.CoreLib.dll -> System.Private.CoreLib.dll.so
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:05:17.64
[11.0.0-prerelease.26230.4+92962e5c46ac08a66ded4c5696209cc60f1a232f] XHarness command issued: android test --app /home/vsts/work/1/s/artifacts/bin/Controls.DeviceTests/Release/net10.0-android/com.microsoft.maui.controls.devicetests-Signed.apk --package-name com.microsoft.maui.controls.devicetests --device-id emulator-5554 -o artifacts/log --timeout 01:00:00 -v --arg TestFilter=Category=CollectionView
�[40m�[37mdbug�[39m�[22m�[49m: ADBRunner using ADB.exe supplied from /home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/tools/net10.0/any/../../../runtimes/any/native/adb/linux/adb
�[40m�[37mdbug�[39m�[22m�[49m: Full resolved path:'/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb'
�[40m�[32minfo�[39m�[22m�[49m: Will attempt to find device supporting architectures: 'arm64-v8a', 'x86_64'
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb start-server'
�[40m�[37mdbug�[39m�[22m�[49m:
�[40m�[32minfo�[39m�[22m�[49m: Finding attached devices/emulators...
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb devices -l'
�[40m�[37mdbug�[39m�[22m�[49m: Found 1 possible devices
�[40m�[37mdbug�[39m�[22m�[49m: Evaluating output line for device serial: emulator-5554 device product:sdk_gphone_x86_64 model:sdk_gphone_x86_64 device:generic_x86_64_arm64 transport_id:2
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 shell getprop ro.product.cpu.abilist'
�[40m�[37mdbug�[39m�[22m�[49m: Found 1 possible devices. Using 'emulator-5554'
�[40m�[32minfo�[39m�[22m�[49m: Active Android device set to serial 'emulator-5554'
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 -s emulator-5554 shell getprop ro.product.cpu.abi'
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 -s emulator-5554 shell getprop ro.build.version.sdk'
�[40m�[32minfo�[39m�[22m�[49m: Waiting for device to be available (max 5 minutes)
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 wait-for-device'
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 -s emulator-5554 shell getprop sys.boot_completed'
�[40m�[37mdbug�[39m�[22m�[49m: sys.boot_completed = '1'
�[40m�[37mdbug�[39m�[22m�[49m: Waited 0 seconds for device boot completion
�[40m�[37mdbug�[39m�[22m�[49m: Working with emulator-5554 (API 30)
�[40m�[37mdbug�[39m�[22m�[49m: Check current adb install and/or package verification settings
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 shell settings get global verifier_verify_adb_installs'
�[40m�[37mdbug�[39m�[22m�[49m: verifier_verify_adb_installs = 0
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 shell settings get global package_verifier_enable'
�[40m�[37mdbug�[39m�[22m�[49m: package_verifier_enable =
�[40m�[1m�[33mwarn�[39m�[22m�[49m: Installing debug apks on a device might be rejected with INSTALL_FAILED_VERIFICATION_FAILURE. Make sure to set 'package_verifier_enable' to '0'
�[40m�[32minfo�[39m�[22m�[49m: Attempting to remove apk 'com.microsoft.maui.controls.devicetests'..
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 uninstall com.microsoft.maui.controls.devicetests'
�[41m�[30mfail�[39m�[22m�[49m: Waiting for command timed out: execution may be compromised
�[41m�[30mfail�[39m�[22m�[49m: Error: Exit code: -2
Std out:
�[40m�[32minfo�[39m�[22m�[49m: Attempting to install /home/vsts/work/1/s/artifacts/bin/Controls.DeviceTests/Release/net10.0-android/com.microsoft.maui.controls.devicetests-Signed.apk
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 install /home/vsts/work/1/s/artifacts/bin/Controls.DeviceTests/Release/net10.0-android/com.microsoft.maui.controls.devicetests-Signed.apk'
�[41m�[30mfail�[39m�[22m�[49m: Error:
Exit code: 1
Std out:
Serving...
Performing Incremental Install
cmd: Failure calling service package: Broken pipe (32)
Performing Streamed Install
Std err:
All files should be loaded. Notifying the device.
adb: failed to install /home/vsts/work/1/s/artifacts/bin/Controls.DeviceTests/Release/net10.0-android/com.microsoft.maui.controls.devicetests-Signed.apk: cmd: Can't find service: package
�[41m�[1m�[37mcrit�[39m�[22m�[49m: Install failure: Test command cannot continue
�[40m�[32minfo�[39m�[22m�[49m: Attempting to remove apk 'com.microsoft.maui.controls.devicetests'..
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 uninstall com.microsoft.maui.controls.devicetests'
�[41m�[30mfail�[39m�[22m�[49m: Error: Exit code: 20
Std out:
Std err:
cmd: Can't find service: package
�[40m�[32minfo�[39m�[22m�[49m: Attempting to remove apk 'com.microsoft.maui.controls.devicetests'..
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 uninstall com.microsoft.maui.controls.devicetests'
�[41m�[30mfail�[39m�[22m�[49m: Error: Exit code: 20
Std out:
Std err:
cmd: Can't find service: package
XHarness exit code: 78 (PACKAGE_INSTALLATION_FAILURE)
Tests completed with exit code: 78
⚠️ Failure Details
- ❌ CollectionViewTests (ItemsSourceGroupedRemoveLastSectionDoesNotCrash, ItemsSourceGroupedRemoveFirstSectionDoesNotCrash, ItemsSourceGroupedRemoveAllSectionsOneByOneDoesNotCrash) PASSED without fix (should fail) — tests don't catch the bug
- ❌ CollectionViewTests (ItemsSourceGroupedRemoveLastSectionDoesNotCrash, ItemsSourceGroupedRemoveFirstSectionDoesNotCrash, ItemsSourceGroupedRemoveAllSectionsOneByOneDoesNotCrash) FAILED with fix (should pass)
📁 Fix files reverted (2 files)
eng/pipelines/ci-copilot.ymlsrc/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs
UI Tests — CollectionView
Detected UI test categories: CollectionView
Pre-Flight — Context & Validation
Issue: #34691 - Grouped CollectionView section removal can crash on iOS
PR: #34692 - Fix grouped CollectionView section removal crash
Platforms Affected: iOS/MacCatalyst implementation; requested test platform was Android
Files Changed: 1 implementation, 1 test
Key Findings
- Local PR squash commit changes
src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.csandsrc/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs. - The PR's fix skips the pre-switch
UpdateSection()call forNotifyCollectionChangedAction.Removeand adds a defensiveGetGroupCountbounds guard. - Gate result was already failed: Android tests passed without the fix and failed with the fix due package installation/emulator failure, so the gate did not demonstrate the iOS regression.
- GitHub PR/issue metadata and comments could not be fetched because
ghis not authenticated; context is based on the local PR squash commit and existing gate artifacts.
Code Review Summary
Verdict: LGTM
Confidence: medium
Errors: 0 | Warnings: 0 | Suggestions: 0
Key code review findings:
- No concrete changed-line findings. Expert reviewer output:
[].
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #34692 | Skip pre-removal section probing and guard stale GetGroupCount indexes |
❌ FAILED (Gate) | ObservableGroupedSource.cs, CollectionViewTests.iOS.cs |
Android gate did not reproduce the iOS issue; with-fix run hit Android package install failure. |
Code Review — Deep Analysis
Code Review — PR #34692
Independent Assessment
What this changes: The PR changes the iOS/MacCatalyst grouped CollectionView source (ObservableGroupedSource) so grouped section removal no longer forces UIKit to query item counts for stale section indexes before DeleteSections reconciles the native section count. It also adds iOS device regression tests for removing last, first, and all grouped sections with the Items2 handler.
Inferred motivation: UICollectionView may synchronously ask the data source for section item counts while the underlying grouped source has already removed a group but UIKit still has the old section count. In the original code, the pre-switch UpdateSection() call can ask for GetGroupCount(N-1) after _groupSource has N-1 groups, causing ArgumentOutOfRangeException.
Reconciliation with PR Narrative
Author claims: GitHub PR metadata was unavailable because gh is not authenticated in this environment. Local gate artifacts identify the linked regression as grouped CollectionView section removal on iOS (https://github.com/dotnet/maui/issues/34691) and show the new regression tests named ItemsSourceGroupedRemoveLastSectionDoesNotCrash, ItemsSourceGroupedRemoveFirstSectionDoesNotCrash, and ItemsSourceGroupedRemoveAllSectionsOneByOneDoesNotCrash.
Agreement/disagreement: The local diff matches the inferred root cause: it avoids the pre-removal UpdateSection() path for Remove and adds a defensive GetGroupCount bounds guard. The added tests are iOS-only, while the provided gate ran Android; that platform mismatch means the gate did not prove the iOS regression.
Findings
No concrete code issues found in the changed lines. Expert reviewer JSON findings were written to CustomAgentLogsTmp/PRState/34692/PRAgent/inline-findings.json and are [].
Devil's Advocate
The PR fix has two overlapping mitigations: skip pre-removal section probing and return 0 for stale group indexes. The bounds guard is defensive and safe for UIKit's count query, but it could hide an unrelated stale index if used from paths that should fail fast. A narrower alternative is to make UpdateSection() itself only query sections that still exist in _groupSource.
Verdict: LGTM
Confidence: medium
Summary: The code change is localized to the iOS grouped CollectionView data-source path and targets the likely crash mechanism. Confidence is limited by the unavailable PR narrative/CI metadata and by the Android gate not exercising the iOS-only regression.
Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix | Clamp UpdateSection section probing to the current group-source count | FAIL | 1 file | Android test timed out; expert review found direct UIKit data-source callbacks remain unguarded. |
| 2 | try-fix | Remove eager pre-switch UpdateSection for all actions | FAIL | 1 file | Android test timed out; expert review found animated DeleteSections still reaches GetGroupCount with stale indexes. |
| 3 | try-fix | Reload removed grouped sections instead of DeleteSections | TEST PASS / REVIEW FAIL | 1 file | Android CollectionView passed, but expert review found deferred ReloadData can still race post-action UpdateSection and loses animations. |
| 4 | try-fix | Keep original timing and rely only on GetGroupCount stale-index tolerance | TEST PASS / REVIEW FAIL | 1 file | Android CollectionView passed, but expert review found unconditional pre-removal UpdateSection can create invalid UIKit accounting for non-tail removals. |
| PR | PR #34692 | Skip pre-removal section probing and guard stale GetGroupCount indexes | FAILED (Gate) | 2 files | Gate failure was not code-specific: Android did not reproduce the iOS bug and with-fix run hit package installation failure. |
Attempt Narrative
- try-fix-1: See
../try-fix-1/content.md; artifacts inattempt-1/. - try-fix-2: See
../try-fix-2/content.md; artifacts inattempt-2/. - try-fix-3: See
../try-fix-3/content.md; artifacts inattempt-3/. - try-fix-4: See
../try-fix-4/content.md; artifacts inattempt-4/.
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| code-review + maui-expert-reviewer | 1 | Yes | Generated and reviewed clamp-only, lifecycle-timing-only, reload-on-remove, and guard-only alternatives. |
| code-review + maui-expert-reviewer | 2 | No | After reviewing failures, the remaining meaningful strategies collapse back to the PR's combined approach: avoid pre-removal probing and retain stale-index tolerance for UIKit callbacks. |
Exhausted: Yes
Selected Fix: PR #34692 — The tested alternatives were either incomplete under expert review or worse tradeoffs. The PR's combined fix is the best candidate among those explored, but its test coverage/gate remains inadequate because the Android gate cannot prove the iOS-only regression.
Report — Final Recommendation
Comparative Fix Report — PR #34692
Winner: pr
pr is the winning candidate. pr-plus-reviewer is equivalent because expert review produced no actionable changes, but the raw submitted PR is preferred as the canonical fix. The PR combines the two safety properties the alternatives failed to provide together: it skips pre-removal section probing that can make UIKit's accounting inconsistent, and it guards stale GetGroupCount indexes during UIKit re-entrancy.
Candidate ranking
| Rank | Candidate | Test / review result | Assessment |
|---|---|---|---|
| 1 | pr |
Gate failed, but non-dispositive Android/package-install failure; expert review LGTM | Best technical fix. Android did not exercise the iOS-only code path, and the failure is not evidence of a regression. |
| 2 | pr-plus-reviewer |
Same as pr; no inline findings |
Equivalent to pr, but no additional reviewer-applied changes exist, so it is not a distinct improvement over the submitted PR. |
| 3 | try-fix-3 |
Android CollectionView passed; expert review failed | Better than candidates with failed regression runs, but unsafe because ReloadData() can be deferred and post-action UpdateSection may still see stale native section counts unless the stale-index guard or synchronous layout flush is retained. |
| 4 | try-fix-4 |
Android CollectionView passed; expert review failed | Better than candidates with failed regression runs, but unsafe for non-tail removals because unconditional pre-removal UpdateSection can feed UIKit shifted post-removal item counts before DeleteSections. |
| 5 | try-fix-1 |
Failed / timed out on Android | Must rank below passing candidates. It clamps section probing, but leaves direct UIKit data-source callback paths exposed without the GetGroupCount guard. |
| 6 | try-fix-2 |
Failed / timed out on Android | Must rank below passing candidates. Removing eager reconciliation alone still allows animated DeleteSections to reach GetGroupCount with stale indexes. |
Regression-test rule application
Candidates with failed regression runs (try-fix-1, try-fix-2) are ranked below candidates that passed their available regression runs (try-fix-3, try-fix-4). The PR gate failure is treated separately because the recorded context says Android did not exercise the iOS-only changed file and the with-fix failure was package installation/emulator infrastructure, not a code-specific regression. Therefore, the gate result does not outweigh the expert-review failures found in the alternative candidates.
Rationale
The try-fix attempts isolated partial fixes. try-fix-1 addressed stale section probing but not callback re-entrancy; try-fix-2 addressed timing but not stale callback indexes; try-fix-3 avoided delete animations but introduced a deferred reload race; and try-fix-4 kept stale-index tolerance but retained unsafe pre-removal section probing. The PR is the only candidate that covers both sides of the issue without introducing the reviewer-identified UIKit accounting risks.
Future Action — review latest findings
No alternative fix was selected for this run. Review the session findings and CI results before merging.
Summary
Fixes #34691 —
ArgumentOutOfRangeExceptioninObservableGroupedSource.GetGroupCountwhen removing a group from a groupedCollectionViewon iOS.Root Cause
CollectionChanged(NotifyCollectionChangedEventArgs)calledUpdateSection()before processing theRemoveaction:Standard
INotifyCollectionChangedsemantics require the item to be already removed from the backing collection beforeCollectionChangedfires. So when MAUI receivesCollectionChanged(Remove),_groupSourcehas N-1 items — but UIKit still has N sections.UpdateSectioniterates UIKit's N sections and callsNumberOfItemsInSection(N-1), which re-enters the data source:The
Compatibilitylayer (Microsoft.Maui.Controls.Compatibility.Platform.iOS.ObservableGroupedSource) does not callUpdateSectionbeforeRemoveand is unaffected.Fix
Skip
UpdateSectionbeforeRemove— the post-processing call (afterDeleteSectionscompletes) already handles UIKit reconciliation.Defensive bounds check in
GetGroupCount— guards against other re-entrancy scenarios where UIKit calls back synchronously with a stale section index duringDeleteSections.Testing
CollectionViewwith dynamic section removal on iOSUpdateSectionis still called forAdd,Replace,Move, andReset— no regression expected for those actionsUpdateSectionstill runs forRemoveafterDeleteSectionscompletes