[Android] Fix for Incorrect ItemsViewScrolledEventArgs Values in CollectionView with Grouped Items#31437
[Android] Fix for Incorrect ItemsViewScrolledEventArgs Values in CollectionView with Grouped Items#31437SyedAbdulAzeemSF4852 wants to merge 8 commits into
Conversation
There was a problem hiding this comment.
Pull Request Overview
This PR fixes incorrect item index values in CollectionView's ItemsViewScrolledEventArgs when IsGrouped is set to true. The issue affected both Android and iOS platforms where the FirstVisibleItemIndex, CenterItemIndex, and LastVisibleItemIndex reported wrong values due to group headers and footers not being properly handled.
- Updated Android's RecyclerViewScrollListener to correctly map RecyclerView positions to actual data item indices by excluding group headers and footers
- Fixed iOS sorting logic to order visible items by Section first, then Row for proper cross-section ordering
- Added comprehensive test coverage with both HostApp UI test page and NUnit test implementation
Reviewed Changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs | Major refactoring of GetVisibleItemsIndex method with new helper methods to handle grouped data sources correctly |
| src/Controls/src/Core/Handlers/Items/iOS/ItemsViewDelegator.cs | Updated sorting logic to order by Section then Row for consistent ordering |
| src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs | Applied same sorting fix as the other iOS delegator |
| src/Controls/tests/TestCases.HostApp/Issues/Issue17664.cs | Added UI test page demonstrating grouped CollectionView scrolling behavior |
| src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17664.cs | Added NUnit test to validate correct visible item indices |
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
76a383f to
9e6da5d
Compare
🤖 AI Summary📊 Expand Full Review🔍 Pre-Flight — Context & Validation📝 Review Session — Used Math.Clamp instead of Math.Max to ensure that visible item indices stay within the valid range. ·
|
| File | Changes | Purpose |
|---|---|---|
RecyclerViewScrollListener.cs |
+120/-37 | Android: AdjustGroupIndex helpers to skip headers/footers |
ItemsViewDelegator.cs (Items/) |
+2/-1 | iOS: Sort by Section then Row |
ItemsViewDelegator2.cs (Items2/) |
+2/-1 | iOS/MacCatalyst: Same sort fix |
Issue17664.cs (HostApp) |
+120 | Test page with grouped CollectionView |
Issue17664.cs (Shared.Tests) |
+29 | NUnit test validating fix |
Key Findings
- Fix uses
AdjustGroupIndexwith 6 Copilot reviewer flagged this as complexparameters AdjustGroupIndexreturnsGetGroupedDataCount()-1for negative positions; Copilot suggests returning0instead- Test uses
#if WINDOWSinline directive withThread.Sleep( violates UI test guidelines (no inline #if in test methods)1000) - Both new test files are missing trailing newlines
kubafloCHANGES_REQUESTED (open, Feb 19, 2026): Split PR into 2 separate PRs (iOS and Android)- Windows checked in validated platforms table but no Windows code changed;
[Issue]attribute restricts test to iOS|Android - Prior agent review (Feb 18-20, 2026) confirmed gate passed on Android; 5 try-fix alternatives all passed; recommended REQUEST CHANGES
Reviewer Feedback Summary
| Reviewer | Status | Key Feedback |
|---|---|---|
| jsuarezruiz | CHANGES_REQUESTED (resolved) | Expand validation, use Math.Clamp, clarify dataIndex semantics |
| copilot-pull-request-reviewer | COMMENTED | Negative position should return 0; AdjustGroupIndex has 6 params |
| kubaflo | CHANGES_REQUESTED (OPEN) | Split PR into 2 separate PRs: iOS and Android |
Open Issues
- BLOCKING (kubaflo): Split PR into separate iOS and Android not yet addressedPRs
AdjustGroupIndexreturns last item for negative positions; should return0(first item)- Inline
#if WINDOWS+Thread.Sleepin test violates UI test guidelines - Missing trailing newlines in both test files
- Windows checked as validated when no Windows code changed
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #31437 | Android: AdjustGroupIndex helpers to skip headers/footers; iOS: sort by Section+ PENDING (Gate) | 3 fix files | Original PR | Row |
🚦 Gate — Test Verification
📝 Review Session — Used Math.Clamp instead of Math.Max to ensure that visible item indices stay within the valid range. · 9e6da5d
Result PASSED:
Platform: android
Mode: Full Verification
- Tests FAIL without fix
- Tests PASS with fix (imported from prior agent emulator config blocker in current session)review
Fresh Verification (This Session)
- Without fix: Tests Test correctly detected the absence of the fixFAILED
- With fix: Android emulator config error (
Unknown AVD name [E]); infrastructure issue, not a code problemINCONCLUSIVE
Prior Review Import
Prior agent review (Feb 18, 2026) confirmed full verification passed on Android:
- Tests FAIL without fix
- Tests PASS with fix
- Commit validated:
9e6da5d(unchanged since prior review)
The test VerifyGroupedCollectionViewVisibleItemIndices scrolls to "Category C, Item #2" and verifies LastVisibleItemIndex maps to "Category C item #2". Without the fix, LastVisibleItemIndex is inflated by group headers/footers, causing wrong item text to be displayed.
🔧 Fix — Analysis & Comparison
📝 Review Session — Used Math.Clamp instead of Math.Max to ensure that visible item indices stay within the valid range. · 9e6da5d
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix (claude-sonnet-4.5) | Direct GetGroupAndIndex API to convert positions; ConvertToDataIndex + BLOCKED | 1 file | HVF emulator unavailable | CountDataItemsInGroup |
| 2 | try-fix (claude-opus-4.6) | Position-to-DataIndex mapping array (O(N) once, O(1) lookup); TranslateGroupedPositions + BLOCKED | 1 file | HVF emulator unavailable | ResolveDataIndex |
| PR | PR #31437 | AdjustGroupIndex(6 params) + GetGroupedDataCount + FindNextDataIndex + FindPrevDataIndex; iOS: sort by Section+Row PASS (Gate) | 3 files | Original PR; Gate validated |
Cross-Pollination Round 1
| Model | Response |
|---|---|
| gpt-5.2 | NEW IDEA: Prefix-sum cache per group with incremental updates via collection-change notifications |
| gpt-5.2-codex | NEW IDEA: Store GroupIndex/IndexInGroup in ViewHolder during bind, read from first visible ViewHolder |
| gemini-3-pro-preview | NEW IDEA: Flattened Source wrap IEnumerable as 1:1 IList, shift complexity to data source layer |
| claude-sonnet-4 | NEW IDEA: Differential Position maintain rolling offset of non-data items above viewport |
| claude-sonnet-4.5 | NEW IDEA: (was used in attempt 1) |
All new ideas are architecturally heavier than the PR's fix (require state tracking, collection-change wiring, or significant refactoring) and cannot be validated in the current environment.
Environment Blocker
All 2 fresh try-fix attempts were blocked by Android emulator HVF error:
HVF error: HV_UNSUPPORTED
qemu-system-aarch64-headless: failed to initialize HVF: Invalid argument
Hardware virtualization is unavailable on this machine. Android testing is not possible.
Prior Review Import
Prior agent review (Feb 18-20, 2026) ran all 5 models with a working emulator:
- All 5 alternatives tested and all passed (subtraction counting, cache-based, BinarySearch, GetGroupAndIndex, GroupInfo precompute)
- PR's fix was selected as best: simplest to review, already validated, fewest moving parts
Exhausted: Yes (environment-limited; all reasonable algorithmic approaches are covered; cross-pollination produced new ideas but all are architecturally heavier than PR's fix)
Selected Fix: PR's only validated candidate; 5 independent alternatives from prior review also passed, confirming the approach is sound. New ideas from cross-pollination require significant additional complexity that is not warranted for this targeted fix.fix
📋 Report — Final Recommendation
📝 Review Session — Used Math.Clamp instead of Math.Max to ensure that visible item indices stay within the valid range. · 9e6da5d
Final Recommendation: REQUEST CHANGES##
Summary
PR #31437 fixes incorrect FirstVisibleItemIndex, CenterItemIndex, and LastVisibleItemIndex values in ItemsViewScrolledEventArgs when CollectionView.IsGrouped = true. Gate verification (Android) confirmed tests fail without fix and pass with fix. Five independent try-fix alternatives from the prior agent review (Feb 18-20, 2026) also passed, confirming the root cause analysis and fix approach are sound.
However, there are blocking and non-blocking issues that should be addressed before merge.
Root Cause
- Android:
RecyclerViewScrollListener.GetVisibleItemsIndexreturned raw RecyclerView adapter positions without excluding group headers/footers. Every visible item's index was off by the number of headers/footers preceding it in the adapter list. - iOS:
ItemsViewDelegator.GetVisibleIndexPathssorted visibleNSIndexPathvalues only byRow, producing incorrect ordering when items from multipleSections were simultaneously visible. This meansFirstVisibleItemIndexalways appeared to be in section 0.
Fix Quality
- Gate passed ( tests fail without fix, pass with fixAndroid)
- 5 independent try-fix alternatives (prior review) also strong validationpassed
- iOS fix is minimal and clearly correct:
OrderBy(x => x.Section).ThenBy(x => x.Row) - Fix applied consistently to both Items/ and Items2/ iOS delegators
- Non-grouped Android path simplified and cleaned up with Math.Clamp
AdjustGroupIndexhas 6 parameters (flagged by Copilot reviewer as difficult to understand)-
Whenposition < 0,AdjustGroupIndexreturnsGetGroupedDataCount()-1(last Copilot suggests returning0for negative positionsitem) -
Test uses inline#if WINDOWSwithThread.Sleep( violates UI test guidelines1000)-
Issues for Author to Address
Must Fix (blocking)
- kubaflo CHANGES_REQUESTED (open Feb 19, 2026): Split into 2 separate one for iOS fix, one for Android fix. Unaddressed open review from MAUI team member.PRs
Should Fix
- Negative position handling in
AdjustGroupIndex:position < 0returns last item (GetGroupedDataCount()-1). Should return0(first item). Copilot reviewer explicitly flagged this. - Test: inline
#if WINDOWS+Thread.Sleep: UI test guidelines prohibit inline platform directives in test methods. Replace withApp.WaitForElement(...)timeout orVerifyScreenshot(retryTimeout:...). Also:[Issue]attribute restricts page toPlatformAffected.iOS | PlatformAffected.Androidso this Windows code is unreachable. - Missing trailing newlines: Both
Issue17664.cstest files end with\ No newline at end of file.
Consider (nitpick)
AdjustGroupIndex6 parameters: Consider extracting a context struct or splitting into smaller methods.- Windows checked as validated: PR description marks Windows in "Validated platforms" but no Windows-specific code changed and the test page is restricted to iOS|Android.
Title Review
Current: [Android, iOS] Fix for Incorrect ItemsViewScrolledEventArgs Values in CollectionView with Grouped Items
Assessment: Acceptable. Could be tightened to: [Android, iOS] CollectionView: Fix scroll event indices for grouped items
Description Review
Assessment: has NOTE block, per-platform root cause, description of change, and before/after video validation. Minor update recommended: uncheck Windows from validated platforms list.Good
Phase Results
| Phase | Status | Notes |
|---|---|---|
| Pre-Flight COMPLETE | Issue #17664, Android+iOS affected, 5 files changed | |
| Gate PASSED | Android; tests FAIL without fix, PASS with fix (imported from prior review; fresh run confirmed "fails without fix", "with fix" blocked by HVF emulator) | |
| Fix COMPLETE | 2 fresh try-fix attempts BLOCKED (HVF emulator); 5 prior review alternatives all passed; PR's fix selected | |
| Report COMPLETE | REQUEST CHANGES recommended |
📋 Expand PR Finalization Review
Title: ✅ Good
Current: [Android, iOS] Fix for Incorrect ItemsViewScrolledEventArgs Values in CollectionView with Grouped Items
Description: ✅ Good
Description needs updates. See details below.
✨ Suggested PR Description
[!NOTE]
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Issue Details
When a CollectionView's IsGrouped property is set to true, the values of FirstVisibleItemIndex, CenterItemIndex, and LastVisibleItemIndex in the ItemsViewScrolledEventArgs passed to the Scrolled event handler are incorrect.
Root Cause
Android
The GetVisibleItemsIndex method did not account for group headers and footers in grouped data sources. The raw RecyclerView positions include group header and footer rows, so the calculated indices were offset and inaccurate.
iOS
Visible items were sorted only by Row, which produced incorrect ordering when items from multiple sections were visible simultaneously (e.g., the last item of section 0 followed by the first item of section 1 would be misordered).
Description of Change
Android (RecyclerViewScrollListener.cs)
When the items source is a grouped source (IGroupableItemsViewSource, excluding UngroupedItemsSource), the raw RecyclerView position is translated to a logical data-item index via a new AdjustGroupIndex helper. This helper iterates through the items, skipping group headers and footers, to map the raw position to the correct 0-based data item index. Helper methods FindNextDataIndex and FindPrevDataIndex are used to snap header/footer positions to the nearest preceding or following data item. GetGroupedDataCount counts only data items for bounds checking.
For non-grouped sources with headers/footers, the existing simpler subtraction logic is preserved (now using Math.Clamp to bound the result).
iOS (ItemsViewDelegator.cs and ItemsViewDelegator2.cs)
Changed the sort from .OrderBy(x => x.Row) to .OrderBy(x => x.Section).ThenBy(x => x.Row) so that IndexPathsForVisibleItems is ordered correctly across section boundaries. The fix is applied to both the legacy handler (Items/) and the current handler (Items2/).
Issues Fixed
Fixes #17664
Platforms Tested
- Android ✅ Fixed
- iOS ✅ Fixed
- Windows
⚠️ Not fixed in this PR (original issue also affects Windows — tracked separately) - Mac
⚠️ Not fixed in this PR
Code Review: ⚠️ Issues Found
Code Review — PR #31437
🔴 Critical Issues
1. Potential off-by-one: dataIndex initialized to hasHeader ? 1 : 0 in AdjustGroupIndex
File: src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs
Problem:
int dataIndex = hasHeader ? 1 : 0, currentItem = hasHeader ? 1 : 0;When hasHeader = true (the CollectionView has a global header), dataIndex starts at 1. The first data item encountered in the loop would then return index 1 instead of 0. This means all data indices are off by one when the collection has both a global header and grouped content.
The existing test (Issue17664) creates a CollectionView with no global header (hasHeader = false), so it doesn't cover this case and won't catch the bug.
currentItem correctly starts at 1 to skip the global header row in the RecyclerView. But dataIndex should always start at 0 because data items are 0-indexed from the consumer's perspective.
Recommendation:
int dataIndex = 0, currentItem = hasHeader ? 1 : 0;🟡 Suggestions / Nitpicks
2. Open review comment not addressed: position < 0 returns last item index (incorrect)
File: src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs (line ~130)
Open comment from copilot-pull-request-reviewer (unresolved):
The current guard for out-of-bounds position:
if (position < 0 || position >= count)
{
return Math.Max(0, GetGroupedDataCount(source) - 1);
}Returns the last valid data item index for both negative positions and positions beyond the count. For position < 0 (item not visible / no item found), returning the last item index is incorrect. It should return 0 (or -1 if the caller can handle it), as a negative position means "no item visible at this end."
Recommendation:
if (position < 0)
{
return 0;
}
if (position >= count)
{
return Math.Max(0, GetGroupedDataCount(source) - 1);
}This matches the suggestion left by copilot-pull-request-reviewer (thread PRRT_kwDOD6PVWM5brioJ), which is still open and unresolved.
3. Open review comment not addressed: AdjustGroupIndex has 6 parameters
File: src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs
Open comment from copilot-pull-request-reviewer (unresolved, thread PRRT_kwDOD6PVWM5briop):
The AdjustGroupIndex method signature is difficult to understand at the call site:
static int AdjustGroupIndex(IGroupableItemsViewSource source, int position, bool hasHeader, bool hasFooter, int count, bool isStart)The isStart parameter is particularly unclear — it's a boolean flag that controls whether to snap to the "next" or "prev" data item when the position lands on a group header/footer. For the centerItemIndex call, isStart: true is used, but "center" semantics don't clearly map to either "start" or "end."
Recommendation: Either create a parameter object, or document the isStart parameter semantics with a comment, and verify the center-item behavior is correct.
4. Open unresolved discussion: dataIndex semantics in FindNextDataIndex
File: src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs (line ~191)
Open comment from jsuarezruiz (thread PRRT_kwDOD6PVWM5f9Mjy, unresolved):
The reviewer asked what dataIndex represents in FindNextDataIndex — is it the index of the "next item to return (exclusive)" or the "last item seen (inclusive)"? The author replied in the thread but the comment was not resolved, indicating the answer may not have been sufficient or a code clarification is still needed.
Recommendation: Add a clarifying comment to FindNextDataIndex explaining the semantics:
// dataIndex: the 0-based data item index to assign to the next valid item found.
// Returned without incrementing because the item following a header/footer inherits this index.
static int FindNextDataIndex(IGroupableItemsViewSource source, int start, bool hasFooter, int count, int dataIndex)5. Performance: O(n) traversal on every scroll event for grouped collections
File: src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs
AdjustGroupIndex iterates linearly through items to translate a RecyclerView position to a data index. It is called three times per scroll event (for first, center, last). GetGroupedDataCount also iterates all items when position is out of bounds. For large grouped datasets, this O(n) traversal on every OnScrolled callback could cause jank.
Recommendation: This is acceptable for typical grouped list sizes, but worth noting. A future optimization could cache a position→data-index mapping that is invalidated when items change.
6. Test uses #if WINDOWS Thread.Sleep(1000) — anti-pattern
File: src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17664.cs
#if WINDOWS
Thread.Sleep(1000);
#endifPer the UI test guidelines, arbitrary Thread.Sleep calls are discouraged. Use App.WaitForElement with a timeout or VerifyScreenshot(retryTimeout: ...) instead. Inline #if directives in test methods also reduce readability.
Recommendation:
App.WaitForElement("Issue17664DescriptionLabel", timeout: TimeSpan.FromSeconds(3));
var resultItem = App.FindElement("Issue17664DescriptionLabel").GetText();7. Missing newline at end of test files
Both new test files end with \ No newline at end of file:
src/Controls/tests/TestCases.HostApp/Issues/Issue17664.cssrc/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17664.cs
Recommendation: Add a trailing newline to both files.
✅ Looks Good
- iOS fix is clean and correct. Changing
OrderBy(x => x.Row)toOrderBy(x => x.Section).ThenBy(x => x.Row)is a minimal, well-targeted fix. Applied correctly to bothItemsViewDelegator.cs(Items/ handler) andItemsViewDelegator2.cs(Items2/ handler). - Grouped vs non-grouped detection using
itemsSource is not UngroupedItemsSource && itemsSource is IGroupableItemsViewSourceis appropriate. Math.Clampon the non-grouped path (addressing a previous review comment) correctly bounds indices.- Test structure is appropriate — uses
AutomationId, inherits_IssuesUITest, has single[Category], correct naming convention. - Test scenario is representative — uses 3 groups × 5 items to verify index correctness after programmatic scroll.
kubaflo
left a comment
There was a problem hiding this comment.
Could you split this PR on 2 separate ones: iOS and Android? Please use the same test for both
kubaflo
left a comment
There was a problem hiding this comment.
Can you please resolve conflicts?
|
/review -b feature/enhanced-reviewer -p android |
|
/review rerun |
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 5 findings
See inline comments for details.
| if (itemsSource is not UngroupedItemsSource && itemsSource is IGroupableItemsViewSource groupable) | ||
| { | ||
| return (firstVisibleItemIndex, centerItemIndex, lastVisibleItemIndex); | ||
| return ( |
There was a problem hiding this comment.
[major] CollectionView Android — RemainingItemsThreshold regression for grouped collections
GetVisibleItemsIndex now returns logical data-item indices (0-based, excluding group headers/footers) for grouped sources. However, OnScrolled still computes actualItemCount = ItemsViewAdapter.ItemCount - headerValue - footerValue, which includes group headers and group footers in the count.
Concrete scenario: 3 categories × 5 items with group headers = 18 adapter items, no collection header/footer.
actualItemCount = 18Last(logical data index) is at most 14Last == actualItemCount - 1→14 == 17→ never trueRemainingItemsThresholdReachednever fires for grouped collections after this PR
This is a functional regression introduced by this fix. The OnScrolled threshold check at lines 78–92 must be updated to use the logical data count (e.g., GetGroupedDataCount) when the source is grouped, rather than the raw adapter item count.
| /// true = snap to the first data item in the following group (use for FirstVisible/Center), | ||
| /// false = snap to the last data item in the preceding group (use for LastVisible). | ||
| /// </param> | ||
| static int AdjustGroupIndex(IGroupableItemsViewSource source, int position, bool hasHeader, bool hasFooter, int count, bool snapForward) |
There was a problem hiding this comment.
[major] Performance-Critical Path — O(N²) per scroll event for grouped collections
AdjustGroupIndex iterates from position 0 to position, and on each step calls source.IsGroupHeader(currentItem) and source.IsGroupFooter(currentItem). Each call to ObservableGroupedSource.IsGroupHeader invokes GetGroupAndIndex(currentItem), which itself runs an O(currentItem) while-loop. Total cost = Σ(0..position) of O(currentItem) = O(position²) per call.
GetVisibleItemsIndex invokes AdjustGroupIndex three times per scroll event (first, center, last). At 60 fps with a 1000-item grouped list (e.g., 100 groups × 10 items + 100 group headers = 1100 adapter positions), this is roughly 3 × O(1100²) ≈ 3.6 M operations per scroll event.
Recommendation: avoid calling IsGroupHeader/IsGroupFooter in a position-scanning loop. Instead, implement a single O(N) forward pass that maintains group context (current group index + offset within group) and advances it in lockstep with currentItem, eliminating repeated calls to GetGroupAndIndex from scratch.
|
|
||
| if (firstVisibleItemIndex == 0 && lastVisibleItemIndex == itemsCount - 1) | ||
| // Adjust for footer: if the last visible item is the footer, decrement to get the last data item index | ||
| if (hasFooter && lastVisibleItemIndex == itemsCount - 1) |
There was a problem hiding this comment.
[moderate] Logic and Correctness — centerItemIndex not adjusted for footer in non-grouped path
The footer check only decrements lastVisibleItemIndex. If the center-visible adapter position is the footer (itemsCount - 1), centerItemIndex is not decremented here. After the subsequent header-offset subtraction it becomes itemsCount - 2. For a 5-data-item list with both header and footer (itemsCount = 7): centerItemIndex = 6 (footer) → no footer decrement → header decrement → 5 → Math.Clamp(5, 0, maxValidIndex=6) = 5. The valid data-index range is 0–4, so CenterItemIndex = 5 is reported but does not correspond to any data item.
Fix: also apply the footer decrement to centerItemIndex when hasFooter && centerItemIndex == itemsCount - 1.
| centerItemIndex--; | ||
| } | ||
|
|
||
| int maxValidIndex = Math.Max(0, itemsSource.Count - 1); |
There was a problem hiding this comment.
[minor] Logic and Correctness — maxValidIndex upper bound is too large
itemsSource.Count includes the collection header and footer (see ObservableItemsSource.Count = ItemsCount() + (HasHeader?1:0) + (HasFooter?1:0) and ListSource.Count — both include header/footer). So maxValidIndex = itemsSource.Count - 1 equals dataCount + header + footer - 1 rather than dataCount - 1.
In practice, the prior footer/header decrements keep values within the correct range for first and last. But the oversized ceiling means the clamp does not catch a centerItemIndex that slips one position past the last valid data index (see the footer-adjustment bug above). The correct bound should be:
int maxValidIndex = Math.Max(0, itemsSource.Count - 1 - (hasHeader ? 1 : 0) - (hasFooter ? 1 : 0));| // Windows: The Scrolled event is not consistently triggered in the CI environment during automated | ||
| // scrolling, so the label text is never updated. This is a test infrastructure limitation on Windows; | ||
| // the fix itself is iOS/MacCatalyst-only and works correctly on iOS and MacCatalyst. | ||
| #if TEST_FAILS_ON_WINDOWS // Windows: The Scrolled event is not consistently triggered in the CI environment during automated scrolling, so the label text is not updated. This is a known limitation of the test infrastructure on Windows. |
There was a problem hiding this comment.
[minor] Regression Prevention — test validates only LastVisibleItemIndex; FirstVisibleItemIndex and CenterItemIndex are untested
The fix translates all three grouped adapter positions to logical data indices (AdjustGroupIndex is called for first, center, and last). The test only exercises the LastVisibleItemIndex path via the Scrolled event handler. A regression in firstVisibleItemIndex or centerItemIndex (e.g., the missing footer adjustment or a future refactor) would go undetected.
Consider adding a second scroll scenario that exposes FirstVisibleItemIndex and CenterItemIndex through the label or separate labels so all three indices are exercised.
kubaflo
left a comment
There was a problem hiding this comment.
Could you please check the ai's suggestions?
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
AI Review Summary
@SyedAbdulAzeemSF4852 — new AI review results are available based on this last commit:
d01eeb8. To request a fresh review after new comments or commits, comment/review rerun.
🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix
Gate Result: ✅ PASSED
Platform: ANDROID · Base: main · Merge base: 5ec887fa
| Test | Without Fix (expect FAIL) | With Fix (expect PASS) |
|---|---|---|
🖥️ Issue17664 Issue17664 |
✅ FAIL — 1459s | ✅ PASS — 644s |
🔴 Without fix — 🖥️ Issue17664: FAIL ✅ · 1459s
Determining projects to restore...
Restored /home/vsts/work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 615 ms).
Restored /home/vsts/work/1/s/src/Essentials/src/Essentials.csproj (in 3.86 sec).
Restored /home/vsts/work/1/s/src/Core/src/Core.csproj (in 5.45 sec).
Restored /home/vsts/work/1/s/src/Core/maps/src/Maps.csproj (in 2.47 sec).
Restored /home/vsts/work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 51 ms).
Restored /home/vsts/work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 32 ms).
Restored /home/vsts/work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 38 ms).
Restored /home/vsts/work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj (in 134 ms).
Restored /home/vsts/work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 856 ms).
Restored /home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj (in 1.45 sec).
1 of 11 projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
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.90-ci+azdo.14486947
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
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.90-ci+azdo.14486947
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
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.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
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
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.90-ci+azdo.14486947
Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
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.90-ci+azdo.14486947
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
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
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:11:10.12
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
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Starting: Intent { act=android.settings.SETTINGS }
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Determining projects to restore...
Restored /home/vsts/work/1/s/src/Controls/tests/CustomAttributes/Controls.CustomAttributes.csproj (in 1.29 sec).
Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils/VisualTestUtils.csproj (in 14 ms).
Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils.MagickNet/VisualTestUtils.MagickNet.csproj (in 5.81 sec).
Restored /home/vsts/work/1/s/src/Controls/tests/TestCases.Android.Tests/Controls.TestCases.Android.Tests.csproj (in 7.42 sec).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Core/UITest.Core.csproj (in 2 ms).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Appium/UITest.Appium.csproj (in 3 ms).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.NUnit/UITest.NUnit.csproj (in 802 ms).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Analyzers/UITest.Analyzers.csproj (in 2.19 sec).
5 of 13 projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.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.90-ci+azdo.14486947
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
UITest.Core -> /home/vsts/work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
VisualTestUtils -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
UITest.NUnit -> /home/vsts/work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
VisualTestUtils.MagickNet -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
UITest.Appium -> /home/vsts/work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
UITest.Analyzers -> /home/vsts/work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
Controls.TestCases.Android.Tests -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 06/25/2026 17:33:08 FixtureSetup for Issue17664(Android)
>>>>> 06/25/2026 17:33:11 VerifyGroupedCollectionViewVisibleItemIndices Start
>>>>> 06/25/2026 17:33:15 VerifyGroupedCollectionViewVisibleItemIndices Stop
>>>>> 06/25/2026 17:33:16 Log types: logcat, bugreport, server
Failed VerifyGroupedCollectionViewVisibleItemIndices [7 s]
Error Message:
Assert.That(resultItem, Is.EqualTo("Category C item #2"))
Expected string length 18 but was 50. Strings differ at index 0.
Expected: "Category C item #2"
But was: "Use the button above to scroll the CollectionView."
-----------^
Stack Trace:
at Microsoft.Maui.TestCases.Tests.Issues.Issue17664.VerifyGroupedCollectionViewVisibleItemIndices() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17664.cs:line 24
1) at Microsoft.Maui.TestCases.Tests.Issues.Issue17664.VerifyGroupedCollectionViewVisibleItemIndices() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17664.cs:line 24
NUnit Adapter 4.5.0.0: Test execution complete
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.17] Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.63] Discovered: Controls.TestCases.Android.Tests
Results File: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue17664.trx
Total tests: 1
Failed: 1
Test Run Failed.
Total time: 8.2662 Minutes
>>> TRX_RESULT_FILE: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue17664.trx
🟢 With fix — 🖥️ Issue17664: PASS ✅ · 644s
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
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.90-ci+azdo.14486947
Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
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.90-ci+azdo.14486947
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
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.90-ci+azdo.14486947
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.90-ci+azdo.14486947
Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
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.90-ci+azdo.14486947
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
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
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
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:08:23.45
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
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Starting: Intent { act=android.settings.SETTINGS }
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.90-ci+azdo.14486947
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.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.90-ci+azdo.14486947
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
UITest.Core -> /home/vsts/work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
VisualTestUtils -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
UITest.NUnit -> /home/vsts/work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
VisualTestUtils.MagickNet -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
UITest.Appium -> /home/vsts/work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
UITest.Analyzers -> /home/vsts/work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
Controls.TestCases.Android.Tests -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 06/25/2026 17:44:09 FixtureSetup for Issue17664(Android)
>>>>> 06/25/2026 17:44:11 VerifyGroupedCollectionViewVisibleItemIndices Start
>>>>> 06/25/2026 17:44:15 VerifyGroupedCollectionViewVisibleItemIndices Stop
Passed VerifyGroupedCollectionViewVisibleItemIndices [4 s]
NUnit Adapter 4.5.0.0: Test execution complete
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.16] Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.69] Discovered: Controls.TestCases.Android.Tests
Results File: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue17664.trx
Test Run Successful.
Total tests: 1
Passed: 1
Total time: 18.8463 Seconds
>>> TRX_RESULT_FILE: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue17664.trx
📁 Fix files reverted (1 files)
src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs
📱 UI Tests — CollectionView
Detected UI test categories: CollectionView
❌ Deep UI tests — 579 passed, 42 failed across 1 category on platform-pool agent (replaces in-process counts above).
🧪 UI Test Execution Results (deep, platform pool)
| Category | Tests | Snapshot diffs |
|---|---|---|
CollectionView |
579/621 (42 ❌) | — |
❌ CollectionView — 42 failed tests
VerifyGroupItemScrollToByIndexWithCenterPositionAndHorizontalList_Tomato
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupItemScrollToByIndexWithCenterPositionAndHorizontalList_Tomato() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 3453
at System.Runt
...
VisitAndUpdateItemsSource("Observable Collection","Add/RemoveItemsGrid",19,6)
The app was expected to be running still, investigate as possible crash
TearDown : The app was expected to be running still, investigate as possible crash
at UITest.Appium.NUnit.UITestBase.UITestBaseTearDown() in /_/src/TestUtils/src/UITest.NUnit/UITestBase.cs:line 159
at UITest.Appium.NUnit.UITestBase.TestTearDown() in /_/src/TestUtils/src/UITest.NUnit/UITestBase.cs:line 45
at InvokeStub_UITestBase.TestTearDown(Object, Object, IntPtr*)
--TearDown
at UITest.Appium.NUnit.UITestBase.UITestBaseTearDown() in /_/src/TestUtils/src/UITest.NUnit/UITestBase.cs:line 159
at UITest.Appium.NUnit.UITestBase.TestTearDown() in /_/src/TestUtils/src/UITest.NUnit/UITestBase.cs:line 45
at InvokeStub_UITestBase.TestTearDown(Object, Object, IntPtr*)
1) at UITest.Appium.NUnit.UITestBase.UITestBaseTearDown() in /_/src/TestUtils/src/UITest.NUnit/UITestBase.cs:line 159
at UITest.Appium.NUnit.UITestBase.TestTearDown() in /_/src/TestUtils/src/UITest.NUnit/UITestBase.cs:line 45
at InvokeS
...
VerifyGroupItemScrollToByIndexWithStartPositionAndVerticalGrid_Carrot
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupItemScrollToByIndexWithStartPositionAndVerticalGrid_Carrot() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 3309
at System.Runtime
...
VerifyGroupItemScrollToByIndexWithMakeVisiblePositionAndVerticalGrid_Apricot
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupItemScrollToByIndexWithMakeVisiblePositionAndVerticalGrid_Apricot() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 3207
at System.
...
VerifyScrollToByIndexWithMakeVisiblePositionAndHorizontalList_Kiwi
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyScrollToByIndexWithMakeVisiblePositionAndHorizontalList_Kiwi() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 2137
at System.RuntimeMet
...
VerifyGroupIndexScrollToByIndexWithCenterPositionAndHorizontalList_Tomato
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupIndexScrollToByIndexWithCenterPositionAndHorizontalList_Tomato() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 3026
at System.Ref
...
VerifyScrollToByIndexWithEndPositionAndHorizontalList_Kiwi
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyScrollToByIndexWithEndPositionAndHorizontalList_Kiwi() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 2376
at System.RuntimeMethodHandl
...
VerifyGroupItemScrollToByIndexWithEndPositionAndHorizontalGrid_Potato
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupItemScrollToByIndexWithEndPositionAndHorizontalGrid_Potato() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 3597
at System.Runtime
...
VerifyGroupIndexScrollToByIndexWithEndPositionAndHorizontalGrid_Potato
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupIndexScrollToByIndexWithEndPositionAndHorizontalGrid_Potato() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 3170
at System.Reflec
...
VerifyScrollToByItemWithEndPositionAndHorizontalList_Kiwi
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyScrollToByItemWithEndPositionAndHorizontalList_Kiwi() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 2715
at System.RuntimeMethodHandle
...
VerifyGroupIndexScrollToByIndexWithStartPositionAndHorizontalGrid_Kiwi
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupIndexScrollToByIndexWithStartPositionAndHorizontalGrid_Kiwi() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 2954
at System.Reflec
...
VerifyGroupItemScrollToByIndexWithEndPositionAndHorizontalList_Kiwi
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupItemScrollToByIndexWithEndPositionAndHorizontalList_Kiwi() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 3561
at System.RuntimeMe
...
VerifyGroupItemScrollToByIndexWithStartPositionAndHorizontalList_Kiwi
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupItemScrollToByIndexWithStartPositionAndHorizontalList_Kiwi() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 3345
at System.Runtime
...
VerifyGroupIndexScrollToByIndexWithCenterPositionAndVerticalGrid_Kiwi
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupIndexScrollToByIndexWithCenterPositionAndVerticalGrid_Kiwi() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 2990
at System.Reflect
...
VerifyGroupItemScrollToByItemWithMakeVisiblePositionAndVerticalList_Apricot
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupItemScrollToByItemWithMakeVisiblePositionAndVerticalList_Apricot() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 1943
at System.R
...
VerifyGroupIndexScrollToByIndexWithCenterPositionAndHorizontalGrid_Kiwi
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupIndexScrollToByIndexWithCenterPositionAndHorizontalGrid_Kiwi() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 3062
at System.Refle
...
VerifyGroupItemScrollToByIndexWithEndPositionAndVerticalGrid_Apricot
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupItemScrollToByIndexWithEndPositionAndVerticalGrid_Apricot() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 3525
at System.RuntimeM
...
VerifyGroupItemScrollToByIndexWithMakeVisiblePositionAndHorizontalGrid_Pear
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupItemScrollToByIndexWithMakeVisiblePositionAndHorizontalGrid_Pear() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 3275
at System.R
...
VerifyGroupIndexScrollToByIndexWithStartPositionAndVerticalGrid_Carrot
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupIndexScrollToByIndexWithStartPositionAndVerticalGrid_Carrot() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 2882
at System.Reflec
...
VerifyGroupIndexScrollToByIndexWithStartPositionAndVerticalList_Carrot
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupIndexScrollToByIndexWithStartPositionAndVerticalList_Carrot() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 1838
at System.Runtim
...
VerifyGroupItemScrollToByIndexWithStartPositionAndHorizontalGrid_Kiwi
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupItemScrollToByIndexWithStartPositionAndHorizontalGrid_Kiwi() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 3381
at System.Runtime
...
VerifyScrollToByIndexWithCenterPositionAndHorizontalList_Kiwi
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyScrollToByIndexWithCenterPositionAndHorizontalList_Kiwi() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 2295
at System.RuntimeMethodHa
...
VerifyGroupIndexScrollToByIndexWithEndPositionAndVerticalList_Papaya
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupIndexScrollToByIndexWithEndPositionAndVerticalList_Papaya() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 1908
at System.Reflecti
...
VerifyGroupIndexScrollToByIndexWithMakeVisiblePositionAndHorizontalList_Kiwi
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupIndexScrollToByIndexWithMakeVisiblePositionAndHorizontalList_Kiwi() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 2810
at System.
...
VerifyRemainingItemsThresholdReachedWithHorizontalList
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyRemainingItemsThresholdReachedWithHorizontalList() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 3652
at System.RuntimeMethodHandle.In
...
VerifyGroupIndexScrollToByIndexWithMakeVisiblePositionAndVerticalGrid_Apricot
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupIndexScrollToByIndexWithMakeVisiblePositionAndVerticalGrid_Apricot() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 2774
at System
...
VerifyGroupIndexScrollToByIndexWithEndPositionAndHorizontalList_Kiwi
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupIndexScrollToByIndexWithEndPositionAndHorizontalList_Kiwi() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 3134
at System.Reflecti
...
VerifyGroupIndexScrollToByIndexWithCenterPositionAndVerticalList_Potato
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupIndexScrollToByIndexWithCenterPositionAndVerticalList_Potato() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 1874
at System.Refle
...
VerifyGroupItemScrollToByItemWithStartPositionAndVerticalList_Carrot
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyGroupItemScrollToByItemWithStartPositionAndVerticalList_Carrot() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 1974
at System.RuntimeM
...
VerifyScrollToByIndexWithStartPositionAndHorizontalList_Kiwi
System.TimeoutException : Timed out waiting for element...
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyScrollToByIndexWithStartPositionAndHorizontalList_Kiwi() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 2214
at System.RuntimeMethodHan
...
(+12 more — see TRX in artifact)
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)
📋 Pre-Flight — Context & Validation
Issue: #17664 - [iOS/Mac/Windows] CollectionView ItemsViewScrolledEventArgs are incorrect when IsGrouped = true
PR: #31437 - [Android] Fix for Incorrect ItemsViewScrolledEventArgs Values in CollectionView with Grouped Items
Platforms Affected: Android CollectionView grouped scrolling
Files Changed: 1 implementation, 2 test
Key Findings
- PR changes Android
RecyclerViewScrollListener.GetVisibleItemsIndex()to translate grouped adapter positions into logical data-item indices by skipping group headers/footers. - PR enables the existing Issue17664 UI test on Android; caller-provided gate result says the test fails without the fix and passes with the PR fix.
- GitHub CLI was unauthenticated, so authenticated PR comments/reviews/checks could not be queried; public PR/issue metadata and local committed diff were used.
- Expert review flagged grouped
RemainingItemsThresholdand empty-group sentinel handling as likely regressions in the current PR approach.
Code Review Summary
Verdict: NEEDS_CHANGES
Confidence: low
Errors: 2 | Warnings: 2 | Suggestions: 0
Key code review findings:
- ✗
RecyclerViewScrollListener.cs: groupedLastVisibleItemIndexbecomes a logical data index, butRemainingItemsThresholdstill compares it to an adapter count that includes group headers/footers. - ✗
RecyclerViewScrollListener.cs: grouped empty data can report0rather than-1/no visible data item. - ⚠
RecyclerViewScrollListener.cs: three per-scroll grouped translations repeatedly scan adapter positions in a hot path. - ⚠
Issue17664.cs: Android UI coverage validates only one happy-pathLastVisibleItemIndexscenario.
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #31437 | Map grouped adapter positions to logical data indices inside RecyclerViewScrollListener using new helper scans and enable Issue17664 on Android. |
✅ PASSED (Gate supplied) | RecyclerViewScrollListener.cs, Issue17664 test files |
Original PR; fixes simple repro but has threshold/empty-group concerns. |
🔬 Code Review — Deep Analysis
Code Review — PR #31437
Independent Assessment
What this changes: Android RecyclerViewScrollListener now translates grouped CollectionView RecyclerView adapter positions into flat logical data-item indices before raising ItemsViewScrolledEventArgs.
Inferred motivation: grouped Android CollectionView adapter positions include group headers/footers, but FirstVisibleItemIndex, CenterItemIndex, and LastVisibleItemIndex are expected to refer to data items only.
Reconciliation with PR Narrative
Author claims: PR #31437 fixes #17664 by mapping grouped Android adapter positions to logical data item indices in RecyclerViewScrollListener, skipping group headers/footers, and enabling the Issue17664 UI test on Android.
Agreement/disagreement: The intent matches the code and the simple issue test. The implementation appears narrow and risks regressions for grouped RemainingItemsThreshold, empty groups, and scroll hot-path cost.
Prior Review Reconciliation
| Prior ❌ Error Finding | Source | Status | Evidence |
|---|---|---|---|
| No prior ❌ Error findings found. | N/A | N/A | GitHub CLI was unauthenticated; public PR/issue metadata and local diff were available, but review/comment surfaces requiring authenticated gh were unavailable. |
Blast Radius Assessment
- Runs for all instances: Android
CollectionView/CarouselViewscroll listener path; grouped source mapping only affects grouped sources, but the method is invoked on every scroll callback. - Startup impact: No.
- Static/shared state: No.
CI Status
- Required-check result: undetermined
- Classification: undetermined
- Action taken: GitHub CLI was unauthenticated (
gh auth loginrequired); confidence capped low. Gate result supplied by caller: Gate ✅ PASSED — tests fail without fix, pass with fix.
Findings
❌ Error — RemainingItemsThresholdReached is now wrong for grouped sources with group headers/footers
src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs: GetVisibleItemsIndex() returns LastVisibleItemIndex as a logical data-item index for grouped sources, but OnScrolled() still compares Last against ItemsViewAdapter.ItemCount - header - footer, which still includes group headers and group footers. For 3 groups x 5 items plus group headers, the last logical data index is 14 while the adapter-derived count says 17, so low thresholds can fail to fire.
❌ Error — Empty grouped sources report index 0 instead of “no data item”
src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs: the new helpers clamp missing positions to 0, so grouped sources with only group headers/footers and no data items can report FirstVisibleItemIndex = CenterItemIndex = LastVisibleItemIndex = 0, which is not a valid data item index.
⚠️ Warning — Grouped index translation is expensive for a scroll hot path
src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs: every scroll computes three grouped translations, each walking adapter positions and repeatedly calling group header/footer predicates. Large grouped lists can turn every scroll callback into repeated linear work.
⚠️ Warning — Android UI test only covers the narrow happy path
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17664.cs: the enabled Android UI test indirectly validates only LastVisibleItemIndex for one ScrollTo scenario, not FirstVisibleItemIndex, CenterItemIndex, CollectionView header/footer, group footers, empty groups, or RemainingItemsThreshold.
Failure-Mode Probing
- Grouped list with group headers and
RemainingItemsThreshold=0: threshold can fail becauseLastis logical but the count remains adapter-based. - All groups empty but headers visible: event can report
0even though no data item exists. - Large grouped list: repeated per-scroll scans can cause Android scroll jank.
- Simple Issue17664 repro: likely fixed for the tested Android
LastVisibleItemIndexscenario.
Verdict: NEEDS_CHANGES
Confidence: low
Summary: The PR addresses the reported scenario but introduces likely grouped threshold and empty-data regressions, with a scroll hot-path performance concern. CI status was not independently available because gh is unauthenticated, though the caller supplied a passing gate result.
🛠️ Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix-1 | Source-owned grouped index translation in ObservableGroupedSource; RecyclerViewScrollListener uses logical data count for threshold. |
✅ PASS | 2 files | Passed Android Issue17664 UI test; expert reviewer accepted as better than PR fix. |
| PR | PR #31437 | Scroll-listener helper scans translate grouped adapter positions to logical indices. | ✅ PASSED (Gate supplied) | 3 files | Original PR fixes the simple repro, but expert review found threshold and empty-group regressions. |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| maui-expert-reviewer | 1 | Yes | Suggested moving mapping into the item source, caching grouped offsets, or using visible data cells. Candidate 1 implemented source-owned mapping. |
| maui-expert-reviewer | 2 | No | Reviewed candidate 1 and found no blocking issues; accepted it as demonstrably better than the PR fix. |
Exhausted: No — stopped because candidate 1 passed the Android gate/regression test and was accepted by expert review as demonstrably better than PR #31437's fix.
Selected Fix: Candidate #1 — source-owned grouped index translation; it preserves the passing Issue17664 behavior while addressing the PR fix's grouped threshold and empty-data failure modes.
📝 Recommended PR Title & Description
Assessment: ✏️ Recommend updating — the current title is still acceptable, but the description describes the raw PR listener-helper implementation rather than the winning source-owned grouped-index fix.
Recommended title
[Android] CollectionView: Fix grouped ItemsViewScrolledEventArgs indices
Recommended description
### Issue Details
- When a CollectionView's IsGrouped property is set to true on Android, the FirstVisibleItemIndex, CenterItemIndex, and LastVisibleItemIndex values in ItemsViewScrolledEventArgs can be incorrect because RecyclerView adapter positions include group headers and footers.
### Root Cause
- Android grouped CollectionView adapter positions include top-level headers/footers and group headers/footers, but ItemsViewScrolledEventArgs should report logical 0-based data-item indices.
- Keeping this translation in RecyclerViewScrollListener can also leave related scroll logic in mixed coordinate spaces; specifically, RemainingItemsThreshold must compare LastVisibleItemIndex against a logical data-item count, not an adapter count that still includes group headers/footers.
### Description of Change
**Android grouped item source**
- Move grouped adapter-position to logical data-item index translation into ObservableGroupedSource, which owns group/header/footer structure.
- Add source-owned helpers for logical grouped data item count and data-item index lookup with forward/backward snapping when a visible adapter position lands on a group header or group footer.
- Preserve -1 when there is no valid visible data item instead of reporting index 0 for empty/no-visible grouped data.
**Android RecyclerViewScrollListener.cs**
- Use ObservableGroupedSource to translate FirstVisibleItemIndex, CenterItemIndex, and LastVisibleItemIndex for grouped sources.
- Use the same logical data-item count for RemainingItemsThreshold calculations so grouped incremental loading remains in the same coordinate space as LastVisibleItemIndex.
- Preserve the existing non-grouped header/footer adjustment path.
**Tests**
- Enable the existing Issue17664 UI test on Android.
### Issues Fixed
Fixes #17664
### Validated the behaviour in the following platforms
- [x] Windows
- [x] Android
- [x] iOS
- [x] Mac
iOS fix PR: https://github.com/dotnet/maui/pull/34240
### Output
| Before | After |
|----------|----------|
| <video src="https://github.com/user-attachments/assets/8205331c-6f92-42ae-a5a1-b18e01b3279f"> | <video src="https://github.com/user-attachments/assets/d7e4ba94-7566-44a0-8c5c-4c5deda1b5b5"> |
🏁 Report — Final Recommendation
Comparative Report — PR #31437
Candidates compared
| Rank | Candidate | Regression result | Assessment |
|---|---|---|---|
| 1 | try-fix-1 |
✅ PASS | Best candidate. Moves grouped index translation into ObservableGroupedSource, uses logical grouped data count for RemainingItemsThreshold, preserves -1 for no valid data item, and avoids the PR's listener-owned repeated helper scans. |
| 2 | pr-plus-reviewer |
✅ Equivalent to tested try-fix-1 patch shape; sandbox-applied only |
Applies the expert reviewer's feedback and converges on the same source-owned grouped-index approach as try-fix-1, but the recorded STEP 5a candidate has the direct test artifact and complete attempt history. |
| 3 | pr |
✅ Gate supplied PASS for Issue17664 | Fixes the narrow Android grouped LastVisibleItemIndex repro, but expert review found two major regressions: grouped RemainingItemsThreshold compares logical indices to adapter counts, and empty/no-visible grouped data can report index 0. |
No candidate with a failed regression test is ranked above a passing candidate.
Winning candidate
Winner: try-fix-1
try-fix-1 is the only candidate with both explicit passing Android regression-test evidence and a design that addresses all expert-review findings. It keeps ItemsViewScrolledEventArgs indices and RemainingItemsThreshold calculations in the same logical data-item coordinate space, returns -1 when no data item exists, and centralizes grouped semantics in the grouped source instead of the scroll listener.
Why the raw PR does not win
The raw PR is a valid narrow repro fix, but it leaves the scroll listener in mixed coordinate spaces. In grouped sources, LastVisibleItemIndex is converted to a logical data index while the threshold logic still uses an adapter-derived count containing group headers/footers. It also clamps missing/empty grouped positions to 0, which creates a false valid item index.
Why pr-plus-reviewer does not win
The reviewer-improved sandbox candidate is technically strong and effectively matches try-fix-1, but it was created as a sandbox application of the reviewer feedback during this phase. try-fix-1 has the same core fix plus the recorded STEP 5a PASS result and prior expert acceptance, so it is the single strongest artifact-backed winner.
🧭 Next Steps — alternative fix proposed (try-fix-1)
Automated review — alternative fix proposed
The expert-reviewer evaluation compared the PR fix against automatically generated candidates and selected try-fix-1 as the strongest fix.
Why: try-fix-1 wins because it passed the Android Issue17664 regression gate and fixes the expert-reviewed PR regressions by keeping grouped visible indices and RemainingItemsThreshold in the same logical data-item coordinate space while preserving -1 for no visible data item.
Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.
Candidate diff (try-fix-1)
diff --git a/src/Controls/src/Core/Handlers/Items/Android/ItemsSources/ObservableGroupedSource.cs b/src/Controls/src/Core/Handlers/Items/Android/ItemsSources/ObservableGroupedSource.cs
index 3a9c57f1f8..f5acd381cd 100644
--- a/src/Controls/src/Core/Handlers/Items/Android/ItemsSources/ObservableGroupedSource.cs
+++ b/src/Controls/src/Core/Handlers/Items/Android/ItemsSources/ObservableGroupedSource.cs
@@ -37,6 +37,20 @@ namespace Microsoft.Maui.Controls.Handlers.Items
public bool HasFooter { get; set; }
public bool ObserveChanges { get; set; } = true;
+ internal int DataItemCount
+ {
+ get
+ {
+ int count = 0;
+ for (int groupIndex = 0; groupIndex < _groups.Count; groupIndex++)
+ {
+ count += GetDataItemCount(_groups[groupIndex]);
+ }
+
+ return count;
+ }
+ }
+
public ObservableGroupedSource(GroupableItemsView groupableItemsView, ICollectionChangedNotifier notifier)
{
_groupableItemsView = groupableItemsView;
@@ -137,6 +151,58 @@ namespace Microsoft.Maui.Controls.Handlers.Items
return _groups[group].GetItem(inGroup);
}
+ internal int GetDataItemIndex(int position, bool snapForward)
+ {
+ int dataItemCount = DataItemCount;
+ if (position < 0 || dataItemCount == 0)
+ {
+ return -1;
+ }
+
+ if (position >= Count || IsFooter(position))
+ {
+ return dataItemCount - 1;
+ }
+
+ if (IsHeader(position))
+ {
+ return snapForward ? 0 : -1;
+ }
+
+ var (group, inGroup) = GetGroupAndIndex(position);
+ int previousDataItemCount = 0;
+ for (int groupIndex = 0; groupIndex < group; groupIndex++)
+ {
+ previousDataItemCount += GetDataItemCount(_groups[groupIndex]);
+ }
+
+ var groupSource = _groups[group];
+ int groupDataItemCount = GetDataItemCount(groupSource);
+ if (groupDataItemCount == 0)
+ {
+ return snapForward
+ ? GetNextDataItemIndex(group + 1, previousDataItemCount)
+ : GetPreviousDataItemIndex(group - 1, previousDataItemCount);
+ }
+
+ if (groupSource.IsHeader(inGroup))
+ {
+ return snapForward
+ ? previousDataItemCount
+ : GetPreviousDataItemIndex(group - 1, previousDataItemCount);
+ }
+
+ if (groupSource.IsFooter(inGroup))
+ {
+ return snapForward
+ ? GetNextDataItemIndex(group + 1, previousDataItemCount + groupDataItemCount)
+ : previousDataItemCount + groupDataItemCount - 1;
+ }
+
+ int localDataIndex = inGroup - (groupSource.HasHeader ? 1 : 0);
+ return previousDataItemCount + Math.Clamp(localDataIndex, 0, groupDataItemCount - 1);
+ }
+
public object GetGroup(int groupIndex)
{
return _groupSource[groupIndex];
@@ -496,5 +562,42 @@ namespace Microsoft.Maui.Controls.Handlers.Items
}
return itemCount;
}
+
+ static int GetDataItemCount(IItemsViewSource source)
+ {
+ return Math.Max(0, source.Count - (source.HasHeader ? 1 : 0) - (source.HasFooter ? 1 : 0));
+ }
+
+ int GetNextDataItemIndex(int startGroupIndex, int precedingDataItemCount)
+ {
+ for (int groupIndex = startGroupIndex; groupIndex < _groups.Count; groupIndex++)
+ {
+ int groupDataItemCount = GetDataItemCount(_groups[groupIndex]);
+ if (groupDataItemCount > 0)
+ {
+ return precedingDataItemCount;
+ }
+
+ precedingDataItemCount += groupDataItemCount;
+ }
+
+ return Math.Max(0, DataItemCount - 1);
+ }
+
+ int GetPreviousDataItemIndex(int startGroupIndex, int followingDataItemStart)
+ {
+ for (int groupIndex = startGroupIndex; groupIndex >= 0; groupIndex--)
+ {
+ int groupDataItemCount = GetDataItemCount(_groups[groupIndex]);
+ if (groupDataItemCount > 0)
+ {
+ return followingDataItemStart - 1;
+ }
+
+ followingDataItemStart -= groupDataItemCount;
+ }
+
+ return -1;
+ }
}
}
diff --git a/src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs b/src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs
index e23134fb4f..bdb21d8997 100644
--- a/src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs
+++ b/src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs
@@ -1,4 +1,5 @@
#nullable disable
+using System;
using AndroidX.RecyclerView.Widget;
namespace Microsoft.Maui.Controls.Handlers.Items
@@ -75,14 +76,12 @@ namespace Microsoft.Maui.Controls.Handlers.Items
}
var itemsSource = ItemsViewAdapter.ItemsSource;
- int headerValue = itemsSource.HasHeader ? 1 : 0;
- int footerValue = itemsSource.HasFooter ? 1 : 0;
- // Calculate actual data item count (excluding header and footer positions)
- int actualItemCount = ItemsViewAdapter.ItemCount - footerValue - headerValue;
+ int actualItemCount = itemsSource is ObservableGroupedSource groupedSource
+ ? groupedSource.DataItemCount
+ : ItemsViewAdapter.ItemCount - (itemsSource.HasFooter ? 1 : 0) - (itemsSource.HasHeader ? 1 : 0);
- // Ensure we're within the data items region (not in header/footer)
- if (Last < headerValue || Last > actualItemCount)
+ if (actualItemCount <= 0 || Last < 0 || Last >= actualItemCount)
{
return;
}
@@ -122,9 +121,7 @@ namespace Microsoft.Maui.Controls.Handlers.Items
protected virtual (int First, int Center, int Last) GetVisibleItemsIndex(RecyclerView recyclerView)
{
- var firstVisibleItemIndex = -1;
- var lastVisibleItemIndex = -1;
- var centerItemIndex = -1;
+ int firstVisibleItemIndex = -1, lastVisibleItemIndex = -1, centerItemIndex = -1;
if (recyclerView.GetLayoutManager() is LinearLayoutManager linearLayoutManager)
{
@@ -133,61 +130,39 @@ namespace Microsoft.Maui.Controls.Handlers.Items
centerItemIndex = recyclerView.CalculateCenterItemIndex(firstVisibleItemIndex, linearLayoutManager, _getCenteredItemOnXAndY);
}
- bool hasHeader = ItemsViewAdapter.ItemsSource.HasHeader;
- bool hasFooter = ItemsViewAdapter.ItemsSource.HasFooter;
- int itemsCount = ItemsViewAdapter.ItemCount;
+ var adapter = ItemsViewAdapter;
+ var itemsSource = adapter.ItemsSource;
+ int itemsCount = adapter.ItemCount;
+ bool hasHeader = itemsSource.HasHeader;
+ bool hasFooter = itemsSource.HasFooter;
- if (!hasHeader && !hasFooter)
+ if (itemsSource is ObservableGroupedSource groupedSource)
{
- return (firstVisibleItemIndex, centerItemIndex, lastVisibleItemIndex);
+ return (
+ groupedSource.GetDataItemIndex(firstVisibleItemIndex, snapForward: true),
+ groupedSource.GetDataItemIndex(centerItemIndex, snapForward: true),
+ groupedSource.GetDataItemIndex(lastVisibleItemIndex, snapForward: false)
+ );
}
- if (firstVisibleItemIndex == 0 && lastVisibleItemIndex == itemsCount - 1)
+ // Adjust for footer: if the last visible item is the footer, decrement to get the last data item index
+ if (hasFooter && lastVisibleItemIndex == itemsCount - 1)
{
- lastVisibleItemIndex -= hasHeader && hasFooter ? 2 : 1;
- }
- else
- {
- if (hasHeader && !hasFooter)
- {
- lastVisibleItemIndex -= 1;
- firstVisibleItemIndex -= 1;
- }
- else if (!hasHeader && hasFooter)
- {
- if (lastVisibleItemIndex == itemsCount - 1)
- {
- lastVisibleItemIndex -= 1;
- }
- }
- else if (hasHeader && hasFooter)
- {
- if (firstVisibleItemIndex == 0)
- {
- lastVisibleItemIndex -= 1;
- }
- else if (lastVisibleItemIndex != itemsCount - 1)
- {
- firstVisibleItemIndex -= 1;
- lastVisibleItemIndex -= 1;
- }
- else
- {
- firstVisibleItemIndex -= 1;
- lastVisibleItemIndex -= 2;
- }
- }
+ lastVisibleItemIndex--;
}
- if (firstVisibleItemIndex < 0)
+ // Non-grouped items adjustment
+ if (hasHeader)
{
- firstVisibleItemIndex = 0;
+ firstVisibleItemIndex--;
+ lastVisibleItemIndex--;
+ centerItemIndex--;
}
- if (lastVisibleItemIndex < 0)
- {
- lastVisibleItemIndex = 0;
- }
+ int maxValidIndex = Math.Max(0, itemsSource.Count - 1);
+ firstVisibleItemIndex = Math.Clamp(firstVisibleItemIndex, 0, maxValidIndex);
+ lastVisibleItemIndex = Math.Clamp(lastVisibleItemIndex, 0, maxValidIndex);
+ centerItemIndex = Math.Clamp(centerItemIndex, 0, maxValidIndex);
return (firstVisibleItemIndex, centerItemIndex, lastVisibleItemIndex);
}
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue17664.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue17664.cs
index de43c9ecc4..01b8f653d2 100644
--- a/src/Controls/tests/TestCases.HostApp/Issues/Issue17664.cs
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue17664.cs
@@ -2,7 +2,7 @@ using System.Collections.ObjectModel;
namespace Maui.Controls.Sample.Issues;
-[Issue(IssueTracker.Github, 17664, "Incorrect ItemsViewScrolledEventArgs in CollectionView when IsGrouped is set to true", PlatformAffected.iOS)]
+[Issue(IssueTracker.Github, 17664, "Incorrect ItemsViewScrolledEventArgs in CollectionView when IsGrouped is set to true", PlatformAffected.iOS | PlatformAffected.Android)]
public class Issue17664 : ContentPage
{
CollectionView _collectionView;
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17664.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17664.cs
index 71874bd0c0..13b94a8d1d 100644
--- a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17664.cs
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17664.cs
@@ -1,7 +1,4 @@
-#if TEST_FAILS_ON_ANDROID && TEST_FAILS_ON_WINDOWS // Android fix: https://github.com/dotnet/maui/pull/31437
-// Windows: The Scrolled event is not consistently triggered in the CI environment during automated
-// scrolling, so the label text is never updated. This is a test infrastructure limitation on Windows;
-// the fix itself is iOS/MacCatalyst-only and works correctly on iOS and MacCatalyst.
+#if TEST_FAILS_ON_WINDOWS // Windows: The Scrolled event is not consistently triggered in the CI environment during automated scrolling, so the label text is not updated. This is a known limitation of the test infrastructure on Windows.
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;
This comment has been minimized.
This comment has been minimized.
Tests Failure Analysis
Test Failure Review: No failures found - click to expandOverall verdict: No failures found All 31 checks completed successfully with no failures, no pending checks, and no extracted test failures. Coverage: 31 checks total · 31 passing/neutral/skipped · 0 failing · 0 pending · 0 inaccessible · 0 unmapped · 0 unexplained build legs · 0 unaccounted failing checks · 0 aborted failing checks · 0 canceled-build checks · 0 device-test unverified · 0 unattributed · 0 regressed-vs-base. Deterministic ceiling: No failures found. Recommended actionNo test-failure action is needed. Evidence details
|
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
kubaflo
left a comment
There was a problem hiding this comment.
Could you please check the ai's suggestions?
|
Closing as a stale one - please open a new one if this PR is still needed |
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Issue Details
Root Cause
Description of Change
Android (RecyclerViewScrollListener.cs)
When the items source is a grouped source (IGroupableItemsViewSource, excluding UngroupedItemsSource), the raw RecyclerView position is translated to a logical data-item index via a new AdjustGroupIndex helper. This helper iterates through the items, skipping group headers and footers, to map the raw position to the correct 0-based data item index.
Helper methods FindNextDataIndex and FindPrevDataIndex are used to snap header/footer positions to the nearest preceding or following data item.
GetGroupedDataCount counts only data items for bounds checking.
For non-grouped sources with headers/footers, the existing simpler subtraction logic is preserved (now using Math.Clamp to bound the result).
Issues Fixed
Fixes #17664
Validated the behaviour in the following platforms
iOS fix PR: #34240
Output
Android_Before.mov
Android_After.mov