[Android] - Fix KeepScrollOffset Behavior During Dynamic Item Additions in CollectionView - #29255
Conversation
|
/azp run MAUI-UITests-public |
jsuarezruiz
left a comment
There was a problem hiding this comment.
The test KeepScrollOffset is failing on Android:
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 2420
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 2437
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 680
at Microsoft.Maui.TestCases.Tests.Issues.CollectionViewItemsUpdatingScrollModeUITests.KeepScrollOffset() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CollectionViewUITests.CollectionViewItemsUpdatingScrollMode.cs:line 51
at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
@jsuarezruiz, Since the existing test logic did not align with the expected |
|
/azp run MAUI-UITests-public |
| { | ||
| items = new ObservableCollection<string>(Enumerable.Range(1, 30).Select(i => $"Item {i}")); | ||
|
|
||
| Button keepScrollOffsetButton = CreateButton("KeepScrollOffset", "KeepScrollOffsetButton", OnKeepScrollOffsetClicked); |
There was a problem hiding this comment.
Could include more Buttons to change the ItemsUpdatingScrollMode value https://github.com/dotnet/maui/blob/main/src/Controls/src/Core/Items/ItemsUpdatingScrollMode.cs#L8
and test the behavior with the different possibilities?
There was a problem hiding this comment.
Yes, adding tests for the various ItemsUpdatingScrollMode values would provide broader coverage. However,I would like to highlight a couple of existing platform-specific issues currently impacting these modes:
On Android: The KeepItemsInView mode currently does not work as expected, which is a known issue (#29145). This is being addressed in PR #27153, which is still under review.
On iOS : KeepLastItemInView is also not functioning correctly. This is another known issue (28716), which is also addressed in the same PR currently under review: PR #28720
Given these inconsistencies, only the KeepScrollOffset mode behaves consistently across all platforms at this time. This PR focuses on verifying that stable behavior.
Since the other two PRs handles the KeepItemsInView mode and KeepLastItemInView mode can we avoid adding tests for those modes?
Looking for your insights.
🤖 AI Summary📊 Expand Full Review🔍 Pre-Flight — Context & Validation📝 Review Session — Updated-Test ·
|
| File:Line | Reviewer Says | Author Says | Status |
|---|---|---|---|
| Issue29131.cs:17 | Add buttons to test all ItemsUpdatingScrollMode values |
KeepItemsInView has known issues (#29145, PR #27153); KeepLastItemInView also broken on iOS (#28716, PR #28720). Only KeepScrollOffset stable. |
|
| KeepScrollOffset test | Test failing on Android with TimeoutException | Updated test logic to properly validate expected KeepScrollOffset behavior |
Addressed |
Reviewer (jsuarezruiz) requested changes due to failing test; author updated the tests.
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #29255 | Move ScrollListener setup to UpdateItemsUpdatingScrollMode(); add isFirstItemReached flag to gate scroll correction to top-position-only |
⏳ PENDING (Gate) | MauiRecyclerView.cs, ScrollHelper.cs |
Original PR |
Key Technical Observations
- Logic change: Old code applied
ScrollBy(-delta)unconditionally; new code only applies it whenisFirstItemReachedis true (i.e., offset == 0) - Listener management: Old code added listener lazily (on first
UndoNextScrollAdjustment()); new code adds it eagerly when mode is set - Potential ordering issue: In adapter update path (lines 363→366),
UpdateItemsUpdatingScrollMode()adds ScrollHelper listener, thenAddOrUpdateScrollListener()callsClearOnScrollListeners()which removes ALL listeners including ScrollHelper's. May cause KeepScrollOffset to not work if mode is set before adapter update. - Style issue:
isFirstItemReacheduses camelCase without underscore prefix, inconsistent with existing_undoNextScrollAdjustmentstyle - Missing newlines: Both new
Issue29131.csfiles lack newline at end of file
🚦 Gate — Test Verification
📝 Review Session — Updated-Test · f951e4d
Result: ❌ INCONCLUSIVE (environment blocker)
Platform: android
Mode: Full Verification (attempted)
Test Runs
| Run | Expected | Actual | Notes |
|---|---|---|---|
| Tests WITHOUT fix | FAIL | FAIL (ADB0010) | ADB install error - "Broken Pipe (32)" during build/install |
| Tests WITH fix | PASS | FAIL (TimeoutException) | NavigateToIssue timed out waiting for GoToTestButton |
| Retry WITH fix | PASS | FAIL (TimeoutException) | Same infrastructure failure |
Root Cause of Gate Failure
The failure is infrastructure-related, NOT a fix code failure:
- Without fix run: Build failed at install phase (
ADB0010: Broken Pipe (32)) - never reached test assertions - With fix runs (x2): Build succeeded, app launched, but
NavigateToIssue()timed out waiting forGoToTestButtonon the main app page
The GoToTestButton timeout indicates the app was in an unexpected state (likely on the Issue29131 page from a previous partial run due to Fast Deploy state preservation). After retry, the same failure occurred.
Assessment
Gate failure is classified as environment blocker (retried once, same result). The fix code itself was not validated by automated tests, but code analysis reveals it is structurally reasonable.
Code Quality Issues Found (from static analysis)
- Listener ordering bug: In the adapter update path (
UpdateAdapter→UpdateItemsUpdatingScrollModeat line 363 →AddOrUpdateScrollListenerat line 366),ClearOnScrollListeners()removes the ScrollHelper listener added byAddScrollListener(). This could cause KeepScrollOffset to silently fail when the adapter is refreshed. - Naming convention:
isFirstItemReachedshould be_isFirstItemReachedper codebase style - Missing EOF newline: Both
Issue29131.csfiles lack newline at end of file - Existing test changed:
CollectionViewUITests.KeepScrollOffsettest assertion changed from"photo.jpg, 9"(1 item) to"FlowerBuds.jpg, 12"(5 items) without clear documentation of why the expected behavior changed
🔧 Fix — Analysis & Comparison
📝 Review Session — Updated-Test · f951e4d
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #29255 | Move listener management to UpdateItemsUpdatingScrollMode(); add isFirstItemReached flag to gate scroll correction |
MauiRecyclerView.cs, ScrollHelper.cs |
NavigateToIssue infrastructure failure |
Exhausted: No - skipped due to environment blocker (NavigateToIssue failure prevents test validation; retried once per rules)
Selected Fix: PR's fix (only candidate; Gate blocked by environment, not fix code)
Environment Blocker
try-fix phase was skipped per SHARED-RULES: "If try-fix cannot run due to environment issues after one retry, skip remaining try-fix models and proceed to Report." The Gate environment blocker (NavigateToIssue timeout) prevents meaningful try-fix validation as well.
📋 Report — Final Recommendation
📝 Review Session — Updated-Test · f951e4d
⚠️ Final Recommendation: REQUEST CHANGES
Summary
PR #29255 fixes KeepScrollOffset mode in Android CollectionView where scroll position jumps when items are inserted at the top. The fix approach is reasonable but has one definite bug in the listener lifecycle that needs addressing before merge.
Gate testing was inconclusive due to an environment blocker (NavigateToIssue navigation timeout after a prior failed test run); the fix code itself was not empirically validated.
Root Cause Analysis
When items are inserted at the top of a RecyclerView using KeepScrollOffset mode:
- If scrolled to middle/end: Android's
LinearLayoutManagernaturally adjusts the scroll offset to maintain visual position. No correction needed. - If at offset (0,0): RecyclerView may or may not auto-adjust; the
ScrollBy(-delta)correction handles this case.
The original code applied ScrollBy(-delta) unconditionally, which interfered with RecyclerView's natural adjustment when scrolled down. The PR correctly gates the correction to isFirstItemReached (offset == 0).
Fix Quality Assessment
✅ Correct approach overall: The isFirstItemReached conditional logic is reasonable. When not at the top, RecyclerView's natural behavior maintains position; when at the top, manual correction is needed.
❌ Critical ordering bug in UpdateItemsSource():
public virtual void UpdateItemsSource()
{
// ...
UpdateItemsUpdatingScrollMode(); // line 363: adds ScrollHelper listener if KeepScrollOffset
UpdateEmptyView();
AddOrUpdateScrollListener(); // line 366: ClearOnScrollListeners() removes ALL listeners!
// ...
}AddOrUpdateScrollListener() calls ClearOnScrollListeners() which removes all registered listeners, including the ScrollHelper listener just added by UpdateItemsUpdatingScrollMode(). After UpdateItemsSource() runs, ScrollHelper._maintainingScrollOffsets = true but the listener is NOT actually registered. This means KeepScrollOffset silently stops working whenever ItemsSource is changed while the mode is active.
Fix: Either call UpdateItemsUpdatingScrollMode() AFTER AddOrUpdateScrollListener(), or call ScrollHelper.AddScrollListener() again after ClearOnScrollListeners():
AddOrUpdateScrollListener(); // call first (clears then adds RecyclerViewScrollListener)
UpdateItemsUpdatingScrollMode(); // call after (won't be wiped by ClearOnScrollListeners)Code Quality Issues
| Issue | Severity | Details |
|---|---|---|
Listener ordering bug in UpdateItemsSource |
Critical | ClearOnScrollListeners() wipes ScrollHelper listener added by UpdateItemsUpdatingScrollMode() |
isFirstItemReached naming |
Minor | Should be _isAtTopOffset or _isScrolledToTop per convention (_ prefix, meaningful name) |
| Indentation on line 219-220 | Minor | isFirstItemReached = newXOffset == 0 / && newYOffset == 0 - && should be indented consistently |
| Missing newline at end of files | Minor | Both Issue29131.cs files (HostApp and TestCases) lack newline at EOF |
| Existing test semantics change | Needs clarification | KeepScrollOffset test changed from "add 1 item → expect photo.jpg, 9" to "add 5 items → expect FlowerBuds.jpg, 12". Why was the expected element changed? |
Test Analysis
- New test (
Issue29131.cs): Well-structured, covers alternating scroll-to-end/start + insert scenarios. Could not be validated due to Gate infrastructure issue. - Existing test modification: The
KeepScrollOffsettest inCollectionViewUITestswas changed. The old expected behavior ("photo.jpg, 9" after 1 insert) was apparently wrong; the new behavior ("FlowerBuds.jpg, 12" after 5 inserts) should be documented.
Title & Description
- Title: Minor style issue:
[Android] -should be[Android](extra dash unnecessary) - Description: Good structure; could clarify the
isFirstItemReachedlogic and why it's the correct approach
Requested Changes
- [Required] Fix listener ordering in
UpdateItemsSource()- moveUpdateItemsUpdatingScrollMode()call to AFTERAddOrUpdateScrollListener()(or re-add ScrollHelper listener after clearing) - [Required] Add comment in
TrackOffsets()explaining WHY correction is conditional onisFirstItemReached(it's a non-obvious design decision) - [Recommended] Rename
isFirstItemReachedto_isAtTopOffsetfor clarity and consistency - [Recommended] Add newline at end of both
Issue29131.csfiles - [Optional] Update PR title to remove extra dash:
[Android] Fix KeepScrollOffset Behavior...
📋 Expand PR Finalization Review
Title: ✅ Good
Current: [Android] - Fix KeepScrollOffset Behavior During Dynamic Item Additions in CollectionView
Description: ✅ Good
- Extra dash after
[Android]is non-standard (convention is[Android] Fix...not[Android] - Fix...) - "Dynamic Item Additions" is somewhat verbose
✨ 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!
Root Cause
When ItemsUpdatingScrollMode is set to KeepScrollOffset, the ScrollHelper previously registered its scroll listener lazily inside UndoNextScrollAdjustment() — only when the first adjustment was about to be undone. This lazy registration caused a race condition: the listener would miss the initial scroll events that captured the pre-insertion offsets, leading to incorrect delta values and unintended upward scrolling after items were inserted at the top of the CollectionView.
Additionally, TrackOffsets() applied the scroll correction unconditionally, which caused spurious corrections when the view was already at a scroll offset of zero.
Description of Change
Two changes in ScrollHelper.cs:
-
Moved scroll listener management to
UpdateItemsUpdatingScrollMode— AddedAddScrollListener()andRemoveScrollListener()methods toScrollHelperand call them fromMauiRecyclerView.UpdateItemsUpdatingScrollMode(). The listener is now registered eagerly whenKeepScrollOffsetmode is set (and unregistered when switching away from it), rather than lazily on the first adjustment. -
Guard scroll correction with position check — Added an
_isFirstItemReachedflag that tracks whether the scroll position has been at offset (0, 0). InTrackOffsets(), theScrollBy(-dx, -dy)correction is only applied when_isFirstItemReachedistrue(scroll position was at the start of the list), preventing spurious upward scrolling when the view is positioned in the middle or end of the list.
Also removed the listener registration code that was previously inside UndoNextScrollAdjustment() since it is now handled upstream.
Files changed:
src/Controls/src/Core/Handlers/Items/Android/ScrollHelper.cs— core fixsrc/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs— callsAddScrollListener/RemoveScrollListenerin mode updatesrc/Controls/tests/TestCases.HostApp/Issues/Issue29131.cs— new UI test pagesrc/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29131.cs— new NUnit UI testsrc/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CollectionViewUITests.CollectionViewItemsUpdatingScrollMode.cs— updated existingKeepScrollOffsettest to add 5 items (was 1) to better validate the fix
Issues Fixed
Fixes #29131
Platforms Tested
- Android
- iOS (Android-only fix, no behavior change)
- Windows (Android-only fix, no behavior change)
- Mac (Android-only fix, no behavior change)
Code Review: ✅ Passed
Code Review — PR #29255
🟡 Suggestions
1. Naming Convention: isFirstItemReached doesn't follow class conventions
File: src/Controls/src/Core/Handlers/Items/Android/ScrollHelper.cs
Problem: All other private fields in ScrollHelper use underscore prefix and camelCase:
bool _undoNextScrollAdjustment;
bool _maintainingScrollOffsets;
int _lastScrollX;
int _lastScrollY;
int _lastDeltaX;
int _lastDeltaY;The new field breaks this convention:
bool isFirstItemReached = true; // ❌ missing _ prefix, no access modifierRecommendation:
bool _isFirstItemReached = true; // ✅2. Misleading Variable Name: isFirstItemReached
File: src/Controls/src/Core/Handlers/Items/Android/ScrollHelper.cs
Problem: The name isFirstItemReached implies "the user scrolled to see the first item in the collection." However, its actual meaning is "the scroll offset is currently at (0, 0)" — the very beginning of the scrollable content. These are related but not identical concepts (the scroll could be at position 0 without the user intentionally "reaching" the first item).
A more descriptive name would clarify the intent:
bool _isAtScrollOrigin = true; // ✅ clearer — tracks whether offset is (0,0)Or if the intent is specifically to guard against repeated corrections:
bool _scrollOffsetResetSeen = true; // ✅ more explicit about the guard purposeThis matters for future maintainers (and agents) trying to understand when the scroll correction applies.
3. Modified existing KeepScrollOffset test changed significantly without explanation
File: src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CollectionViewUITests.CollectionViewItemsUpdatingScrollMode.cs
Problem: The existing KeepScrollOffset test was changed from clicking AddItemAbove once to five times, and the expected visible element changed from "photo.jpg, 9" to "FlowerBuds.jpg, 12":
// Before:
App.Click("AddItemAbove");
App.WaitForElement("photo.jpg, 9");
// After:
for (int i = 0; i < 5; i++)
{
App.Click("AddItemAbove");
}
App.WaitForElement("FlowerBuds.jpg, 12");Questions raised:
- Why does 5 additions instead of 1 better validate the fix?
- Did the old expected element (
"photo.jpg, 9") change because the fix altered what should be visible after one insertion? - Was the previous assertion wrong, or does the new behavior under the fix naturally land on
"FlowerBuds.jpg, 12"after 5 insertions?
Recommendation: Add a comment explaining why 5 iterations and why the expected element changed. If the old assertion ("photo.jpg, 9") was previously failing due to this bug, that's worth documenting.
4. Missing newline at end of file
Files:
src/Controls/tests/TestCases.HostApp/Issues/Issue29131.cssrc/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29131.cs
Both files are missing a trailing newline (the diff shows \ No newline at end of file). This is a minor style issue but inconsistent with the rest of the codebase.
Recommendation: Add a newline at the end of both files.
✅ Looks Good
-
Listener lifecycle management — Moving
AddScrollListener/RemoveScrollListenertoUpdateItemsUpdatingScrollModeis architecturally correct: listener registration should be tied to mode changes, not to the first scroll event. The old lazy approach inUndoNextScrollAdjustmentwas fragile. -
Guard idiom in
AddScrollListener/RemoveScrollListener— Theif (!_maintainingScrollOffsets)/if (_maintainingScrollOffsets)guards prevent double-registration and double-removal. Correct. -
New test file
Issue29131.cs— The HostApp page and NUnit test are well-structured. UsesTestContentPagebase class, properAutomationIds,[Category(UITestCategories.CollectionView)], and the test exercises the KeepScrollOffset behavior from multiple scroll positions. -
Platform tagging — The HostApp
[Issue]attribute correctly marksPlatformAffected.Android, matching the Android-only fix.
kubaflo
left a comment
There was a problem hiding this comment.
Looks like the test is failing
f951e4d to
4f664ee
Compare
There was a problem hiding this comment.
Pull request overview
Adds coverage and an Android handler change intended to address ItemsUpdatingScrollMode.KeepScrollOffset behavior when items are inserted into a CollectionView, plus updates an existing CollectionView scroll-mode UI test.
Changes:
- Add new HostApp issue page + UITest for GitHub issue 29131 (KeepScrollOffset + inserting items).
- Update existing
CollectionViewItemsUpdatingScrollModeUITest assertion logic. - Modify Android
ScrollHelper/MauiRecyclerViewto manage scroll-offset tracking via a scroll listener.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29131.cs | New Appium UITest for issue 29131 scenario. |
| src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/CollectionViewUITests.CollectionViewItemsUpdatingScrollMode.cs | Adjusts KeepScrollOffset test to perform multiple insertions + new expected element. |
| src/Controls/tests/TestCases.HostApp/Issues/Issue29131.cs | New HostApp repro page for issue 29131 (CollectionView + buttons to scroll/insert). |
| src/Controls/src/Core/Handlers/Items/Android/ScrollHelper.cs | Changes KeepScrollOffset offset-tracking logic; adds explicit add/remove listener helpers. |
| src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs | Hooks ScrollHelper listener lifecycle to ItemsUpdatingScrollMode changes. |
Comments suppressed due to low confidence (1)
src/Controls/src/Core/Handlers/Items/Android/ScrollHelper.cs:20
isFirstItemReachedis initialized to true and only updated when_undoNextScrollAdjustmentis false. That means the first item update after enabling KeepScrollOffset may incorrectly run the undo path even when the user is not at offset 0, because the flag hasn't been computed from the current scroll offsets yet. Consider initializing this flag based on the current computed offsets when starting tracking (or removing it and directly checking offsets).
bool _isAtScrollOrigin = true;
int _lastScrollX;
int _lastScrollY;
int _lastDeltaX;
int _lastDeltaY;
| if (ItemsView.ItemsUpdatingScrollMode == ItemsUpdatingScrollMode.KeepScrollOffset) | ||
| { | ||
| ScrollHelper.AddScrollListener(); | ||
| } | ||
| else | ||
| { | ||
| ScrollHelper.RemoveScrollListener(); | ||
| } |
There was a problem hiding this comment.
Addressed the concern
| { | ||
| public Issue29131(TestDevice device) : base(device) { } | ||
|
|
||
| public override string Issue => "Android - KeepScrollOffset doesn't not works as expected when new items are added in CollectionView"; |
There was a problem hiding this comment.
Addressed the concern
|
|
||
| [Test] | ||
| [Category(UITestCategories.CollectionView)] | ||
| public void KeepScrollOffSetShouldWork() |
There was a problem hiding this comment.
Addressed the concern
|
|
||
| namespace Controls.TestCases.HostApp.Issues; | ||
|
|
||
| [Issue(IssueTracker.Github, 29131, "Android - KeepScrollOffset doesn't not works as expected when new items are added in CollectionView", PlatformAffected.Android)] |
There was a problem hiding this comment.
Addressed the concern
@kubaflo, I ensured the test passed locally and also addressed the valid AI concerns. |
kubaflo
left a comment
There was a problem hiding this comment.
Could you please review the AI's summary?
|
/review -b feature/refactor-copilot-yml |
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 2 findings
See inline comments for details.
|
|
||
| _undoNextScrollAdjustment = false; | ||
| _recyclerView.ScrollBy(-_lastDeltaX, -_lastDeltaY); | ||
| if (_isAtScrollOrigin) |
There was a problem hiding this comment.
[major] Android CollectionView — KeepScrollOffset semantics — This gates the offset correction on _isAtScrollOrigin, so once the user is scrolled away from offset 0, inserts before the viewport are allowed to keep RecyclerView's automatic shifted offset instead of restoring the previous absolute pixel offset. Concrete scenario: set ItemsUpdatingScrollMode=KeepScrollOffset, scroll down, insert an item at index 0; RecyclerView increases ComputeVerticalScrollOffset() to keep the old item visible, but this branch skips ScrollBy(-_lastDeltaY), so the absolute scroll offset is not preserved. KeepScrollOffset should undo the adapter-induced delta regardless of whether the current offset is origin.
|
|
||
| if (ItemsView.ItemsUpdatingScrollMode == ItemsUpdatingScrollMode.KeepScrollOffset) | ||
| { | ||
| ScrollHelper.AddScrollListener(); |
There was a problem hiding this comment.
[major] Android CollectionView — scroll listener lifecycle — ScrollHelper.AddScrollListener() can no-op after the helper has already been removed by AddOrUpdateScrollListener()/RemoveScrollListener(), because those methods call ClearOnScrollListeners() but do not reset ScrollHelper's _maintainingScrollOffsets flag. Concrete scenario: with KeepScrollOffset active, UpdateItemsSource() calls AddOrUpdateScrollListener(), clearing all listeners including ScrollHelper; then this line calls AddScrollListener(), but _maintainingScrollOffsets is still true so the helper is not re-registered and future collection updates are not tracked.
This comment has been minimized.
This comment has been minimized.
|
/review -b feature/enhanced-reviewer -p android |
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 3 findings
See inline comments for details.
|
|
||
| _undoNextScrollAdjustment = false; | ||
| _recyclerView.ScrollBy(-_lastDeltaX, -_lastDeltaY); | ||
| if (_isAtScrollOrigin) |
There was a problem hiding this comment.
[major] CollectionView Android — Gating the offset correction on _isAtScrollOrigin changes KeepScrollOffset semantics when the user is already scrolled away from zero. In that scenario, inserting items above lets RecyclerView keep the same visible item anchored instead of restoring the absolute scroll offset, which matches KeepItemsInView more than the documented KeepScrollOffset behavior.
|
|
||
| if (ItemsView.ItemsUpdatingScrollMode == ItemsUpdatingScrollMode.KeepScrollOffset) | ||
| { | ||
| ScrollHelper.AddScrollListener(); |
There was a problem hiding this comment.
[major] Handler lifecycle — ScrollHelper.AddScrollListener() can no-op after AddOrUpdateScrollListener() calls ClearOnScrollListeners(). ClearOnScrollListeners() removes the helper from RecyclerView, but _maintainingScrollOffsets remains true, so changing/replacing the ItemsSource while in KeepScrollOffset mode can leave the helper unregistered and future offset tracking disabled.
| { | ||
| int index = (count % 2 == 0) ? 0 : items.Count - 1; | ||
| var position = (count % 2 == 0) ? ScrollToPosition.Start : ScrollToPosition.End; | ||
| collectionView.ScrollTo(index, position: position, animate: true); |
There was a problem hiding this comment.
[moderate] UI test reliability — The page starts an animated ScrollTo and the test immediately inserts an item. On slower Android devices the insert can race the still-running scroll animation, making the test assert the animation timing rather than the CollectionView update behavior. Prefer a non-animated scroll or a deterministic wait for the target item before enabling insertion.
MauiBot
left a comment
There was a problem hiding this comment.
AI Review Summary
@prakashKannanSf3972 — new AI review results are available based on this last commit:
f1750ff.
Address AI summary 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: e904e900
| Test | Without Fix (expect FAIL) | With Fix (expect PASS) |
|---|---|---|
🖥️ CollectionViewItemsUpdatingScrollModeUITests CollectionViewItemsUpdatingScrollModeUITests |
✅ FAIL — 1297s | ✅ PASS — 547s |
🖥️ Issue29131 Issue29131 |
✅ FAIL — 683s | ✅ PASS — 501s |
🔴 Without fix — 🖥️ CollectionViewItemsUpdatingScrollModeUITests: FAIL ✅ · 1297s
(truncated to last 15,000 chars)
ntPtr*)
Standard Error Messages:
>>>>> 06/06/2026 20:45:03 The FixtureSetup threw an exception. Attempt 0/1.
Exception details: System.TimeoutException: CollectionView ItemsUpdatingScrollMode
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._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTestState() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 25
at Microsoft.Maui.TestCases.Tests.UITest.FixtureSetup() in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 576
>>>>> 06/06/2026 20:45:21 The FixtureSetup threw an exception. Attempt 1/1.
Exception details: System.TimeoutException: CollectionView ItemsUpdatingScrollMode
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._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTestState() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 25
at Microsoft.Maui.TestCases.Tests.UITest.FixtureSetup() in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 576
>>>>> 06/06/2026 20:45:37 The FixtureSetup threw an exception. Attempt 0/1.
Exception details: System.TimeoutException: Support for KeepLastItemInView for CV2
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._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTestState() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 25
at Microsoft.Maui.TestCases.Tests.UITest.FixtureSetup() in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 576
>>>>> 06/06/2026 20:45:39 FixtureSetup for Issue28716(Android)
>>>>> 06/06/2026 20:45:55 The FixtureSetup threw an exception. Attempt 1/1.
Exception details: System.TimeoutException: Support for KeepLastItemInView for CV2
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._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTestState() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 25
at Microsoft.Maui.TestCases.Tests.UITest.FixtureSetup() in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 576
>>>>> 06/06/2026 20:45:55 Log types: logcat, bugreport, server
>>>>> 06/06/2026 20:45:55 Log types: logcat, bugreport, server
Setup failed for test fixture Microsoft.Maui.TestCases.Tests.Issues.Issue28716(Android)
System.TimeoutException : Support for KeepLastItemInView for CV2
StackTrace: 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._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTestState() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 25
at Microsoft.Maui.TestCases.Tests.UITest.FixtureSetup() in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 576
at UITest.Appium.NUnit.UITestBase.OneTimeSetup() in /_/src/TestUtils/src/UITest.NUnit/UITestBase.cs:line 221
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
>>>>> 06/06/2026 20:45:56 FixtureSetup for Issue29131(Android)
Failed KeepLastItemInViewShouldWork [33 s]
Error Message:
OneTimeSetUp: System.TimeoutException : Support for KeepLastItemInView for CV2
Stack Trace:
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._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTestState() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 25
at Microsoft.Maui.TestCases.Tests.UITest.FixtureSetup() in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 576
at UITest.Appium.NUnit.UITestBase.OneTimeSetup() in /_/src/TestUtils/src/UITest.NUnit/UITestBase.cs:line 221
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
>>>>> 06/06/2026 20:46:12 The FixtureSetup threw an exception. Attempt 0/1.
Exception details: System.TimeoutException: Android - KeepScrollOffset does not work as expected when new items are added in CollectionView
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._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTestState() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 25
at Microsoft.Maui.TestCases.Tests.UITest.FixtureSetup() in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 576
>>>>> 06/06/2026 20:46:14 FixtureSetup for Issue29131(Android)
>>>>> 06/06/2026 20:46:30 The FixtureSetup threw an exception. Attempt 1/1.
Exception details: System.TimeoutException: Android - KeepScrollOffset does not work as expected when new items are added in CollectionView
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._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTestState() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 25
at Microsoft.Maui.TestCases.Tests.UITest.FixtureSetup() in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 576
>>>>> 06/06/2026 20:46:30 Log types: logcat, bugreport, server
>>>>> 06/06/2026 20:46:30 Log types: logcat, bugreport, server
Failed KeepScrollOffsetShouldWork [34 s]
Error Message:
OneTimeSetUp: System.TimeoutException : Android - KeepScrollOffset does not work as expected when new items are added in CollectionView
Stack Trace:
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._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTestState() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 25
at Microsoft.Maui.TestCases.Tests.UITest.FixtureSetup() in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 576
at UITest.Appium.NUnit.UITestBase.OneTimeSetup() in /_/src/TestUtils/src/UITest.NUnit/UITestBase.cs:line 221
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
Setup failed for test fixture Microsoft.Maui.TestCases.Tests.Issues.Issue29131(Android)
System.TimeoutException : Android - KeepScrollOffset does not work as expected when new items are added in CollectionView
StackTrace: 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._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTestState() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 25
at Microsoft.Maui.TestCases.Tests.UITest.FixtureSetup() in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 576
at UITest.Appium.NUnit.UITestBase.OneTimeSetup() in /_/src/TestUtils/src/UITest.NUnit/UITestBase.cs:line 221
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
NUnit Adapter 4.5.0.0: Test execution complete
Results File: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/retry-CollectionViewItemsUpdatingScrollModeUITests.trx
Test Run Failed.
Total tests: 17
Failed: 17
Total time: 6.7606 Minutes
🟢 With fix — 🖥️ CollectionViewItemsUpdatingScrollModeUITests: PASS ✅ · 547s
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
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.80-ci+azdo.14304363
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
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.80-ci+azdo.14304363
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.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
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.80-ci+azdo.14304363
Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
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.80-ci+azdo.14304363
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
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
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:06:34.33
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.80-ci+azdo.14304363
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.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.80-ci+azdo.14304363
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.
[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.12] Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.34] Discovered: Controls.TestCases.Android.Tests
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 3 of 3 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 06/06/2026 21:05:59 KeepItemsInView Start
>>>>> 06/06/2026 21:05:59 FixtureSetup for KeepItemsInView
>>>>> 06/06/2026 21:06:25 KeepItemsInView Stop
Passed KeepItemsInView [27 s]
>>>>> 06/06/2026 21:06:27 KeepLastItemInView Start
>>>>> 06/06/2026 21:06:27 FixtureSetup for KeepLastItemInView
>>>>> 06/06/2026 21:06:41 KeepLastItemInView Stop
Passed KeepLastItemInView [16 s]
>>>>> 06/06/2026 21:06:43 KeepScrollOffset Start
>>>>> 06/06/2026 21:06:43 FixtureSetup for KeepScrollOffset
>>>>> 06/06/2026 21:06:59 KeepScrollOffset Stop
Passed KeepScrollOffset [17 s]
NUnit Adapter 4.5.0.0: Test execution complete
Results File: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/CollectionViewItemsUpdatingScrollModeUITests.trx
Test Run Successful.
Total tests: 3
Passed: 3
Total time: 1.1769 Minutes
>>> TRX_RESULT_FILE: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/CollectionViewItemsUpdatingScrollModeUITests.trx
🔴 Without fix — 🖥️ Issue29131: FAIL ✅ · 683s
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
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.80-ci+azdo.14304363
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
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.80-ci+azdo.14304363
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
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
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
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.80-ci+azdo.14304363
Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
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.80-ci+azdo.14304363
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
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:09:02.46
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.80-ci+azdo.14304363
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.80-ci+azdo.14304363
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
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.80-ci+azdo.14304363
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
UITest.Appium -> /home/vsts/work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.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.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.
[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.12] Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.34] Discovered: Controls.TestCases.Android.Tests
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/06/2026 20:57:28 FixtureSetup for Issue29131(Android)
>>>>> 06/06/2026 20:57:29 KeepScrollOffsetShouldWork Start
>>>>> 06/06/2026 20:57:51 KeepScrollOffsetShouldWork Stop
>>>>> 06/06/2026 20:57:51 Log types: logcat, bugreport, server
Failed KeepScrollOffsetShouldWork [22 s]
Error Message:
System.TimeoutException : Timed out waiting for element...
Stack Trace:
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.Issues.Issue29131.KeepScrollOffsetShouldWork() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29131.cs:line 23
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
NUnit Adapter 4.5.0.0: Test execution complete
Results File: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue29131.trx
Total tests: 1
Failed: 1
Test Run Failed.
Total time: 37.4899 Seconds
>>> TRX_RESULT_FILE: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue29131.trx
🟢 With fix — 🖥️ Issue29131: PASS ✅ · 501s
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
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.80-ci+azdo.14304363
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
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.80-ci+azdo.14304363
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.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.80-ci+azdo.14304363
Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.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.80-ci+azdo.14304363
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.80-ci+azdo.14304363
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:06:36.61
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.80-ci+azdo.14304363
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14304363
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.80-ci+azdo.14304363
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
UITest.Appium -> /home/vsts/work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
UITest.NUnit -> /home/vsts/work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
VisualTestUtils -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
VisualTestUtils.MagickNet -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.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.
[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.18] Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.38] Discovered: Controls.TestCases.Android.Tests
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/06/2026 21:15:09 FixtureSetup for Issue29131(Android)
>>>>> 06/06/2026 21:15:10 KeepScrollOffsetShouldWork Start
>>>>> 06/06/2026 21:15:21 KeepScrollOffsetShouldWork Stop
Passed KeepScrollOffsetShouldWork [11 s]
NUnit Adapter 4.5.0.0: Test execution complete
Results File: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue29131.trx
Test Run Successful.
Total tests: 1
Passed: 1
Total time: 22.8361 Seconds
>>> TRX_RESULT_FILE: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue29131.trx
📁 Fix files reverted (3 files)
eng/pipelines/ci-copilot.ymlsrc/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cssrc/Controls/src/Core/Handlers/Items/Android/ScrollHelper.cs
UI Tests — CollectionView
Detected UI test categories: CollectionView
Pre-Flight — Context & Validation
Issue: #29131 - [Android] KeepItemsInView and KeepScrollOffset doesn't not works as expected when new items are added in CollectionView
PR: #29255 - [Android] - Fix KeepScrollOffset Behavior During Dynamic Item Additions in CollectionView
Platforms Affected: Android
Files Changed: 2 implementation, 3 test
Key Findings
- Issue #29131 reports Android CollectionView
ItemsUpdatingScrollMode.KeepScrollOffsetfailing when new items are inserted at the top. - PR #29255 fixes the observed gate scenario by registering
ScrollHelperearly and only undoing RecyclerView offset shifts when the view is at origin. - Code review found the PR likely changes documented
KeepScrollOffsetsemantics and has a listener lifecycle risk whenClearOnScrollListeners()removesScrollHelper. - GitHub CLI authentication was unavailable, so PR/issue context was gathered via unauthenticated GitHub API reads and local branch diff.
Code Review Summary
Verdict: NEEDS_CHANGES
Confidence: high
Errors: 2 | Warnings: 1 | Suggestions: 0
Key code review findings:
- ❌
src/Controls/src/Core/Handlers/Items/Android/ScrollHelper.cs:208- origin-only correction changesKeepScrollOffsettowardKeepItemsInViewbehavior. - ❌
src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs:386-ClearOnScrollListeners()can removeScrollHelperwhile_maintainingScrollOffsetsremains true. ⚠️ src/Controls/tests/TestCases.HostApp/Issues/Issue29131.cs:77- animatedScrollTocan race immediate item insertion in the UI test.
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #29255 | Persistent ScrollHelper listener plus origin-only offset undo |
✅ PASSED (Gate) | MauiRecyclerView.cs, ScrollHelper.cs, UI tests |
Original PR; gate result supplied by caller |
Code Review — Deep Analysis
Code Review — PR #29255
Independent Assessment
What this changes: Android CollectionView KeepScrollOffset now eagerly registers ScrollHelper as a RecyclerView scroll listener, tracks whether the view is at scroll origin, and only applies the compensating ScrollBy(-delta) when at origin. It also adds/updates UI tests for item insertion while scrolling.
Inferred motivation: Prevent Android CollectionView from jumping unexpectedly when items are inserted at the top.
Reconciliation with PR Narrative
Author claims: KeepScrollOffset should preserve scroll behavior during dynamic insertions by adjusting offsets only when the first item is reached.
Agreement/disagreement: The listener-management motivation matches the code. However, the implementation conflicts with the documented KeepScrollOffset contract: ItemsUpdatingScrollMode.KeepScrollOffset says the absolute scroll position remains fixed and different items may become visible.
Findings
❌ Error — KeepScrollOffset semantics are changed to "keep visible items"
src/Controls/src/Core/Handlers/Items/Android/ScrollHelper.cs:208
Gating the correction on _isAtScrollOrigin means that when the user is scrolled away from offset 0, RecyclerView's anchor adjustment is left in place. That keeps the same visible item stable, which is closer to KeepItemsInView, not KeepScrollOffset. The public API docs state KeepScrollOffset maintains the absolute scroll position.
❌ Error — ScrollHelper listener state can desynchronize after ClearOnScrollListeners
src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs:386
AddOrUpdateScrollListener() calls ClearOnScrollListeners(), which removes ScrollHelper too, but does not reset _maintainingScrollOffsets. A later ScrollHelper.AddScrollListener() can no-op while the native listener is no longer registered.
⚠️ Warning — New UI test can race animated scrolling
src/Controls/tests/TestCases.HostApp/Issues/Issue29131.cs:77
The test page uses ScrollTo(..., animate: true), but the test immediately inserts items after clicking the scroll button. On slower Android devices, insertion may happen before scrolling completes. Prefer non-animated scrolling or wait for a deterministic sentinel before insertion.
Devil's Advocate
The PR may match the issue author's desired visual behavior, but that behavior appears to contradict the enum's documented absolute-offset semantics. Existing bot comments already identified related listener/semantic concerns, but they still apply to the current diff.
Verdict: NEEDS_CHANGES
Confidence: high
Summary: The PR has correctness issues in both behavior and listener lifecycle. CI was not assessed via authenticated gh because this environment lacks GitHub CLI authentication.
Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix | Post-layout absolute-offset restoration with RecyclerView.Post |
2 files | Build/deploy succeeded; UI test execution did not complete before stop | |
| 2 | try-fix | Pre-layout adapter metadata anchor with ScrollToPositionWithOffset |
❌ Fail | 3 files | KeepScrollOffset and KeepScrollOffsetShouldWork timed out |
| 3 | try-fix | Lazy one-shot ScrollHelper listener and targeted listener removal |
❌ Fail | 2 files | Corrected compile error, then KeepScrollOffset tests timed out |
| 4 | try-fix | Persistent listener with unconditional offset restoration and targeted listener removal | ❌ Fail | 2 files | Addresses review concerns but fails current KeepScrollOffset expectations |
| PR | PR #29255 | Persistent listener plus origin-only offset undo | ✅ PASSED (Gate) | 2 implementation files + tests | Original PR; supplied gate result says tests fail without fix and pass with fix |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| maui-expert-reviewer | 1 | Yes | Candidate 1: post-layout absolute-offset restoration |
| maui-expert-reviewer | 2 | Yes | Candidate 2: adapter metadata/pre-layout anchoring after Candidate 1 was blocked |
| maui-expert-reviewer | 3 | Yes | Candidate 3: lazy one-shot listener after Candidate 2 failed |
| maui-expert-reviewer | 4 | Yes | Candidate 4: persistent listener with semantic/lifecycle fixes after Candidate 3 failed |
Exhausted: Yes
Selected Fix: PR's fix — It is the only fix with a passing gate result. No alternative candidate both passed the Android UI tests and was demonstrably better than the PR's current implementation.
Report — Final Recommendation
Comparative Candidate Report — PR #29255
Ranking
| Rank | Candidate | Regression result | Assessment |
|---|---|---|---|
| 1 | pr |
✅ Passed gate | Only candidate with confirmed Android gate success. It fixes the observed Issue29131 scenario but retains major review concerns around documented KeepScrollOffset semantics and listener lifecycle. |
| 2 | try-fix-1 |
Post-layout absolute-offset restoration is conceptually promising and avoids the PR's origin gate, but the Android UI test run did not complete, so it cannot outrank a passing candidate. | |
| 3 | pr-plus-reviewer |
❌ Failed by equivalence to try-fix-4 implementation-side changes |
Applies all expert feedback, including unconditional offset restoration and targeted listener removal. Those implementation changes match try-fix-4, which failed the Android KeepScrollOffset regression tests, so this candidate must rank below passing or non-failing candidates. |
| 4 | try-fix-4 |
❌ Failed | Persistent early listener plus semantic/lifecycle fixes addressed the expert concerns, but KeepScrollOffset and KeepScrollOffsetShouldWork failed and failed again on retry. |
| 5 | try-fix-2 |
❌ Failed | Pre-layout adapter metadata anchoring passed unrelated modes but failed both KeepScrollOffset tests; anchoring by numeric adapter position was insufficient. |
| 6 | try-fix-3 |
❌ Failed | Lazy one-shot listener plus targeted removal was too narrow; the helper was not registered early/long enough to observe RecyclerView's offset delta. |
Candidate comparison
pr: Best empirical result. The supplied gate says tests fail without the PR and pass with it on Android. The downside is real: the expert reviewer found that origin-only correction changes the documented absolute-offset semantics and that broad listener clearing can desynchronize ScrollHelper's native registration state.
pr-plus-reviewer: Best code-review posture but not best empirical posture. Applying all reviewer feedback necessarily includes the semantic/lifecycle changes tested by try-fix-4; that candidate failed the Android regression tests, so pr-plus-reviewer must be ranked lower than the raw PR despite addressing the review findings.
try-fix-1: Uses a different post-layout absolute-offset restoration strategy and may resolve the semantic concern without relying on a persistent listener. It was blocked rather than failed, but the requested ranking rule only guarantees failed regression candidates are below passed candidates; with no completed pass, it cannot beat pr.
try-fix-2: Adds adapter-change metadata and anchors before layout with ScrollToPositionWithOffset, but test evidence showed the viewport ended at the wrong content after insertions.
try-fix-3: Reduces listener lifetime and removes only owned listeners, but failed because the listener was active too late or too briefly for RecyclerView's update/layout scroll callbacks.
try-fix-4: Directly addresses the expert semantic and listener-lifecycle findings while keeping the PR's persistent early listener model, but failed both KeepScrollOffset regressions.
Winner
Winner: pr
The raw PR fix is the single winning candidate because it is the only candidate with confirmed passing Android gate results. This is not a clean merge recommendation: the expert findings should be surfaced as inline comments, but every candidate that applied those implementation fixes failed or lacked completed validation.
Future Action — review latest findings
No alternative fix was selected for this run. Review the session findings and CI results before merging.
|
@kubaflo a couple of those last MauiBot comments look like real regressions to me. Is there an intent to follow-up in separate PR(s)? Or should new issues be opened? |
|
@AdamEssenmacher here's the follow up PR: #27153 but you can open yours |
…ns in CollectionView (#29255) > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details `KeepScrollOffset` mode does not maintain scroll position when new items are inserted into the CollectionView, resulting in unintended upward scrolling. ### Root Cause When new items are added to the collection, the scroll offset is recalculated based on the updated layout. This results in incorrect offset adjustments, causing the scroll position to shift upward instead of being correctly preserved. ### Description of Change * Added `AddScrollListener` and `RemoveScrollListener` methods to manage scroll listeners dynamically. Adjusted the `TrackOffsets` method to ensure scroll offsets are only adjusted when the first item is reached, ensuring accurate scroll behavior and preventing unintended upward scrolling during dynamic item insertions. ### Issues Fixed Fixes #29131 * The Following [PR - 27153](#27153) fixes the `KeepItemsInView` issue on Android. **Tested the behaviour in the following platforms** - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Output | Before| After| |--|--| | <video src="https://github.com/user-attachments/assets/5d258709-c128-4cc4-9a73-bf3ad32b1cc3"> | <video src="https://github.com/user-attachments/assets/8b22544b-cbbd-4d97-a54a-4573ca0290d5"> | ---------
…#29255 (#35946) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details - Android CollectionView.ItemsUpdatingScrollMode = KeepScrollOffset stops working after ItemsSource is replaced. - After the reassignment, inserting a new item at index 0 leaves the previous top item anchored on screen and hides the new item above the viewport — effectively KeepItemsInView semantics instead of KeepScrollOffset. - Only Android is affected. iOS behaves correctly. ### Root Cause of the issue - MauiRecyclerView.RemoveScrollListener() calls ClearOnScrollListeners(), which removes all scroll listeners registered on the RecyclerView — including the one ScrollHelper self-registered. - ScrollHelper._maintainingScrollOffsets flag is never reset, so it goes stale (true while the listener is actually detached). - Subsequent ScrollHelper.AddScrollListener() calls short-circuit on the stale flag, so ScrollHelper is never re-attached. - With no scroll callbacks reaching ScrollHelper, TrackOffsets() never runs and the ScrollBy(-delta) correction that drives KeepScrollOffset is silently skipped. ### Before PR #29255 - ScrollHelper attached its listener lazily, inside UndoNextScrollAdjustment(), only when an insert actually needed it. - The stale-flag desync caused by ClearOnScrollListeners() was a latent bug, but the lazy re-register path inside UndoNextScrollAdjustment() masked it for the typical user — the listener got attached the next time it was needed, so most KeepScrollOffset scenarios still worked. ### After PR #29255 - Listener registration was moved out of UndoNextScrollAdjustment() and into an explicit ScrollHelper.AddScrollListener() method, called eagerly from MauiRecyclerView.UpdateItemsUpdatingScrollMode(). - Both AddScrollListener() and the old lazy block use the same _maintainingScrollOffsets guard, but the lazy self-heal block in UndoNextScrollAdjustment() was deleted. - Now, when ClearOnScrollListeners() detaches ScrollHelper's listener, the flag stays true, the eager AddScrollListener() no-ops, the lazy safety net is gone, and KeepScrollOffset is permanently broken after the first ItemsSource reassignment. ### Description of Change <!-- Enter description of the fix in this section --> **Bug fix:** * Fixed the scroll listener removal logic in `RemoveScrollListener()` by removing only the specific listener instead of clearing all listeners, which resolves the `KeepScrollOffset` issue when replacing the `ItemsSource` in `CollectionView` on Android. (`src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs`) **Test coverage:** * Added a new test case page that reproduces the issue and provides buttons to replace the `ItemsSource`, scroll to the top, and insert items at the top, making it easier to manually verify the fix. (`src/Controls/tests/TestCases.HostApp/Issues/Issue35806.cs`) * Introduced an automated UI test (Android only) that verifies the `KeepScrollOffset` behavior after replacing the `ItemsSource`, ensuring the regression is caught in the future. (`src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35806.cs`) ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes #35806 ### Tested the behavior in the following platforms - [ ] Windows - [x] Android - [ ] iOS - [ ] Mac ### Output | Before | After | |----------|----------| | <video src="https://github.com/user-attachments/assets/0b73f9e5-83e1-4e82-a382-47b22e56c797"> | <video src="https://github.com/user-attachments/assets/fc2c4648-4d77-4b55-a988-0a87c2c17e8a"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. --> --------- Co-authored-by: KarthikRajaKalaimani <92777139+KarthikRajaKalaimani@users.noreply.github.com>
…ns in CollectionView (#29255) > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details `KeepScrollOffset` mode does not maintain scroll position when new items are inserted into the CollectionView, resulting in unintended upward scrolling. ### Root Cause When new items are added to the collection, the scroll offset is recalculated based on the updated layout. This results in incorrect offset adjustments, causing the scroll position to shift upward instead of being correctly preserved. ### Description of Change * Added `AddScrollListener` and `RemoveScrollListener` methods to manage scroll listeners dynamically. Adjusted the `TrackOffsets` method to ensure scroll offsets are only adjusted when the first item is reached, ensuring accurate scroll behavior and preventing unintended upward scrolling during dynamic item insertions. ### Issues Fixed Fixes #29131 * The Following [PR - 27153](#27153) fixes the `KeepItemsInView` issue on Android. **Tested the behaviour in the following platforms** - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Output | Before| After| |--|--| | <video src="https://github.com/user-attachments/assets/5d258709-c128-4cc4-9a73-bf3ad32b1cc3"> | <video src="https://github.com/user-attachments/assets/8b22544b-cbbd-4d97-a54a-4573ca0290d5"> | ---------
…#29255 (#35946) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details - Android CollectionView.ItemsUpdatingScrollMode = KeepScrollOffset stops working after ItemsSource is replaced. - After the reassignment, inserting a new item at index 0 leaves the previous top item anchored on screen and hides the new item above the viewport — effectively KeepItemsInView semantics instead of KeepScrollOffset. - Only Android is affected. iOS behaves correctly. ### Root Cause of the issue - MauiRecyclerView.RemoveScrollListener() calls ClearOnScrollListeners(), which removes all scroll listeners registered on the RecyclerView — including the one ScrollHelper self-registered. - ScrollHelper._maintainingScrollOffsets flag is never reset, so it goes stale (true while the listener is actually detached). - Subsequent ScrollHelper.AddScrollListener() calls short-circuit on the stale flag, so ScrollHelper is never re-attached. - With no scroll callbacks reaching ScrollHelper, TrackOffsets() never runs and the ScrollBy(-delta) correction that drives KeepScrollOffset is silently skipped. ### Before PR #29255 - ScrollHelper attached its listener lazily, inside UndoNextScrollAdjustment(), only when an insert actually needed it. - The stale-flag desync caused by ClearOnScrollListeners() was a latent bug, but the lazy re-register path inside UndoNextScrollAdjustment() masked it for the typical user — the listener got attached the next time it was needed, so most KeepScrollOffset scenarios still worked. ### After PR #29255 - Listener registration was moved out of UndoNextScrollAdjustment() and into an explicit ScrollHelper.AddScrollListener() method, called eagerly from MauiRecyclerView.UpdateItemsUpdatingScrollMode(). - Both AddScrollListener() and the old lazy block use the same _maintainingScrollOffsets guard, but the lazy self-heal block in UndoNextScrollAdjustment() was deleted. - Now, when ClearOnScrollListeners() detaches ScrollHelper's listener, the flag stays true, the eager AddScrollListener() no-ops, the lazy safety net is gone, and KeepScrollOffset is permanently broken after the first ItemsSource reassignment. ### Description of Change <!-- Enter description of the fix in this section --> **Bug fix:** * Fixed the scroll listener removal logic in `RemoveScrollListener()` by removing only the specific listener instead of clearing all listeners, which resolves the `KeepScrollOffset` issue when replacing the `ItemsSource` in `CollectionView` on Android. (`src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs`) **Test coverage:** * Added a new test case page that reproduces the issue and provides buttons to replace the `ItemsSource`, scroll to the top, and insert items at the top, making it easier to manually verify the fix. (`src/Controls/tests/TestCases.HostApp/Issues/Issue35806.cs`) * Introduced an automated UI test (Android only) that verifies the `KeepScrollOffset` behavior after replacing the `ItemsSource`, ensuring the regression is caught in the future. (`src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35806.cs`) ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes #35806 ### Tested the behavior in the following platforms - [ ] Windows - [x] Android - [ ] iOS - [ ] Mac ### Output | Before | After | |----------|----------| | <video src="https://github.com/user-attachments/assets/0b73f9e5-83e1-4e82-a382-47b22e56c797"> | <video src="https://github.com/user-attachments/assets/fc2c4648-4d77-4b55-a988-0a87c2c17e8a"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. --> --------- Co-authored-by: KarthikRajaKalaimani <92777139+KarthikRajaKalaimani@users.noreply.github.com>
…ns in CollectionView (#29255) > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details `KeepScrollOffset` mode does not maintain scroll position when new items are inserted into the CollectionView, resulting in unintended upward scrolling. ### Root Cause When new items are added to the collection, the scroll offset is recalculated based on the updated layout. This results in incorrect offset adjustments, causing the scroll position to shift upward instead of being correctly preserved. ### Description of Change * Added `AddScrollListener` and `RemoveScrollListener` methods to manage scroll listeners dynamically. Adjusted the `TrackOffsets` method to ensure scroll offsets are only adjusted when the first item is reached, ensuring accurate scroll behavior and preventing unintended upward scrolling during dynamic item insertions. ### Issues Fixed Fixes #29131 * The Following [PR - 27153](#27153) fixes the `KeepItemsInView` issue on Android. **Tested the behaviour in the following platforms** - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Output | Before| After| |--|--| | <video src="https://github.com/user-attachments/assets/5d258709-c128-4cc4-9a73-bf3ad32b1cc3"> | <video src="https://github.com/user-attachments/assets/8b22544b-cbbd-4d97-a54a-4573ca0290d5"> | ---------
…#29255 (#35946) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details - Android CollectionView.ItemsUpdatingScrollMode = KeepScrollOffset stops working after ItemsSource is replaced. - After the reassignment, inserting a new item at index 0 leaves the previous top item anchored on screen and hides the new item above the viewport — effectively KeepItemsInView semantics instead of KeepScrollOffset. - Only Android is affected. iOS behaves correctly. ### Root Cause of the issue - MauiRecyclerView.RemoveScrollListener() calls ClearOnScrollListeners(), which removes all scroll listeners registered on the RecyclerView — including the one ScrollHelper self-registered. - ScrollHelper._maintainingScrollOffsets flag is never reset, so it goes stale (true while the listener is actually detached). - Subsequent ScrollHelper.AddScrollListener() calls short-circuit on the stale flag, so ScrollHelper is never re-attached. - With no scroll callbacks reaching ScrollHelper, TrackOffsets() never runs and the ScrollBy(-delta) correction that drives KeepScrollOffset is silently skipped. ### Before PR #29255 - ScrollHelper attached its listener lazily, inside UndoNextScrollAdjustment(), only when an insert actually needed it. - The stale-flag desync caused by ClearOnScrollListeners() was a latent bug, but the lazy re-register path inside UndoNextScrollAdjustment() masked it for the typical user — the listener got attached the next time it was needed, so most KeepScrollOffset scenarios still worked. ### After PR #29255 - Listener registration was moved out of UndoNextScrollAdjustment() and into an explicit ScrollHelper.AddScrollListener() method, called eagerly from MauiRecyclerView.UpdateItemsUpdatingScrollMode(). - Both AddScrollListener() and the old lazy block use the same _maintainingScrollOffsets guard, but the lazy self-heal block in UndoNextScrollAdjustment() was deleted. - Now, when ClearOnScrollListeners() detaches ScrollHelper's listener, the flag stays true, the eager AddScrollListener() no-ops, the lazy safety net is gone, and KeepScrollOffset is permanently broken after the first ItemsSource reassignment. ### Description of Change <!-- Enter description of the fix in this section --> **Bug fix:** * Fixed the scroll listener removal logic in `RemoveScrollListener()` by removing only the specific listener instead of clearing all listeners, which resolves the `KeepScrollOffset` issue when replacing the `ItemsSource` in `CollectionView` on Android. (`src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs`) **Test coverage:** * Added a new test case page that reproduces the issue and provides buttons to replace the `ItemsSource`, scroll to the top, and insert items at the top, making it easier to manually verify the fix. (`src/Controls/tests/TestCases.HostApp/Issues/Issue35806.cs`) * Introduced an automated UI test (Android only) that verifies the `KeepScrollOffset` behavior after replacing the `ItemsSource`, ensuring the regression is caught in the future. (`src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35806.cs`) ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes #35806 ### Tested the behavior in the following platforms - [ ] Windows - [x] Android - [ ] iOS - [ ] Mac ### Output | Before | After | |----------|----------| | <video src="https://github.com/user-attachments/assets/0b73f9e5-83e1-4e82-a382-47b22e56c797"> | <video src="https://github.com/user-attachments/assets/fc2c4648-4d77-4b55-a988-0a87c2c17e8a"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. --> --------- Co-authored-by: KarthikRajaKalaimani <92777139+KarthikRajaKalaimani@users.noreply.github.com>
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
KeepScrollOffsetmode does not maintain scroll position when new items are inserted into the CollectionView, resulting in unintended upward scrolling.Root Cause
When new items are added to the collection, the scroll offset is recalculated based on the updated layout. This results in incorrect offset adjustments, causing the scroll position to shift upward instead of being correctly preserved.
Description of Change
AddScrollListenerandRemoveScrollListenermethods to manage scroll listeners dynamically. Adjusted theTrackOffsetsmethod to ensure scroll offsets are only adjusted when the first item is reached, ensuring accurate scroll behavior and preventing unintended upward scrolling during dynamic item insertions.Issues Fixed
Fixes #29131
KeepItemsInViewissue on Android.Tested the behaviour in the following platforms
Output
BeforeFix_ScrollOffset.mov
AfterFix_ScrollOffset.mov