Skip to content

CarouselView: Fix cascading PositionChanged/CurrentItemChanged events on collection update#31275

Open
praveenkumarkarunanithi wants to merge 12 commits intodotnet:inflight/currentfrom
praveenkumarkarunanithi:fix-29529
Open

CarouselView: Fix cascading PositionChanged/CurrentItemChanged events on collection update#31275
praveenkumarkarunanithi wants to merge 12 commits intodotnet:inflight/currentfrom
praveenkumarkarunanithi:fix-29529

Conversation

@praveenkumarkarunanithi
Copy link
Copy Markdown
Contributor

@praveenkumarkarunanithi praveenkumarkarunanithi commented Aug 21, 2025

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

Windows
Position not updating on item add: CarouselView stayed at the old position after an item was added, leaving current/previous positions unsynced.

Cascading events: With ItemsUpdatingScrollMode.KeepItemsInView, programmatic smooth scrolls triggered multiple ViewChanged calls, causing PositionChanged to fire repeatedly with intermediate values.

Android
Programmatic smooth scrolls produced the same cascading PositionChanged events as on Windows.

Description of Change

Windows
Position Update: On item add, ItemsView.Position is explicitly set based on ItemsUpdatingScrollMode, keeping current and previous positions in sync.

Prevent Cascading Events: Added _isInternalPositionUpdate. For collection changes, animations are disabled (animate = false) so scrolling jumps directly, firing PositionChanged only once.

Android
Reused the _isInternalPositionUpdate logic. Disabled animations during collection changes, ensuring a single clean position update without duplicate events.

Issues Fixed

Fixes #29529

Tested the behaviour in the following platforms

  • Android
  • Windows
  • iOS
  • Mac

Screenshots

Before Issue Fix After Issue Fix
withoutfix withfix

@dotnet-policy-service dotnet-policy-service bot added the community ✨ Community Contribution label Aug 21, 2025
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Hey there @@praveenkumarkarunanithi! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@dotnet-policy-service dotnet-policy-service bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Aug 21, 2025
@praveenkumarkarunanithi praveenkumarkarunanithi changed the title Fix 29529 [Windows] Fix for CurrentItemChangedEventArgs and PositionChangedEventArgs Not Working Properly in CarouselView Aug 21, 2025
@praveenkumarkarunanithi praveenkumarkarunanithi changed the title [Windows] Fix for CurrentItemChangedEventArgs and PositionChangedEventArgs Not Working Properly in CarouselView [Windows, Android] Fix for CurrentItemChangedEventArgs and PositionChangedEventArgs Not Working Properly in CarouselView Aug 21, 2025
@praveenkumarkarunanithi praveenkumarkarunanithi marked this pull request as ready for review August 22, 2025 10:28
Copilot AI review requested due to automatic review settings August 22, 2025 10:28
@praveenkumarkarunanithi praveenkumarkarunanithi requested a review from a team as a code owner August 22, 2025 10:28
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes issues with CarouselView's event handling system where CurrentItemChangedEventArgs and PositionChangedEventArgs were not working properly on Windows and Android platforms. The fix prevents cascading position change events during collection updates by introducing an internal flag to disable animations during programmatic scrolls.

Key Changes:

  • Added _isInternalPositionUpdate flag to prevent cascading events during collection changes
  • Fixed position synchronization issues on Windows when items are added
  • Disabled animations during collection updates to ensure single, clean position updates

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
Issue29529.cs (TestCases.Shared.Tests) Adds automated UI test to verify position and item change events fire correctly after item insertion
Issue29529.cs (TestCases.HostApp) Creates test UI page with CarouselView demonstrating the issue and event tracking
CarouselViewHandler.Windows.cs Implements Windows-specific fix with internal flag and position synchronization logic
MauiCarouselRecyclerView.cs Implements Android-specific fix with internal flag and centralized scroll method

@jsuarezruiz
Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 3 pipeline(s).

@jsuarezruiz jsuarezruiz added the area-controls-collectionview CollectionView, CarouselView, IndicatorView label Sep 16, 2025
@rmarinho rmarinho added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-gate-failed AI could not verify tests catch the bug s/agent-fix-win AI found a better alternative fix than the PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Feb 18, 2026
@kubaflo kubaflo added s/agent-fix-lose Author adopted the agent's fix and it turned out to be bad and removed s/agent-fix-win AI found a better alternative fix than the PR s/agent-fix-lose Author adopted the agent's fix and it turned out to be bad labels Feb 20, 2026
@praveenkumarkarunanithi
Copy link
Copy Markdown
Contributor Author

🤖 AI Summary

📊 Expand Full Review
🔍 Pre-Flight — Context & Validation
📝 Review Sessionupdated fix. · b650f8e
Issue: #29529 - [Windows] CurrentItemChangedEventArgs and PositionChangedEventArgs Not Working Properly in CarouselView Platforms Affected: Windows (original issue), Android (PR extends fix) Files Changed: 2 implementation files, 2 test files

Issue Summary

On Windows (and Android), CarouselView's CurrentItemChangedEventArgs and PositionChangedEventArgs do not behave correctly:

  1. PreviousItem/CurrentItem don't update correctly when items are dynamically added at index 0
  2. PreviousPosition/CurrentPosition don't reflect correct positions after dynamic additions
  3. With ItemsUpdatingScrollMode.KeepItemsInView, programmatic smooth scrolls triggered multiple ViewChanged calls, causing PositionChanged to fire repeatedly with intermediate values (cascading events)

Files Changed

Fix files:

  • src/Controls/src/Core/Handlers/Items/Android/MauiCarouselRecyclerView.cs (+20/-3)
  • src/Controls/src/Core/Handlers/Items/CarouselViewHandler.Windows.cs (+47/-23)

Test files:

  • src/Controls/tests/TestCases.HostApp/Issues/Issue29529.cs (+113, new file)
  • src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29529.cs (+28, new file)

PR's Fix Approach

  • Added _isInternalPositionUpdate bool flag on both Android and Windows handlers
  • Flag is set true during CollectionItemsSourceChanged/OnCollectionItemsSourceChanged
  • ScrollToItemPosition (Android) / UpdateCurrentItem (Windows) check flag to disable animation
  • Windows: Also added explicit ItemsView.Position = currentItemPosition on Add to sync position
  • Windows: Wrapped collection change logic in try/finally for safe flag reset
  • Android: Added bounds check in new ScrollToItemPosition helper method

Reviewer Feedback

File:Line Reviewer Comment Status
Issue29529.cs HostApp:103 copilot-pull-request-reviewer Insert(6,...) should be Insert(0,...) to match issue (adds at end, not front) ⚠️ UNRESOLVED
Issue29529.cs HostApp:57 copilot-pull-request-reviewer Missing ": " in positionLabel initial text ⚠️ UNRESOLVED
Issue29529.cs HostApp:65 copilot-pull-request-reviewer Missing ": " in itemLabel initial text ⚠️ UNRESOLVED
CarouselViewHandler.Windows.cs jsuarezruiz Use try/finally for flag reset ✅ ADDRESSED
MauiCarouselRecyclerView.cs :570 jsuarezruiz Add bounds check on scroll ✅ ADDRESSED
Note on Insert(6) vs Insert(0): The copilot reviewer suggested Insert(0,...) because the issue is about "adding at index 0". However, the button text in the test says "Insert item at index 6". The Insert(6,...) adds to the end of a 6-item list, triggering the KeepItemsInView scroll-to-0 path. This tests the cascading events scenario. The original issue's "index 0" scenario is NOT directly tested.

Edge Cases to Check

  • Does PositionChanged fire exactly once after insert with KeepItemsInView on Android?
  • Does the test assertion match what actually happens (position goes to 0, events fire once)?
  • Are text formatting bugs in initial labels inconsequential to test assertions?

Fix Candidates

Source Approach Test Result Files Changed Notes

PR PR #31275 _isInternalPositionUpdate flag + disable animation during collection changes ⏳ PENDING (Gate) MauiCarouselRecyclerView.cs, CarouselViewHandler.Windows.cs Original PR
🚦 Gate — Test Verification
📝 Review Sessionupdated fix. · b650f8e
Result: ❌ GATE FAILED / ENVIRONMENT BLOCKED Platform: android Mode: Full Verification Attempted

Test Run Results

  • Tests WITHOUT fix: PASSED (unexpected - should FAIL)
  • Tests WITH fix: PASSED (expected)

Environment Analysis

The test output shows only build output (no device/emulator logs). Both runs completed with Build succeeded but no actual UI test execution on a device. This indicates:

  • No Android emulator was running during verification
  • dotnet test completed with only build phase, no deployment/execution phase
  • The "Passed: True" results reflect build success, not actual test execution

Root Cause of Gate Failure

The test verification could not determine if tests catch the bug because no Android device/emulator was available to deploy and run the UI tests.

Additional Analysis (from code review)

The test in Issue29529.cs (HostApp) uses Insert(6, "Item 0") which inserts at the END of a 6-item list. The original issue reports the bug when inserting at INDEX 0 (front of list). The test may not reproduce the exact scenario from the issue report. However, the test does verify the KeepItemsInView cascading events scenario.

Unresolved text formatting bugs in test (noted by copilot-pull-request-reviewer):

  • "Current Position{carouselView.Position}" → missing ": " (doesn't affect assertions)
  • "Current Item{carouselView.CurrentItem}" → missing ": " (doesn't affect assertions)

🔧 Fix — Analysis & Comparison
📝 Review Sessionupdated fix. · b650f8e

Fix Candidates

Source Approach Test Result Files Changed Notes

PR PR #31275 _isInternalPositionUpdate flag + disable animation during collection changes + explicit position sync (Windows) ✅ PASS (Gate/Build) MauiCarouselRecyclerView.cs, CarouselViewHandler.Windows.cs Original PR

Try-Fix Status

SKIPPED - No Android device/emulator available for test execution. All try-fix attempts require a running device to validate test results. The Gate phase confirmed no device is available (builds succeed but no test execution occurs).

Exhausted: No (skipped due to environment blocker) Selected Fix: PR's fix - Only candidate; code analysis confirms the approach is generally correct

Code Analysis (In Lieu of try-fix)

The PR's approach of using _isInternalPositionUpdate flag to disable animation during collection changes is a valid pattern. Key observations:

  1. Windows: Uses try/finally for flag reset (✅ Good - addressed jsuarezruiz feedback)

  2. Android: Does NOT use try/finally - flag reset paths are:

    • Early return when removingAnyPrevious = true (resets inline)
    • Inside async dispatched lambda (may not always execute)
    • Risk: If an exception occurs in the non-early-return path, flag stays true
  3. Test insert index: Uses Insert(6,...) (adds at end) vs original issue's "insert at index 0" - tests different scenario

📋 Report — Final Recommendation
📝 Review Sessionupdated fix. · b650f8e

⚠️ Final Recommendation: REQUEST CHANGES

Summary

PR #31275 fixes CarouselView's PositionChanged and CurrentItemChanged cascading events on Windows and Android when items are added during KeepItemsInView mode. The fix approach is sound but has a few issues that should be addressed before merging.

Gate Status

Gate phase was environment-blocked (no Android device available for UI test execution). Tests built successfully but could not be deployed/executed. The fix was assessed via code analysis.

Root Cause

On both Windows and Android, when items are added to a CarouselView with ItemsUpdatingScrollMode.KeepItemsInView, the scroll-to-position call uses animation. The animated scroll generates multiple ViewChanged/OnScrollViewChanged events, each firing PositionChanged with intermediate values — causing cascading events.

On Windows specifically, the position (ItemsView.Position) was not explicitly updated when items were added, leaving CurrentItem and position out of sync.

Fix Quality Assessment

What's Good:

  • The _isInternalPositionUpdate flag pattern is clean and targeted
  • Windows: uses try/finally for safe flag reset (good defensive programming)
  • Windows: adds explicit ItemsView.Position = currentItemPosition when adding items — correctly fixes the position sync bug
  • Android: new ScrollToItemPosition helper adds bounds check (good defensive programming)
  • Resolves both reviewer requests from jsuarezruiz

Issues Found:

1. Android: Missing try/finally (Medium Risk)

The Windows handler was updated to use try/finally for _isInternalPositionUpdate after reviewer feedback. The Android handler was NOT updated similarly. If an exception occurs in the Dispatcher.Dispatch lambda (lines 295–316 of MauiCarouselRecyclerView.cs), _isInternalPositionUpdate will be stuck as true, permanently disabling animation for subsequent scrolls.

Suggested fix for Android:

_isInternalPositionUpdate = true;
try
{
    // ... existing collection change logic ...
    Carousel.Handler.MauiContext.GetDispatcher().Dispatch(() =>
    {
        try
        {
            // ... existing dispatch logic ...
        }
        finally
        {
            _isInternalPositionUpdate = false;
        }
    });
}
catch
{
    _isInternalPositionUpdate = false;
    throw;
}

Or more simply, handle it within the existing removingAnyPrevious early-return path (which correctly resets, but the rest of the code relies on the async reset).

2. Test Uses Wrong Insert Index (Medium)

The test (Issue29529.cs HostApp) inserts at index 6 (Insert(6, "Item 0")) of a 6-item collection — this adds to the end of the list. But issue #29529 explicitly states the bug occurs when "items are dynamically added at index 0" (front of list). This means the test does NOT reproduce the primary scenario from the issue.

The copilot-pull-request-reviewer raised this: Insert(6, "Item 0") should be Insert(0, "Item 0").

The button text says "Insert item at index 6" — both the button text and the insert index should be changed to 0 to match the original issue.

3. Minor: Text Formatting in Initial Label Text

  • Text = $"Current Position{carouselView.Position}" → missing ": "
  • Text = $"Current Item{carouselView.CurrentItem}" → missing ": "

These don't affect test assertions (the assertions read after events fire, which use correct formatting), but they cause confusing initial display.

Detailed Code Review

File Change Assessment
MauiCarouselRecyclerView.cs _isInternalPositionUpdate flag ✅ Correct approach
MauiCarouselRecyclerView.cs ScrollToItemPosition helper with bounds check ✅ Good improvement
MauiCarouselRecyclerView.cs Flag reset in dispatched lambda (no try/finally) ⚠️ Needs try/finally
CarouselViewHandler.Windows.cs _isInternalPositionUpdate with try/finally ✅ Correct
CarouselViewHandler.Windows.cs Explicit ItemsView.Position = currentItemPosition ✅ Fixes position sync
Issue29529.cs (HostApp) Insert(6,...) should be Insert(0,...) ❌ Wrong insert index
Issue29529.cs (HostApp) Missing : in initial label texts ⚠️ Minor cosmetic
Issue29529.cs (Tests) Asserts position=0, count=1 each ✅ Correct for KeepItemsInView

Requested Changes

  1. Android MauiCarouselRecyclerView.cs: Add try/finally or equivalent exception safety for _isInternalPositionUpdate reset, consistent with the Windows handler
  2. Issue29529.cs (HostApp): Change carouselItems.Insert(6, "Item 0") to carouselItems.Insert(0, "Item 0") and update button text to "Insert item at index 0" — to test the original reported bug scenario
  3. Issue29529.cs (HostApp): Fix initial label text formatting ("Current Position: {carouselView.Position}" and "Current Item: {carouselView.CurrentItem}")
  4. Issue29529.cs (Tests): Update test assertions to match new Insert(0) behavior — with KeepItemsInView and insert at index 0, verify position stays at 0 and PreviousPosition is 3

📋 Expand PR Finalization Review
Title: ✅ Good

Current: [Windows, Android] Fix for CurrentItemChangedEventArgs and PositionChangedEventArgs Not Working Properly in CarouselView

Description: ✅ Good

Description needs updates. See details below.

✨ Suggested PR Description

[!NOTE]
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Root Cause

Windows: When a new item was added to the CarouselView's items source, ItemsView.Position was not explicitly updated to reflect the current item's new position. This left CurrentItem and Position out of sync. Additionally, when ItemsUpdatingScrollMode was KeepItemsInView, the resulting programmatic smooth scroll triggered multiple ScrollViewerViewChanged calls, causing PositionChanged to fire repeatedly with intermediate values — instead of once with the final position.

Android: Programmatic smooth scrolls during collection changes similarly triggered cascading PositionChanged events, producing multiple intermediate events instead of a single clean update.

Description of Change

Windows (CarouselViewHandler.Windows.cs):

  • Added _isInternalPositionUpdate flag, set to true at the start of OnCollectionItemsSourceChanged and reset in a finally block (ensuring reset even if an exception occurs).
  • On item add, explicitly sets ItemsView.Position = currentItemPosition to keep position in sync.
  • Added handling for KeepLastItemInView (position = last item) and KeepItemsInView (position = 0) modes in the collection-changed handler.
  • In UpdateCurrentItem, animation is disabled when _isInternalPositionUpdate is true, causing a direct jump scroll that fires PositionChanged only once.

Android (MauiCarouselRecyclerView.cs):

  • Added _isInternalPositionUpdate flag, set to true at the start of CollectionItemsSourceChanged and reset at the exit points.

  • Extracted a new ScrollToItemPosition(int position, bool shouldAnimate) helper method that:

    • Includes bounds checking (position < 0 || position >= Count) to prevent out-of-range scrolls.
    • Disables animation when _isInternalPositionUpdate is true, preventing cascading scroll events.
  • Both UpdateFromCurrentItem and UpdateFromPosition now call ScrollToItemPosition instead of ItemsView.ScrollTo directly.

Key Technical Details

  • _isInternalPositionUpdate flag: Guards scroll calls that occur as a side effect of collection mutations. When true, the animate parameter is forced to false, causing a jump scroll instead of a smooth scroll — which avoids triggering intermediate ViewChanged/OnScrolled callbacks.
  • Windows uses try/finally to ensure the flag is always reset, even on exceptions.
  • Android resets the flag manually at each exit point of CollectionItemsSourceChanged — not in a try/finally (see code review).

Issues Fixed

Fixes #29529

Platforms Tested

  • Android
  • Windows
  • iOS
  • Mac

Code Review: ✅ Passed

Code Review Findings — PR #31275

🟠 High Priority Issues

1. Unresolved Review Comment: Test inserts at wrong index

File: src/Controls/tests/TestCases.HostApp/Issues/Issue29529.cs, line 103 Reviewer comment (copilot-pull-request-reviewer, unresolved):

// Current (BUG):
carouselItems.Insert(6, "Item 0");

// Should be:
carouselItems.Insert(0, "Item 0");

Problem: The button text says "Insert item at index 6", but inserting at index 6 in a 6-item collection (indices 0–5) adds the item to the end, not the beginning. The original issue reports a bug with inserting at index 0. The UI test verifies behavior after inserting at index 6, which tests KeepItemsInView scroll-to-zero behavior rather than the issue's described scenario of insertion at the front.

This is a functional mismatch: the test is not actually reproducing the original bug scenario. The button text and insert index should both be corrected.

2. Unresolved Review Comment: Label text formatting — missing : separator

File: src/Controls/tests/TestCases.HostApp/Issues/Issue29529.cs, lines 57 and 65 Reviewer comments (copilot-pull-request-reviewer, unresolved):

// Line 57 - Current (BUG):
Text = $"Current Position{carouselView.Position}",
// Should be:
Text = $"Current Position: {carouselView.Position}",

// Line 65 - Current (BUG):
Text = $"Current Item{carouselView.CurrentItem}",
// Should be:
Text = $"Current Item: {carouselView.CurrentItem}",

Problem: The initial label text is missing : between the label name and the value. The event handlers correctly format as "Current Position: {e.CurrentPosition}", but the initialization strings don't match this format. If the test page is ever inspected before the first event fires, the displayed values won't match expectations and could confuse debugging.

More importantly, the positionLabel and itemLabel initial values are used as the initial display — if the test reads these before tapping InsertButton, the format mismatch could cause test failures in edge cases.

🟡 Medium Priority Issues

3. Android: _isInternalPositionUpdate flag not guarded with try/finally

File: src/Controls/src/Core/Handlers/Items/Android/MauiCarouselRecyclerView.cs

The Windows implementation (correctly) wraps the entire OnCollectionItemsSourceChanged body in a try/finally block to ensure _isInternalPositionUpdate is always reset. The Android CollectionItemsSourceChanged method resets the flag manually at two exit points:

  1. Early return when removingAnyPrevious is true
  2. At the end of the async MainThread.BeginInvokeOnMainThread (or equivalent) callback

If any exception occurs between setting _isInternalPositionUpdate = true and the final reset, the flag remains true permanently. Subsequent scroll operations would silently have animations disabled even in non-collection-change contexts.

Recommendation: Apply the same try/finally pattern as Windows:

_isInternalPositionUpdate = true;
try
{
    // ... existing logic ...
}
finally
{
    _isInternalPositionUpdate = false;
}

Note: the async callback adds complexity — the flag should ideally be reset after the async work completes, not in a synchronous finally. Consider whether the flag needs to be scoped to the async continuation as well.

4. Windows: KeepItemsInView unconditionally sets carouselPosition = 0

File: src/Controls/src/Core/Handlers/Items/CarouselViewHandler.Windows.cs

else if (ItemsView.ItemsUpdatingScrollMode == ItemsUpdatingScrollMode.KeepItemsInView)
{
    carouselPosition = 0;
}

Problem: KeepItemsInView means "keep the currently visible items in view" — when adding items, the view should not scroll away. Unconditionally setting carouselPosition = 0 overrides any current position and always jumps to the first item, even if the user was viewing item 3 of 10. This seems overly aggressive and may change behavior in existing apps using KeepItemsInView.

The original code before this PR did not include this branch for KeepItemsInView. It was only added in this PR and is not explained in the description.

Recommendation: Verify whether this is the intended behavior for KeepItemsInView. If adding an item at any position should always reset the carousel to position 0, that should be clearly documented. If not, this branch may be incorrect.

✅ Positive Observations

  • Windows try/finally: Correctly uses try/finally to guarantee _isInternalPositionUpdate is always reset, even on exceptions. This was added in response to a reviewer comment and is the right approach.
  • Android bounds check: ScrollToItemPosition validates position >= 0 && position < Count before scrolling, preventing out-of-range scroll attempts. This was added in response to a reviewer comment.
  • Android helper method extraction: The new ScrollToItemPosition helper cleanly centralizes the animation-disable logic, avoiding duplication in UpdateFromCurrentItem and UpdateFromPosition.
  • UI tests added: Both a host app page and a shared NUnit test are included for the new functionality.

Addressed all AI Agent concerns — corrected Insert(0, ...) with button text "Insert item at index 0", fixed label initial text formatting (": "), wrapped the Android dispatch lambda in try/finally to ensure _isInternalPositionUpdate resets on exception and scoped UpdateItemDecoration/UpdateVisualStates inside the _scrollToCounter guard, and confirmed KeepItemsInViewcarouselPosition = 0 is intentional — Android and iOS had identical behavior pre-PR, the Windows branch now aligns with them. No pending concerns remaining.

@kubaflo kubaflo removed the s/agent-gate-failed AI could not verify tests catch the bug label Feb 25, 2026
@dotnet dotnet deleted a comment from rmarinho Mar 17, 2026
@kubaflo kubaflo added s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues labels Mar 17, 2026
@praveenkumarkarunanithi praveenkumarkarunanithi changed the title [Windows, Android] Fix for CurrentItemChangedEventArgs and PositionChangedEventArgs Not Working Properly in CarouselView CarouselView: Fix cascading PositionChanged/CurrentItemChanged events on collection update Mar 17, 2026
@sheiksyedm
Copy link
Copy Markdown
Contributor

/azp run maui-pr-uitests , maui-pr-devicetests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

@github-actions
Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 31275

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 31275"

@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Mar 24, 2026

🤖 AI Summary

📊 Expand Full Reviewb62ddb8 · Merge branch 'dotnet:main' into fix-29529
🔍 Pre-Flight — Context & Validation

Issue: #29529 - [Windows] CurrentItemChangedEventArgs and PositionChangedEventArgs Not Working Properly in CarouselView
PR: #31275 - CarouselView: Fix cascading PositionChanged/CurrentItemChanged events on collection update
Platforms Affected: Windows (original issue), Android (PR extends fix), iOS/MacCatalyst (iOS fix via Items2/)
Files Changed: 3 implementation files, 2 test files

Key Findings

  • Root cause: Inserting items at index 0 in a CarouselView collection triggers a scroll-position adjustment internally (RecyclerView/WinUI scroll offset shifts). This fires CarouselViewScrolled/OnScrollViewChanged, which calls SetCarouselViewPosition() or updates Position, causing PositionChanged and CurrentItemChanged to fire multiple times with stale values.
  • PR approach: Adds _isInternalPositionUpdate boolean flag (per-platform). Set to true before collection change processing, false in finally block. During collection changes, scroll callbacks check this flag and bail early, and ScrollTo calls disable animation (preventing intermediate scroll events).
  • Android (Items/): Flag set at top of CollectionItemsSourceChanged, cleared in two places (early exit branch + finally in dispatched callback). CarouselViewScrolled bails if flag is set. New ScrollToItemPosition helper disables animation when flag is active.
  • Windows (Items/): Same flag/try-finally pattern in OnCollectionItemsSourceChanged. UpdateCurrentItem also checks flag before animating. Also fixes a Windows-specific bug: position not updated when ItemsUpdatingScrollMode is KeepItemsInView or KeepLastItemInView.
  • iOS/MacCatalyst (Items2/): Different flag name _isInternalCollectionUpdate. Set in CollectionViewUpdating, cleared at TOP of CollectionViewUpdated (before SetPosition calls). SetPosition() early-returns when flag is set to suppress spurious UIKit scroll callbacks during batch updates.
  • Test (UI test): Uses BuildAndRunHostApp.ps1 with Issue29529 test in TestCases.Shared.Tests. Verifies: after inserting item at index 0, PositionChanged fires exactly once with correct CurrentPosition: 0, PreviousPosition: 3, CurrentItemChanged fires exactly once, event counts are both 1.
  • Review feedback addressed: (1) Reviewer (jsuarezruiz) asked for try/finally to ensure flag reset → PR added it. (2) Reviewer asked for bounds check in ScrollToItemPosition → PR added it. (3) Copilot reviewer noted Insert(6,...) vs Insert(0,...) bug in test → PR fixed it.
  • Android flag lifecycle issue: The flag is set to true at the top of CollectionItemsSourceChanged but the async Dispatch callback also accesses it. There's a subtle race: _isInternalPositionUpdate is reset to false in the early-exit branch (removingAnyPrevious) before the dispatched lambda runs — but then the dispatched lambda's finally also sets it false, which is already false. Not a real race since Dispatcher.Dispatch on Android posts to the main thread queue.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #31275 Boolean flag _isInternalPositionUpdate to suppress scroll callbacks + disable animation during collection changes (all 3 platforms) ✅ PASSED (Gate) MauiCarouselRecyclerView.cs, CarouselViewHandler.Windows.cs, CarouselViewController2.cs Addresses both cascading events and animation-driven repeat events

🔧 Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix (claude-opus-4.6) Detach CarouselViewScrolled during collection change + set _gotoPosition to block UpdateFromCurrentItem from re-triggering ScrollTo ✅ PASS MauiCarouselRecyclerView.cs (Android only) Uses no new fields; leverages existing _gotoPosition and event subscribe/unsubscribe
2 try-fix (claude-sonnet-4.6) _nextExpectedPosition guard in UpdatePosition — rejects spurious intermediate positions from RecyclerView scroll offset shifts ✅ PASS MauiCarouselRecyclerView.cs (Android only) Minimal: one new int field + one early-return guard
3 try-fix (gpt-5.3-codex) Suppress non-user scroll callbacks during collection updates using existing _noNeedForScroll + IsDragging/IsScrolling state — no new fields ✅ PASS MauiCarouselRecyclerView.cs (Android only) Uses existing state, callback-level filtering
4 try-fix (gpt-5.4) Reorder UpdatePosition before SetCurrentItem in dispatch callback ❌ FAIL MauiCarouselRecyclerView.cs RecyclerView compensation scroll fires independently of ordering
PR PR #31275 Boolean flag _isInternalPositionUpdate + try/finally, disables animation, bounds check (all 3 platforms) ✅ PASSED (Gate) MauiCarouselRecyclerView.cs, CarouselViewHandler.Windows.cs, CarouselViewController2.cs Only approach covering all 3 platforms

Cross-Pollination

Model Round New Ideas? Details
claude-opus-4.6 2 Yes RecyclerView.SuppressLayout(true) to prevent OnScrolled at Android level
claude-sonnet-4.6 2 Yes Gate on RecyclerView.IsComputingLayout internal flag
gpt-5.3-codex 2 Yes Update-generation token on pending syncs; ignore stale callbacks
gpt-5.4 2 Yes Capture centered item pixel offset before change, restore after

Exhausted: Yes (4/4 models complete, cross-pollination done; new ideas are more complex than existing passes)

Selected Fix: PR #31275 — Only approach covering all 3 platforms (Android + Windows + iOS/Mac); try/finally robustness; self-documenting flag; handles both cascading event sources.


📋 Report — Final Recommendation

✅ Final Recommendation: APPROVE

Phase Status

Phase Status Notes
Pre-Flight ✅ COMPLETE Issue #29529, 3 platform impls, UI test
Gate ✅ PASSED Android — FAIL without fix, PASS with fix
Try-Fix ✅ COMPLETE 4 attempts: 3 PASS, 1 FAIL; cross-pollination exhausted
Report ✅ COMPLETE

Summary

PR #31275 fixes cascading PositionChanged/CurrentItemChanged events in CarouselView when items are inserted at index 0 with ItemsUpdatingScrollMode.KeepItemsInView. The fix spans all 3 platform implementations (Android via Items/, Windows via Items/, iOS/Mac via Items2/). Gate passed on Android. Three independent alternative approaches also passed tests, confirming the root cause is well-understood and the PR's approach is sound.

Selected Fix: PR #31275 (over all try-fix alternatives)

Root Cause

When an item is inserted at index 0, the platform scroll views (Android RecyclerView, WinUI ScrollViewer) automatically shift their scroll offset to compensate, keeping the currently-displayed item visible. This offset shift triggers the CarouselViewScrolled/OnScrollViewChanged callback, which calls SetCarouselViewPosition()/UpdatePosition(). Because this happens during — or immediately after — the collection-change processing, Position and CurrentItem are updated again with stale/intermediate values, causing PositionChanged and CurrentItemChanged to fire multiple times with wrong PreviousPosition values.

A secondary source on Android: calling ScrollTo() with animation during collection changes produces frame-by-frame scroll events that each trigger intermediate position updates.

Fix Quality

Strengths:

  1. Cross-platform completeness: The only passing approach that fixes all 3 platforms. All 3 independent try-fix alternatives (Attempts 1-3) only fixed Android. The PR also fixes Windows (CarouselViewHandler.Windows.cs) and iOS/Mac (CarouselViewController2.cs).

  2. Robustness via try/finally: The _isInternalPositionUpdate flag is reset in a finally block, ensuring it is always cleared even if an exception occurs. This was explicitly requested by reviewer jsuarezruiz and correctly implemented.

  3. Handles both cascading sources: (a) Suppresses CarouselViewScrolled callbacks via early-return when flag is set. (b) Disables animation via ScrollToItemPosition helper so no animation-driven intermediate scroll positions are generated.

  4. Bounds check added: New ScrollToItemPosition() helper validates position >= 0 && position < Count before calling ScrollTo, preventing out-of-range scrolls (also requested by reviewer).

  5. iOS guard placement is correct: _isInternalCollectionUpdate is cleared at the top of CollectionViewUpdated (before any SetPosition/SetCurrentItem calls within that method), so legitimate position updates in CollectionViewUpdated are not suppressed — only spurious UIKit scroll callbacks during the batch update window are blocked.

Observations (non-blocking):

  • Android flag lifecycle: _isInternalPositionUpdate is set to true at the top of CollectionItemsSourceChanged before the removingAnyPrevious early-exit check. The early-exit branch resets it synchronously (line 269). The dispatched callback's finally resets it asynchronously (line 319). This creates a small window where rapid collection changes could interact. This is an acceptable trade-off and consistent with the approach used in the Windows implementation.

  • Android test only: The UI test (Issue29529VerifyPreviousPositionOnInsert) runs on Android (confirmed by Gate). Windows behavior relies on code review. The issue was originally Windows-only; Android exposure was found during PR development.

  • Test assertions are precise: Verifies CurrentPosition: 0, Previous Position: 3, Current Item: Item 0, Previous Item: Item 4, and event counts PositionChanged: 1, CurrentItemChanged: 1 — directly validates the fix eliminates duplicates and ensures correct previous values.

  • private → no modifier on GetTargetPosition (iOS): Minor style change (removing private keyword) is inconsequential in C#.

Selected Fix: PR's fix (over alternatives)

Criterion PR Fix Best Alternative (Attempt 1)
Platforms fixed ✅ Android + Windows + iOS/Mac ❌ Android only
New fields added 1 bool per platform 0 new fields
Reviewer feedback addressed ✅ try/finally + bounds check N/A
Animation-driven cascades fixed ✅ Yes (disables animation) ✅ Yes (_gotoPosition blocks ScrollTo)
Maintainability Clear, self-documenting flag Requires understanding event subscribe/unsubscribe lifecycle

@MauiBot MauiBot added s/agent-approved AI agent recommends approval - PR fix is correct and optimal and removed s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) labels Mar 24, 2026
@kubaflo kubaflo changed the base branch from main to inflight/current March 24, 2026 12:23
Copy link
Copy Markdown
Contributor

@kubaflo kubaflo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please resolve conflicts?

kubaflo pushed a commit that referenced this pull request Mar 29, 2026
…ound in ItemsSource (#32141)

<!-- 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!

### Issue Details

- When a current item is set to a value that doesn't exist in the
CarouselView's items source, the carousel incorrectly scrolls to the
last item in a looped carousel.

### Root Cause
CarouselViewHandler1 :
- When CarouselView.CurrentItem is set to an item that is not present in
the ItemsSource, ItemsSource.GetIndexForItem(invalidItem) returns -1,
indicating that the item was not found. This -1 value is then passed
through several methods: UpdateFromCurrentItem() calls
ScrollToPosition(-1, currentPosition, animate), which triggers
CarouselView.ScrollTo(-1, ...). In loop mode, this leads to
CarouselViewHandler.ScrollToRequested being invoked with args.Index =
-1. The handler then calls GetScrollToIndexPath(-1), which in turn
invokes CarouselViewLoopManager.GetGoToIndex(collectionView, -1). Inside
GetGoToIndex, arithmetic operations are performed on the invalid index,
causing -1 to be treated as a valid position. As a result, the
UICollectionView scrolls to this calculated physical position, which
corresponds to the last logical item, producing unintended scroll
behavior.

CarouselViewHandler2 :
- When CurrentItem is not found in ItemsSource, GetIndexForItem returns
-1; in loop mode,
CarouselViewLoopManager.GetCorrectedIndexPathFromIndex(-1) adds 1,
incorrectly converting it to 0, which results in an unintended scroll to
the last duplicated item.

### Description of Change

- Added a check in ScrollToPosition methods in both
CarouselViewController.cs and CarouselViewController2.cs to return early
if goToPosition is less than zero, preventing unwanted scrolling when
the target item is invalid.
- **NOTE** : This [PR](#31275)
resolves the issue of incorrect scrolling in loop mode when CurrentItem
is not found in the ItemsSource, on Android.


### Issues Fixed
Fixes #32139 

### Validated the behaviour in the following platforms

- [x] Windows
- [x] Android
- [x] iOS
- [x] Mac

### Output
| Before | After |
|----------|----------|
| <video
src="https://github.com/user-attachments/assets/48c77f1b-0819-4717-8cf6-68873f82ec1d">
| <video
src="https://github.com/user-attachments/assets/1a667869-d79b-48fd-bc05-7ae3bd16a654">
|
@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Mar 29, 2026

🚦 Gate - Test Before and After Fix

📊 Expand Full Gateb62ddb8 · Merge branch 'dotnet:main' into fix-29529

Gate Result: ✅ PASSED

Platform: ANDROID · Base: main · Merge base: 794a9fa6

Test Without Fix (expect FAIL) With Fix (expect PASS)
🖥️ Issue29529 Issue29529 ✅ FAIL — 677s ✅ PASS — 512s
🔴 Without fix — 🖥️ Issue29529: FAIL ✅ · 677s
  Determining projects to restore...
  Restored /home/vsts/work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 568 ms).
  Restored /home/vsts/work/1/s/src/Essentials/src/Essentials.csproj (in 4.5 sec).
  Restored /home/vsts/work/1/s/src/Core/src/Core.csproj (in 5.96 sec).
  Restored /home/vsts/work/1/s/src/Core/maps/src/Maps.csproj (in 2.4 sec).
  Restored /home/vsts/work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 37 ms).
  Restored /home/vsts/work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 28 ms).
  Restored /home/vsts/work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 38 ms).
  Restored /home/vsts/work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj (in 48 ms).
  Restored /home/vsts/work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 848 ms).
  Restored /home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj (in 1.44 sec).
  1 of 11 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  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
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  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.60-ci+azdo.13736349
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.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
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  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:08:33.51
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
  Determining projects to restore...
  Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils/VisualTestUtils.csproj (in 1.14 sec).
  Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils.MagickNet/VisualTestUtils.MagickNet.csproj (in 4.43 sec).
  Restored /home/vsts/work/1/s/src/Controls/tests/TestCases.Android.Tests/Controls.TestCases.Android.Tests.csproj (in 5.77 sec).
  Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Core/UITest.Core.csproj (in 9 ms).
  Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Appium/UITest.Appium.csproj (in 2 ms).
  Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.NUnit/UITest.NUnit.csproj (in 271 ms).
  Restored /home/vsts/work/1/s/src/Controls/tests/CustomAttributes/Controls.CustomAttributes.csproj (in 5 ms).
  Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Analyzers/UITest.Analyzers.csproj (in 1.82 sec).
  5 of 13 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  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.60-ci+azdo.13736349
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  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.60-ci+azdo.13736349
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
  VisualTestUtils -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
  UITest.Core -> /home/vsts/work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.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.NUnit -> /home/vsts/work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.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.
/home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
[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.10]   Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.30]   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
>>>>> 04/03/2026 14:32:11 FixtureSetup for Issue29529(Android)
>>>>> 04/03/2026 14:32:13 Issue29529VerifyPreviousPositionOnInsert Start
>>>>> 04/03/2026 14:32:18 Issue29529VerifyPreviousPositionOnInsert Stop
>>>>> 04/03/2026 14:32:18 Log types: logcat, bugreport, server
  Failed Issue29529VerifyPreviousPositionOnInsert [5 s]
  Error Message:
     Assert.That(text, Is.EqualTo("Current Position: 0, Previous Position: 3"))
  String lengths are both 41. Strings differ at index 40.
  Expected: "Current Position: 0, Previous Position: 3"
  But was:  "Current Position: 0, Previous Position: 4"
  ---------------------------------------------------^

  Stack Trace:
     at Microsoft.Maui.TestCases.Tests.Issues.Issue29529.Issue29529VerifyPreviousPositionOnInsert() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29529.cs:line 22

1)    at Microsoft.Maui.TestCases.Tests.Issues.Issue29529.Issue29529VerifyPreviousPositionOnInsert() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29529.cs:line 22


NUnit Adapter 4.5.0.0: Test execution complete

Total tests: 1
     Failed: 1
Test Run Failed.
 Total time: 27.4258 Seconds

🟢 With fix — 🖥️ Issue29529: PASS ✅ · 512s
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  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.60-ci+azdo.13736349
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
  Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
  Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:06:36.49
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  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.60-ci+azdo.13736349
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13736349
  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.60-ci+azdo.13736349
  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.
/home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
[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.10]   Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.29]   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
>>>>> 04/03/2026 14:40:46 FixtureSetup for Issue29529(Android)
>>>>> 04/03/2026 14:40:48 Issue29529VerifyPreviousPositionOnInsert Start
>>>>> 04/03/2026 14:40:52 Issue29529VerifyPreviousPositionOnInsert Stop
  Passed Issue29529VerifyPreviousPositionOnInsert [4 s]
NUnit Adapter 4.5.0.0: Test execution complete

Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 21.0709 Seconds

📁 Fix files reverted (4 files)
  • eng/pipelines/ci-copilot.yml
  • src/Controls/src/Core/Handlers/Items/Android/MauiCarouselRecyclerView.cs
  • src/Controls/src/Core/Handlers/Items/CarouselViewHandler.Windows.cs
  • src/Controls/src/Core/Handlers/Items2/iOS/CarouselViewController2.cs

@MauiBot MauiBot added s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) s/agent-approved AI agent recommends approval - PR fix is correct and optimal and removed s/agent-approved AI agent recommends approval - PR fix is correct and optimal s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) labels Mar 29, 2026
Copy link
Copy Markdown
Contributor

@kubaflo kubaflo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please resolve conflicts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-controls-collectionview CollectionView, CarouselView, IndicatorView community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration s/agent-approved AI agent recommends approval - PR fix is correct and optimal s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Windows] CurrentItemChangedEventArgs and PositionChangedEventArgs Not Working Properly in CarouselView

8 participants