[Windows] Fixed CollectionView throws NRE when value of IsGrouped property is changed to false#27331
Conversation
There was a problem hiding this comment.
Copilot reviewed 3 out of 4 changed files in this pull request and generated 1 comment.
Files not reviewed (1)
- src/Controls/tests/TestCases.HostApp/Issues/Issue17864.xaml: Language not supported
Comments suppressed due to low confidence (1)
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17864.cs:22
- Add a check to ensure that the CollectionView is correctly updated after the IsGrouped property changes.
App.WaitForElement(CollectionView);
| public void CollectionViewShouldNotCrashWhenIsGroupedChanges() | ||
| { | ||
| App.WaitForElement(CollectionView); | ||
| App.Tap("Button"); |
There was a problem hiding this comment.
Ensure that the AutomationId for 'Button' is unique and not reused, or WaitForElement will fail to find the correct element.
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
|
/rebase |
fa90e42 to
64f07b9
Compare
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
|
/rebase |
64f07b9 to
8ee52ca
Compare
|
/rebase |
8ee52ca to
5560feb
Compare
|
/rebase |
5560feb to
7dc3dea
Compare
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
|
/rebase |
7dc3dea to
c42a16d
Compare
## Description Remove the "Are you waiting for the changes in this PR to be merged?" note from all Copilot instruction files. This note was previously required at the top of every PR description so users could find instructions on how to test PR artifacts. We now have a **dogfooding comment bot** that automatically posts testing instructions under each PR, making this manual note redundant. Copilot agents were still prepending this note to every PR description because it was hardcoded in: - `.github/copilot-instructions.md` (main instructions) - `.github/skills/pr-finalize/SKILL.md` (PR finalization skill) - `.github/skills/pr-finalize/references/complete-example.md` (example PR) ### Issues Fixed N/A — instruction cleanup only. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## Problem The `maui-pr-uitests` pipeline has 98+ test jobs that all publish artifacts using `PublishBuildArtifacts@1` with **no artifact name**, defaulting to `drop`. When multiple jobs upload the same file to the same container simultaneously, AzDO blob storage encounters write conflicts: ``` Blob is incomplete (missing block). Blob: 29adda685a1ff1119a49000d3a9183a5, Expected Offset: 0, Actual Offset: 8388608 ##[error]File upload failed even after retry. ``` ### Recent failures - [Build 1334980](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1334980): 3 jobs failed (Controls CollectionView, Controls (vlatest), Controls (vlatest) CollectionView) - [Build 1334245](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1334245): 1 job failed (Controls (vlatest)) The specific collision: both "Controls (vlatest)" and "Controls (vlatest) CollectionView" upload `drop/logs/appium_ios_Controls.TestCases.iOS.Tests-Release-ios-CollectionView.log` — same blob ID, concurrent writes. ## Fix Add a unique artifact name per job using `$(System.StageName)-$(System.JobName)-$(System.JobAttempt)` in `eng/pipelines/common/ui-tests-steps.yml`. This matches the pattern already used by snapshot diff artifacts in `ui-tests-collect-snapshot-diffs.yml`. Fixes dotnet#34477 Ref: dotnet/dnceng#1916 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🤖 AI Summary📊 Expand Full Review —
|
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #27331 | Guard grouped item source creation so TemplatedItemSourceFactory.Create(...) is only called when the group object implements IEnumerable. |
PENDING (Gate) | src/Controls/src/Core/Platform/Windows/CollectionView/GroupedItemTemplateCollection.cs, src/Controls/tests/TestCases.HostApp/Issues/Issue17864.xaml, src/Controls/tests/TestCases.HostApp/Issues/Issue17864.xaml.cs, src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17864.cs |
PR claims #17864 and #28824; explicit added UI test appears to cover only #17864. |
Issue: #17864 - [Windows] CollectionView throws NRE when value of IsGrouped property is changed to false
Related Issue: #28824 - [Windows] NullReferenceException thrown When Toggling IsGrouped to True in ObservableCollection Binding
PR: #27331 - [Windows] Fixed CollectionView throws NRE when value of IsGrouped property is changed to false
Platforms Affected: Windows
Files Changed: 1 implementation, 3 test
Test Type: UI test (TestCases.HostApp + TestCases.Shared.Tests)
Key Findings
- The live review branch currently changes
src/Controls/src/Core/Platform/Windows/CollectionView/TemplatedItemSourceFactory.cs, notGroupedItemTemplateCollection.cs; it returnsArray.Empty<object>()whenCreate(...)receives a nullitemsSource. - Added coverage includes a HostApp issue page plus a UI test for
Issue17864; the test exercises bothtrue -> falseandfalse -> true while items remain flattransitions. - Issue [Windows]
CollectionViewthrows NRE when value ofIsGroupedproperty is changed tofalse#17864 is explicitly Windows-only and was reported as not reproducing on Android; issue [Windows] NullReferenceException thrown When Toggling IsGrouped to True in ObservableCollection Binding #28824 is a related Windows repro for the opposite toggle direction. - Prior PR discussion includes an older AI review summary and a low-signal Copilot comment about AutomationId uniqueness, but the author later stated those concerns were addressed and the current branch diff reflects a different implementation.
- The PR description still claims validation on Android, Windows, iOS, and Mac even though the issue context and added automated coverage are Windows-focused.
Edge Cases Mentioned in Context
#17864: toggling from grouped data to flat data whileIsGroupedbecomesfalse.#28824: toggling from flat data to grouped mode while the bound items remain a flatObservableCollection/List.- The new UI repro page intentionally exercises both toggle directions on Windows.
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #27331 | Normalize null grouped item sources at the Windows templated-item factory boundary by returning Array.Empty<object>() from TemplatedItemSourceFactory.Create(...). |
PENDING (Gate) | src/Controls/src/Core/Platform/Windows/CollectionView/TemplatedItemSourceFactory.cs, src/Controls/tests/TestCases.HostApp/Issues/Issue17864.xaml, src/Controls/tests/TestCases.HostApp/Issues/Issue17864.xaml.cs, src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17864.cs |
Current branch content differs from an earlier AI summary comment on the PR; gate should validate the live implementation. |
🚦 Gate — Test Verification
Gate Result: PASSED
Platform: windows
Mode: Full Verification
- Tests FAIL without fix:
- Tests PASS with fix:
Evidence Summary: Issue17864 failed without the fix (app crash / expected failure) and passed with the fix restored on Windows. Full verification report was generated under CustomAgentLogsTmp/PRState/27331/PRAgent/gate/verify-tests-fail/verification-report.md.
Gate Result: PASSED
Platform: windows
Mode: Full Verification
- Tests FAIL without fix:
- Tests PASS with fix:
Evidence Summary: Issue17864 failed without the fix (app crash / expected failure) and passed with the fix restored on Windows. Full verification report was generated under CustomAgentLogsTmp/PRState/27331/PRAgent/gate/verify-tests-fail/verification-report.md.
🔧 Fix — Analysis & Comparison
Gate Result: PASSED
Platform: windows
Mode: Full Verification
- Tests FAIL without fix:
- Tests PASS with fix:
Evidence Summary: Issue17864 failed without the fix (app crash / expected failure) and passed with the fix restored on Windows. Full verification report was generated under CustomAgentLogsTmp/PRState/27331/PRAgent/gate/verify-tests-fail/verification-report.md.
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix (claude-opus-4.6) |
Guard grouped source creation upstream in GroupedItemTemplateCollection and harden GroupTemplateContext against non-IEnumerable items. |
PASS | 2 files | Avoids propagating null grouped items and closes the footer path too. |
| 2 | try-fix (claude-sonnet-4.6) |
Guard ItemTemplateContextEnumerable.GetEnumerator() so a null _itemsSource yields an empty enumeration. |
PASS | 1 file | Smallest leaf-level fix; handles the exact crash site directly. |
| 3 | try-fix (gpt-5.3-codex) |
Normalize null input in the ItemTemplateContextEnumerable constructor so the instance always wraps an empty enumerable instead of null. |
PASS | 1 file | Similar to candidate #2, but normalizes earlier in object lifetime. |
| 4 | try-fix (gemini-3-pro-preview) |
Coalesce null source in TemplatedItemSourceFactory.Create when constructing ItemTemplateContextEnumerable, preserving the wrapper type instead of returning a raw array. |
PASS | 1 file | Closest alternative to the PR fix; keeps the wrapper alive. |
| 5 | try-fix (claude-opus-4.6, round 2 idea) |
Normalize invalid groups before they enter the factory and harden GroupTemplateContext as a safety net. |
PASS | 2 files | Functionally close to candidate #1; not meaningfully better than the PR fix. |
| 6 | try-fix (claude-sonnet-4.6, round 2 idea) |
Treat invalid top-level groups as singleton groups instead of empty ones. | PASS | 1 file | Prevents the crash but changes semantics by showing stray items as one-item groups. |
| 7 | try-fix (gpt-5.3-codex, round 2 idea) |
Clear the native Windows handler items source during IsGrouped transitions before rebuilding. | FAIL | 1 file | Did not stop the crash; timing-layer reset was too late to prevent bad grouped data reaching the pipeline. |
| 8 | try-fix (gemini-3-pro-preview, round 2 idea) |
Defer grouped source rebuild in the Windows handler until state changes settle. | FAIL | 1 file | Repeated retries still crashed; scheduling later did not fix the bad grouped shape. |
| 9 | try-fix (claude-opus-4.6, round 3 idea) |
Add a group-shape gate in GroupedItemTemplateCollection and skip non-enumerable top-level items during grouped rebuilds. |
PASS | 1 file | Semantically precise, but more invasive because grouped collection update paths need null filtering. |
| 10 | try-fix (claude-sonnet-4.6, round 3 idea) |
Revisited null-safety lower in the pipeline and converged on the same enumerator guard as candidate #2. | PASS | 1 file | Duplicate of candidate #2 rather than a distinct new fix. |
| 11 | try-fix (gemini-3-pro-preview, round 3 idea) |
Coalesce non-IEnumerable groups to Array.Empty<object>() at the GroupedItemTemplateCollection call site. |
PASS | 1 file | Valid caller-side guard, but conceptually similar to the PR's boundary normalization. |
| PR | PR #27331 | Normalize null grouped item sources at the Windows templated-item factory boundary by returning Array.Empty<object>() from TemplatedItemSourceFactory.Create(...). |
PASSED (Gate) | 4 files | Original PR; gate passed on Windows and covers both toggle directions via Issue17864. |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
claude-opus-4.6 |
2 | Yes | Normalize invalid group objects at the group-context layer before the factory sees them. |
claude-sonnet-4.6 |
2 | Yes | Treat invalid top-level groups as singleton groups rather than null/empty. |
gpt-5.3-codex |
2 | Yes | Clear native ItemsSource during grouped/un-grouped transitions at the Windows handler layer. |
gemini-3-pro-preview |
2 | Yes | Defer grouped source rebuild in the Windows handler until property changes settle. |
claude-opus-4.6 |
3 | Yes | Skip grouped rebuild for invalid top-level groups instead of normalizing them. |
claude-sonnet-4.6 |
3 | Yes | Add null-safe accessors lower in the item-template pipeline (which converged to candidate #2). |
gpt-5.3-codex |
3 | No | NO NEW IDEAS |
gemini-3-pro-preview |
3 | Yes | Guard the async-dispatch/disposal gap in grouped change handling (did not produce a distinct passing fix). |
Exhausted: Yes max 3 rounds reached, and remaining ideas either duplicated already-tested null-normalization strategies or failed at the handler/timing layer.
Selected Fix: PR #27331 it is the best tradeoff because it applies the normalization once at the shared factory boundary, prevents null from reaching downstream grouped consumers, preserves empty-group semantics, and is less invasive than group-shape filtering while more robust than handler timing changes.
📋 Report — Final Recommendation
Gate Result: PASSED
Platform: windows
Mode: Full Verification
- Tests FAIL without fix:
- Tests PASS with fix:
Evidence Summary: Issue17864 failed without the fix (app crash / expected failure) and passed with the fix restored on Windows. Full verification report was generated under CustomAgentLogsTmp/PRState/27331/PRAgent/gate/verify-tests-fail/verification-report.md.
Final Recommendation: APPROVE
Phase Status
| Phase | Status | Notes |
|---|---|---|
| Pre-Flight | COMPLETE | Windows CollectionView fix with HostApp + shared UI coverage for Issue17864; current branch implements a factory-boundary null guard. |
| Gate | PASSED | Full Windows verification: test failed without fix and passed with fix. |
| Try-Fix | COMPLETE | 11 attempts run, 9 passing; handler-timing alternatives failed, and the PR's fix was selected as best. |
| Report | COMPLETE |
Summary
The PR should be approved. The live implementation is a small, robust Windows-only fix that passed gate verification and held up well against extensive try-fix exploration. The added repro page and UI test cover both toggle directions exercised by the bug scenario, so the review now has both regression coverage and empirical validation.
Root Cause
During Windows CollectionView IsGrouped transitions, a flat top-level item can temporarily flow through the grouped pipeline. GroupedItemTemplateCollection casts that item with group as IEnumerable; for flat items the cast yields null. Without normalization, that null reaches the templated-item pipeline and is later dereferenced while WinUI enumerates grouped items, producing the NullReferenceException.
Fix Quality
Multi-model try-fix found many passing variants, but they all clustered around the same core idea: normalize or reject invalid grouped input somewhere in the data pipeline. The two handler-layer timing ideas failed. Among the passing candidates, the PR's factory-boundary normalization is the best balance of simplicity and robustness: it is smaller and less invasive than group-shape gating, avoids the semantic change introduced by singleton wrapping, and prevents null from leaking to downstream grouped consumers earlier than leaf-level enumerator guards.
kubaflo
left a comment
There was a problem hiding this comment.
Could you please review the AI's summary?
c42a16d to
1a22771
Compare
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 27331Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 27331" |
|
@kubaflo , Addressed AI review summary concerns. |
…perty is changed to false (#27331) ### Issue Details NullReference exception is thrown when disabling IsGrouped at runtime. ### Root Cause The value passed as the itemsource when creating a groupItemsList is not IEnumerable. This causes the value to be set as null. ### Description of Change Ensured the value is of type IEnumerable and pass it to Create method of TemplatedItemSourceFactory to avoid null reference exceptions. Validated the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Issues Fixed Fixes #17864 Fixes #28824 ### Output | Before | After | | ------ | ----- | |<video src="https://github.com/user-attachments/assets/a651b7ce-a1e7-4603-95bc-9638fc3cf7c7">|<video src="https://github.com/user-attachments/assets/3729a956-d37e-4fc2-aaa2-ec4c43493054">| ---------
Issue Details
NullReference exception is thrown when disabling IsGrouped at runtime.
Root Cause
The value passed as the itemsource when creating a groupItemsList is not IEnumerable. This causes the value to be set as null.
Description of Change
Ensured the value is of type IEnumerable and pass it to Create method of TemplatedItemSourceFactory to avoid null reference exceptions.
Validated the behaviour in the following platforms
Issues Fixed
Fixes #17864
Fixes #28824
Output
Before.mp4
After.mp4