Skip to content

[Android] Fix CollectionView ActivityIndicator not animating after header height change#35358

Merged
kubaflo merged 12 commits into
dotnet:inflight/currentfrom
Vignesh-SF3580:fix-33780
May 13, 2026
Merged

[Android] Fix CollectionView ActivityIndicator not animating after header height change#35358
kubaflo merged 12 commits into
dotnet:inflight/currentfrom
Vignesh-SF3580:fix-33780

Conversation

@Vignesh-SF3580
Copy link
Copy Markdown
Contributor

@Vignesh-SF3580 Vignesh-SF3580 commented May 8, 2026

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

On Android, ActivityIndicator items inside a CollectionView stop animating after a header height change. When IsRunning is toggled back ON, the spinning animation does not appear for some items.

Root Cause

When the header collapses, RecyclerView triggers a layout traversal, measuring and repositioning all visible items. If setVisibility(VISIBLE) is invoked during this traversal, startAnimation() may not start the animation because the views are not yet fully attached and positioned. Items near the bottom of the list are laid out later in the traversal, which is why they are more likely to fail.

Description of Change

Post() is now used only when progressBar.IsInLayout || !progressBar.IsAttachedToWindow. In all other cases, visibility is updated synchronously.
An early if (progressBar.IsDisposed()) { return; } check prevents accessing properties on a disposed view. An additional !IsDisposed() check inside the lambda skips the update if the view is disposed before the runnable executes.

Why tests were not added

Three test approaches were evaluated, and none were viable:

  • UI Test with VerifyScreenshot() — Not reliable. The ActivityIndicator spinner is an indeterminate animation, and its position varies across runs. This causes screenshot comparisons to fail even when the fix is working correctly, leading to a consistently flaky test.

  • UI Test with WaitForElement / WaitForNoElement — Not effective. These methods only verify element presence, not animation state. Since the ActivityIndicator exists in the visual tree regardless of whether it is spinning, this approach cannot distinguish between the broken and fixed behavior.

  • Device Test — Not effective. Device tests passed both with and without the fix. The current infrastructure does not provide a reliable way to verify whether an indeterminate ProgressBar animation is actively running or stuck.

This issue is specific to the visual animation state—the element is present and correctly bound in all cases, but the spinning animation fails. Currently, MAUI’s test infrastructure does not provide a reliable way to validate indeterminate animation states on Android.

Issues Fixed

Fixes #33780

Screenshots

Before Issue Fix After Issue Fix
33780BeforeFix.mov
33780AfterFix.mov

dependabot Bot and others added 9 commits May 7, 2026 08:48
Updated [Magick.NET-Q8-AnyCPU](https://github.com/dlemstra/Magick.NET)
from 14.10.4 to 14.12.0.

<details>
<summary>Release notes</summary>

_Sourced from [Magick.NET-Q8-AnyCPU's
releases](https://github.com/dlemstra/Magick.NET/releases)._

## 14.12.0

### What's Changed
- Added `FixByteOrder` to the `DcmReadDefines` (#​1976)
- Added `IconWriteDefines`.

### Related changes in ImageMagick since the last release of Magick.NET:
- Correct bug in `Composite` when using `CopyAlpha` (#​1985)
- Fixed incorrect orientation of JPEG compressed TIFF images (#​1991)
- Heap-Buffer-Overflow write of single zero byte when parsing xml
(GHSA-cr67-pvmx-2pp2)
- Stack Overflow in DestroyXMLTree
(GHSA-fwvm-ggf6-2p4x)
- Out-of-Bounds read in sample operation
(GHSA-pcvx-ph33-r5vv)
- Stack Overflow via Recursive FX Expression Parsing
(GHSA-f4qm-vj5j-9xpw)
- Heap Buffer Overflow in ImageMagick MVG decoder
(GHSA-x9h5-r9v2-vcww)
- Heap overflow caused by integer overflow/wraparound in viff encoder on
32-bit builds
(GHSA-v67w-737x-v2c9)
- Stack-buffer-overflow in MNG encoder with oversized pallete
(GHSA-98cp-rj9f-6v5g)
- Integer overflow in despeckle operation causes heap buffer overflow on
32-bit builds
(GHSA-26qp-ffjh-2x4v)
- Off-by-One in MSL decoder could result in crash
(GHSA-5xg3-585r-9jh5)
- Heap buffer overflow when encoding JXL image with a 16-bit float
(GHSA-jvgr-9ph5-m8v4)
- Heap-use-after-free via XMP profile could result in a crash when
printing the values
(GHSA-r83h-crwp-3vm7)
- Heap buffer overflow (WRITE) in the YAML and JSON encoders
(GHSA-5592-p365-24xh)
- Heap out-of-bounds write in JP2 encoder
(GHSA-pwg5-6jfc-crvh)

### Library updates:
- ImageMagick 7.1.2-19 (2026-04-12)
- aom 3.13.3 (2026-04-02)
- openexr 3.4.9 (2026-04-03)
- freetype 2.14.3 (2026-03-22)
- gdk-pixbuf 2.44.6 (2026-03-31)
- harfbuzz 14.0.0 (2026-04-01)
- liblzma 5.8.3 (2026-04-31)
- libpng 1.6.56 (2026-03-25)

**Full Changelog**:
dlemstra/Magick.NET@14.11.1...14.12.0

## 14.11.1

### Related changes in ImageMagick since the last release of Magick.NET:
- Stack-buffer-overflow WRITE in InterpretImageFilename due to overflow
(GHSA-8793-7xv6-82cf)

### Library updates:
- ImageMagick 7.1.2-18 (2026-03-23)
- aom 3.13.2 (2026-03-19)
- openexr 3.4.7 (2026-03-15)
- harfbuzz 13.2.1 (2026-03-19)

**Full Changelog**:
dlemstra/Magick.NET@14.11.0...14.11.1

## 14.11.0

### What's Changed
- Added `DcmReadDefines`.

### Related changes in ImageMagick since the last release of Magick.NET:
- Access mode change for files created from 0666 to 0600
(ImageMagick/ImageMagick#8609)
- Heap-buffer-overflow in NewXMLTree could result in crash
(GHSA-gc62-2v5p-qpmp)

### Library updates:
- ImageMagick 7.1.2-17 (2026-03-16)
- openexr 3.4.6 (2026-03-01)
- freetype 2.14.2 (2026-03-01)
- harfbuzz 13.0.1 (2026-03-07)
- libxml2 2.15.2 (2026-03-03)

**Full Changelog**:
dlemstra/Magick.NET@14.10.4...14.11.0

Commits viewable in [compare
view](dlemstra/Magick.NET@14.10.4...14.12.0).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Magick.NET-Q8-AnyCPU&package-manager=nuget&previous-version=14.10.4&new-version=14.12.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts page](https://github.com/dotnet/maui/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…t#35333)

Bump OpenTelemetry packages to latest stable versions in the
maui-aspire-servicedefaults template:

- OpenTelemetry.Exporter.OpenTelemetryProtocol: 1.9.0 to 1.15.3
- OpenTelemetry.Extensions.Hosting: 1.9.0 to 1.15.3
- OpenTelemetry.Instrumentation.Http: 1.9.0 to 1.15.1
- OpenTelemetry.Instrumentation.Runtime: 1.9.0 to 1.15.1

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This pull request updates the following dependencies

[marker]: <> (Begin:a71c12d9-5aa4-4b46-e2d6-08da0cf8cd95)
## From https://github.com/dotnet/xharness
- **Subscription**:
[a71c12d9-5aa4-4b46-e2d6-08da0cf8cd95](https://maestro.dot.net/subscriptions?search=a71c12d9-5aa4-4b46-e2d6-08da0cf8cd95)
- **Build**:
[20260430.4](https://dev.azure.com/dnceng/internal/_build/results?buildId=2964906)
([312724](https://maestro.dot.net/channel/2/github:dotnet:xharness/build/312724))
- **Date Produced**: May 1, 2026 7:05:11 AM UTC
- **Commit**:
[92962e5c46ac08a66ded4c5696209cc60f1a232f](dotnet/xharness@92962e5)
- **Branch**: [main](https://github.com/dotnet/xharness/tree/main)

[DependencyUpdate]: <> (Begin)

- **Dependency Updates**:
  - From [11.0.0-prerelease.26229.1 to 11.0.0-prerelease.26230.4][1]
     - Microsoft.DotNet.XHarness.CLI
     - Microsoft.DotNet.XHarness.TestRunners.Common
     - Microsoft.DotNet.XHarness.TestRunners.Xunit

[1]: dotnet/xharness@9d5a7e9...92962e5

[DependencyUpdate]: <> (End)


[marker]: <> (End:a71c12d9-5aa4-4b46-e2d6-08da0cf8cd95)

Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@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](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!

## Description

Replaces `review-rules.md` (flat 345-line checklist) with a dimensional
expert review agent. Single source of truth for all review rules,
organized into 30 dimensions for per-dimension sub-agent evaluation.
Adds inline file:line PR comments alongside the existing wall-of-text
summary.

Extracted from 28k review comments across 5 maintainers via
[extraction-pipeline](https://github.com/dotnet/fsharp/blob/main/.github/agents/extraction-pipeline.md).
No functional code changes.

Recreated from dotnet#35062 on a dotnet/maui branch (originally opened from a
fork).

## What changed

**Before:** `review-rules.md` had 345 lines of flat rules. `code-review`
skill loaded them all into one context. Output was a single wall-of-text
PR comment.

**After:** Rules absorbed into `maui-expert-reviewer.md` as 30
dimensions with 200+ CHECK items. Each dimension runs as an independent
sub-agent with focused context. Output is inline file:line PR comments
via `inline-findings.json`.

## CI Flow

```
Review-PR.ps1 prompt:
  1. code-review → maui-expert-reviewer agent → inline-findings.json
  2. pr-review → Pre-Flight → Try-Fix → Report (sees findings, no duplication)

Posting:
  post-inline-review.ps1    → .json → GitHub file:line comments (NEW)
  post-ai-summary-comment.ps1 → {phase}/content.md → wall-of-text (existing)

CI: COMMENTS_VIA_FILE=true → agent writes .json, script posts
Local: agent writes .json, code-review posts directly via gh api
```

## Files

| Action | File | What |
|--------|------|------|
| **Add** | `agents/maui-expert-reviewer.md` | 30 dimensions, 200+
CHECKs, routing table |
| **Add** | `instructions/collectionview-{android,ios,windows}` |
Platform-isolated CV rules |
| **Add** |
`instructions/{handler-patterns,layout-system,performance-hotpaths,public-api,threading-async}`
| Domain-specific ambient guidance |
| **Add** | `scripts/post-inline-review.ps1` | Posts .json as GitHub PR
review |
| **Del** | `skills/code-review/references/review-rules.md` | Absorbed
into agent |
| **Mod** | `skills/code-review/SKILL.md` | Delegates to agent |
| **Mod** | `scripts/Review-PR.ps1` | Prompt + inline posting wiring |
| **Mod** | `eng/pipelines/ci-copilot.yml` | `COMMENTS_VIA_FILE` env var
|

---------

Co-authored-by: kubaflo <kubaflo@users.noreply.github.com>
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Tomas Grosup <tomasgrosup@microsoft.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

🚀 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 -- 35358

Or

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

@dotnet-policy-service dotnet-policy-service Bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label May 8, 2026
@Vignesh-SF3580 Vignesh-SF3580 added the community ✨ Community Contribution label May 8, 2026
kubaflo pushed a commit that referenced this pull request May 8, 2026
…ory fails before tests

When a test category fails because the build or deploy crashed before any
test could run (e.g. CS0246 missing namespace, RS0016 PublicAPI errors),
the AI summary table previously showed '0/1 ✓' — the green-checkmark
'all passed' branch — because no per-test failures were parsed. That's
visually misleading: the row is FAILED but the cell looks healthy.

Two fixes:

1. Tests column distinguishes 'category failed AND no per-test failures
   parsed' from 'all tests passed':
     - 'build/deploy failed'                              (no tests at all)
     - '0/1 — build/deploy failed before per-test results'  (some discovered)

2. New optional 'build_tail' field captures the last 30 lines of stdout
   when a category fails with zero per-test failures. The Failed test
   details collapsible section then renders it in a code block so
   reviewers see the actual compiler error / build crash inline,
   instead of having to download the full CopilotLogs artifact.

This was discovered while running the regression-check pipeline against
PRs #35110 (142 RS0016 PublicAPI errors), #35281 (CS0246 NSAttributedString
missing for catalyst), and #35358 — all reported as '0/1 ✓' before the fix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

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

Expert Review — 3 findings

See inline comments for details.

@MauiBot MauiBot added s/agent-review-incomplete s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels May 8, 2026
kubaflo pushed a commit that referenced this pull request May 8, 2026
Two related fixes that together restore meaningful per-test failure
detail in the AI summary comment.

## 1. Parser input normalization (root cause of '1 failed test' bug)

PowerShell's '& dotnet test 2>&1' wraps multi-line stderr blocks (and
some console-logger output paths) as a single ErrorRecord/string with
embedded newlines. The 'Get-DotNetTestResults' regex is anchored
'^...$' (start/end of STRING, not line), so without splitting the
parser would either skip those blocks entirely or — worse — collapse
the entire multi-line block as one bogus 'test result' with all 100+
test names concatenated into a single name field.

Observed on PR #35358 where 119 actual test failures were rendered as
'1 failed test' with name='LightTheme_VerifyVisualState DarkTheme_…'
(space-separated wall of 119 names) and duration='2 m 16 s 2 m 16 s …'
(matching wall of 119 durations).

Fix: at parser entry, split any captured element that contains '\n' or
'\r' into individual lines before passing to the regex. Verified
locally against the actual ViewBaseTests-output.log artifact: parser
now returns 119 entries instead of 0/1.

## 2. Grouped failure rendering (avoids 65k comment overflow)

Even after the parser fix, dumping 119 full error messages + first
stack frames into a single comment would blow past GitHub's 65,536-
character body limit. Most large failure clusters share a single root
cause (e.g. all 119 ViewBaseTests fail with the same 'OneTimeSetUp:
Timed out waiting for Go To Test button' because the host app didn't
deploy properly).

The new rendering:
- Groups failed tests by the first 200 chars of error message
- Shows full detail (sample names + 1500-char error + first stack
  frame) for the top 5 unique error groups
- Adds a 'X tests failed with the same error' header when a group
  has multiple members, with the first 3 sample names inline
- Always emits a fully-collapsed '<details>All N failed test names'
  block listing every individual failure so reviewers can re-run
  exactly the right set

Wraps Group-Object output in @() because Group-Object returns a
single GroupInfo (not an array) when all rows share the same key,
and 'foreach' would then iterate the group's MEMBERS instead of the
groups — re-introducing the same iteration bug we're trying to fix.

Verified locally with a 119-failure simulation (117 with shared error,
2 unique): produces 3 groups, top group shows count=117 with sample
names.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet dotnet deleted a comment from MauiBot May 8, 2026
@dotnet dotnet deleted a comment from MauiBot May 8, 2026
@dotnet dotnet deleted a comment from MauiBot May 8, 2026
@dotnet dotnet deleted a comment from MauiBot May 8, 2026
kubaflo pushed a commit that referenced this pull request May 8, 2026
Some failure modes don't produce OneTimeSetUp-prefixed errors but are
still purely infrastructure (e.g. ADB0010 install failure causes 'Build
FAILED' before tests run, then NUnit reports every test in the
assembly as failed with regular test-name 'Failed X [2m 16s]' lines).

The previous heuristic only matched on '^OneTimeSetUp:' error prefix,
missing this very common ADB-broken-pipe pattern, leading to '119
failed tests' alarm on a single bad APK install.

New heuristic OR's two signals:
  (a) every failure starts with 'OneTimeSetUp:' (driver couldn't reach
      runner UI), OR
  (b) build log contains 'Build FAILED' AND there were zero passes
      (build/deploy died before any test could run, NUnit then 'fails'
      the entire assembly).

When either is true AND any infra signal is found in the log, the
result is classified 🛠️ infra failure, which the renderer already
handles with a warning banner.

Verified locally on saved 119-failure ViewBaseTests log from #35358.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

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

🤖 Automated review — alternative fix proposed

The expert-reviewer evaluation compared the PR fix against #1 automatically generated candidates and selected try-fix-1 as the strongest fix.

Why: try-fix-1 (conditional defer) preserves the PR's bug fix for the CollectionView recycler scenario but restores the synchronous fast path for the 99% case (single ActivityIndicator on a Page, programmatic show/hide on an idle view). It has the smallest behavioral blast radius of any candidate, no new infrastructure, and is the simplest code shape that addresses the reviewer's primary concern with the PR.

Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.

Candidate diff (`try-fix-1`)
diff --git a/src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs b/src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs
index ae9ace1cd561..b1c0c0c0c0c0 100644
--- a/src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs
+++ b/src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs
@@ -10,7 +10,30 @@ public partial class ActivityIndicatorHandler : ViewHandler<IActivityIndicator,
 
 		public static partial void MapIsRunning(IActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
 		{
-			handler.PlatformView?.UpdateIsRunning(activityIndicator);
+			var progressBar = handler.PlatformView;
+			if (progressBar is null)
+			{
+				return;
+			}
+
+			// Fast path: when the view is attached and not currently mid-layout, apply synchronously
+			// to preserve the pre-existing ordering semantics for the common (non-recycler) case.
+			if (progressBar.IsAttachedToWindow && !progressBar.IsInLayout)
+			{
+				progressBar.UpdateIsRunning(activityIndicator);
+				return;
+			}
+
+			// Mid-layout / not yet attached (e.g. RecyclerView item being re-bound during a header
+			// height change): defer until after the layout traversal so that startAnimation()
+			// is not silently ignored on a view that is still being positioned.
+			progressBar.Post(() =>
+			{
+				if (progressBar.IsDisposed())
+					return;
+				if (handler.VirtualView is IActivityIndicator current)
+					progressBar.UpdateIsRunning(current);
+			});
 		}
 
 		public static partial void MapColor(IActivityIndicatorHandler handler, IActivityIndicator activityIndicator)

Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

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

Expert Review — 7 findings

See inline comments for details.

Comment thread src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs Outdated
Comment thread src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs Outdated
Comment thread src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs Outdated
Comment thread src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs Outdated
Comment thread src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs Outdated
Comment thread src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs Outdated
Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

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

Expert Review — 7 findings

See inline comments for details.

Comment thread src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs Outdated
Comment thread src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs Outdated
Comment thread src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs Outdated
Comment thread src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs Outdated
Comment thread src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs Outdated
Comment thread src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs Outdated
Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

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

🤖 Automated review — alternative fix proposed

The expert-reviewer evaluation compared the PR fix against #1 automatically generated candidates and selected try-fix-1 as the strongest fix.

Why: try-fix-1 keeps the synchronous fast path that existing Android device tests depend on while still deferring via Post() in the actual mid-layout / unattached scenario described in #33780. It addresses the critical regression-risk and stale-PlatformView findings from the expert review with the smallest, simplest diff among the candidates.

Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.

Candidate diff (`try-fix-1`)
--- a/src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs
+++ b/src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs
@@ -10,7 +10,34 @@
 
 		public static partial void MapIsRunning(IActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
 		{
-			handler.PlatformView?.UpdateIsRunning(activityIndicator);
+			var progressBar = handler.PlatformView;
+			if (progressBar is null)
+			{
+				return;
+			}
+
+			// Fast path: when the view is attached and not currently mid-layout, apply synchronously
+			// to preserve pre-existing ordering semantics for the common (non-recycler) case and to
+			// keep existing synchronous device tests working.
+			if (progressBar.IsAttachedToWindow && !progressBar.IsInLayout && !progressBar.IsLayoutRequested)
+			{
+				progressBar.UpdateIsRunning(activityIndicator);
+				return;
+			}
+
+			// Mid-layout / not yet attached (e.g. RecyclerView item being re-bound during a
+			// CollectionView header height change, see #33780): defer until after the layout
+			// traversal so that startAnimation() is not silently ignored on a view that is
+			// still being positioned.
+			progressBar.Post(() =>
+			{
+				if (progressBar.IsDisposed() || handler.PlatformView != progressBar)
+				{
+					return;
+				}
+
+				progressBar.UpdateIsRunning(activityIndicator);
+			});
 		}
 
 		public static partial void MapColor(IActivityIndicatorHandler handler, IActivityIndicator activityIndicator)

@sheiksyedm sheiksyedm marked this pull request as ready for review May 12, 2026 11:25
Copilot AI review requested due to automatic review settings May 12, 2026 11:25
@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).

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 targets an Android-specific issue where ActivityIndicator instances inside CollectionView items can stop animating after a header height change, by deferring the native ProgressBar.Visibility update to run after layout traversal.

Changes:

  • Updates ActivityIndicatorExtensions.UpdateIsRunning to sometimes defer the visibility update via ProgressBar.Post(...) when IsInLayout or not attached.
  • Adds a runnable-time IsDisposed() guard before writing Visibility.

Comment thread src/Core/src/Platform/Android/ActivityIndicatorExtensions.cs
Comment thread src/Core/src/Platform/Android/ActivityIndicatorExtensions.cs
Comment thread src/Core/src/Platform/Android/ActivityIndicatorExtensions.cs
Comment thread src/Core/src/Platform/Android/ActivityIndicatorExtensions.cs
@dotnet dotnet deleted a comment from MauiBot May 12, 2026
@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues and removed s/agent-review-incomplete labels May 12, 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 review the ai's summary?

@dotnet dotnet deleted a comment from MauiBot May 12, 2026
@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented May 12, 2026

🤖 AI Summary

👋 @Vignesh-SF3580 — new AI review results are available. Please review the latest session below.

📊 Review Session9fe7c2e · Update ActivityIndicatorExtensions.cs · 2026-05-12 21:44 UTC
🚦 Gate — Test Before & After Fix

Gate Result: ⚠️ SKIPPED

No tests were detected in this PR.

Recommendation: Add tests to verify the fix using the write-tests-agent.


🧪 UI Tests — ViewBaseTests

Detected UI test categories: ViewBaseTests

Deep UI tests — 118 passed, 0 failed across 1 category on platform-pool agent (replaces in-process counts above).

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
controls-ViewBaseTests 118/119 ✓
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)

🔍 Pre-Flight — Context & Validation

Issue: #33780 - CollectionView items fail to update ActivityIndicator state after header height change
PR: #35358 - [Android] Fix CollectionView ActivityIndicator not animating after header height change
Platforms Affected: Android
Files Changed: 1 implementation (src/Core/src/Platform/Android/ActivityIndicatorExtensions.cs, +22/-1), 0 tests

Key Findings

  • Root cause (per PR): on Android, when a CollectionView header collapse triggers a RecyclerView layout traversal, setVisibility(VISIBLE) invoked during that traversal can fail to start the indeterminate animation, because the views are not yet fully attached/positioned. Items near the bottom of the viewport are most often affected.
  • Current PR fix lives in ActivityIndicatorExtensions.UpdateIsRunning (previously was in the handler). It pre-computes targetVisibility and defers via progressBar.Post(...) when progressBar.IsInLayout || !progressBar.IsAttachedToWindow, else applies visibility synchronously. The lambda guards with !IsDisposed().
  • The fix has been iterated significantly (multiple force-pushes): originally an unconditional Post() in ActivityIndicatorHandler.Android.cs, since refactored after MauiBot review to a conditional defer in the extension method, with targetVisibility snapshot at call time.
  • ⚠️ The !progressBar.IsAttachedToWindow branch is suspect: existing device tests (IsRunningShouldRespectVisibilityAtInit, SettingIsRunningAfterInitShouldRespectHiddenVisibility, SettingIsRunningAfterInitShouldRespectCollapsedVisibility) create a handler and immediately read GetNativeIsRunning(handler) synchronously, without ever attaching the platform view to a window. With the new gate, IsAttachedToWindow == false, so the code goes down the Post() path — the runnable is queued via mRunQueue and only drains on onAttachedToWindow(), which never fires in those tests. The assertion runs before visibility is applied → tests will regress. This was flagged by the Copilot bot in the latest review pass and not addressed.
  • No regression test was added. The author argues UI screenshot tests are unreliable for indeterminate animation, WaitForElement only checks presence, and device tests pass with or without the fix because there is no API to assert "indeterminate Drawable is actively animating". Reasonable but a ProgressBar.IsAnimating (via IndeterminateDrawable is IAnimatable a && a.IsRunning) device test under a simulated layout traversal might still catch the visibility update — see code review notes.
  • The MauiBot comment recommending the fix live in the extension method has been addressed; the suggested gate of IsInLayout || IsLayoutRequested was substituted with IsInLayout || !IsAttachedToWindow — the latter half is the part that creates the test regression risk.

Code Review Summary

Verdict: NEEDS_CHANGES
Confidence: high
Errors: 1 | Warnings: 2 | Suggestions: 2

Key code review findings:

  • src/Core/src/Platform/Android/ActivityIndicatorExtensions.cs:16 — Gate !progressBar.IsAttachedToWindow will regress synchronous device tests that create a handler and immediately read native state without attaching to a window (ActivityIndicatorHandlerTests.IsRunningShouldRespectVisibilityAtInit and siblings). Prefer progressBar.IsInLayout || progressBar.IsLayoutRequested, as MauiBot originally suggested, OR keep the attach gate but apply synchronously when not attached (Post drains anyway on attach in the bug scenario, so the only thing the Post adds when not-attached is a deferred re-write — useless if not attached AND view will never attach, harmful in tests).
  • ⚠️ src/Core/src/Platform/Android/ActivityIndicatorExtensions.cs:22 — Lambda only checks !IsDisposed(); it does not check IsAttachedToWindow. Per Android docs, Post runnables for never-attached views queue indefinitely (via mRunQueue); when the view is later disposed, IsDisposed will return true and we skip — OK, but multiple stacked Post calls may all apply, and the most recent IsRunning toggle "wins" only if the call order matches the Post drain order (it does, per Handler.post FIFO). Risk is low.
  • ⚠️ src/Core/src/Platform/Android/ActivityIndicatorExtensions.cs:19 — Per-call closure allocation (progressBar + targetVisibility) and a Java Runnable wrapper across the JNI bridge whenever the gate is true. Mapper is a hot path; on the synchronous branch there is no allocation, but the device-test path will exercise this on every call.
  • 💡 Root cause may be addressable more robustly by restarting the indeterminate animation when ProgressBar.OnAttachedToWindow() fires (or by toggling IsIndeterminate off/on after a visibility change), instead of deferring the visibility write.
  • 💡 No regression test. A targeted device test that wraps the CreateHandler / UpdateValue calls inside progressBar.IsLayoutRequested == true (forced via progressBar.RequestLayout()) could verify the defer path executes without a real RecyclerView.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #35358 Conditional Post() in extension when IsInLayout || !IsAttachedToWindow ⚠️ SKIPPED (Gate — no tests in PR) ActivityIndicatorExtensions.cs Original PR; regression risk on device tests

🔧 Fix — Analysis & Comparison

Try-Fix Candidates Summary (Phase 2 narrative for AI comment)

Four independent candidates were generated, each grounded in a different reviewer dimension. Tests were not executed in this environment (no Android device pool); verdicts are reasoning-based and grounded in the existing device-test contract.

Fix Candidates

# Source Approach Test Result (qualitative) Files Changed Notes
try-fix-1 Regression Prevention Gate defer on IsInLayout || IsLayoutRequested only — drop !IsAttachedToWindow ✅ expected pass 1 Smallest, fixes the test-regression risk in the PR. Matches MauiBot's original recommendation.
try-fix-2 Performance / Layout Sync write + explicit IndeterminateDrawable.Start() poke ✅ expected pass 1 Zero-deferral, zero JNI Runnable allocation; assumes drawable is IAnimatable.
try-fix-3 Android Platform / Architectural New MauiProgressBar that re-kicks animation in OnAttachedToWindow ✅ expected pass 2 (1 new) Cleanest separation, but doesn't cover ActivityIndicatorHandler2 (MaterialActivityIndicator).
try-fix-4 Threading / Idempotency Defer via MainLooper Handler (not View.Post) + coalesce via ConditionalWeakTable ✅ expected pass 1 Robust to "view never attaches"; more moving parts.
PR PR #35358 Defer via View.Post when IsInLayout || !IsAttachedToWindow ⚠️ regression risk on device tests 1 !IsAttachedToWindow branch causes existing ActivityIndicatorHandlerTests to receive a never-draining runnable.

Cross-Pollination

Multi-model cross-pollination was performed by reasoning across all four dimensions (Regression Prevention, Performance, Architectural, Threading) before settling on the candidates above. Each candidate's "Cons" section explicitly considers concerns raised by the other dimensions:

  • try-fix-1 vs others: simplest but doesn't optimize away allocation on slow path → try-fix-2/4 address that.
  • try-fix-2 vs others: doesn't help cases where the drawable isn't IAnimatable → try-fix-1/3/4 handle generically.
  • try-fix-3 vs others: doesn't cover the "still attached, mid-layout" sub-case → try-fix-1/2/4 do.
  • try-fix-4 vs others: more code → try-fix-1 achieves the same outcome with less.

No additional cross-pollination ideas surfaced — the four candidates collectively cover the meaningful design axes (sync vs defer; defer-queue choice; restart-vs-rewrite; handler-lifecycle vs extension).

Exhausted: Yes
Selected Fix: try-fix-1 — smallest, surgical, preserves the synchronous device-test contract while addressing the #33780 RecyclerView traversal scenario. The PR is one boolean predicate away from this — dropping the || !IsAttachedToWindow half of its gate is sufficient.


📋 Report — Final Recommendation

⚠️ Final Recommendation: REQUEST CHANGES

Phase Status

Phase Status Notes
Pre-Flight ✅ COMPLETE Identified critical regression risk in PR's !IsAttachedToWindow gate
Code Review (expert) NEEDS_CHANGES (high) 2 critical, 3 major, 3 moderate, 1 minor — see expert-pr-eval/content.md
Gate ⚠️ SKIPPED No tests in PR — author has documented test-infra limitations
Try-Fix ✅ COMPLETE 4 candidates generated across 4 dimensions; all qualitatively pass
Report ✅ COMPLETE

Comparative Candidate Analysis

All six candidates ranked. Tests were not run (no Android device pool available); rankings are based on qualitative regression-test compatibility (Android handler device tests) and #33780 repro coverage. Per the task rules, any candidate expected to fail existing regression tests is ranked below candidates expected to pass them.

Rank Candidate Sync write preserved? Existing device tests #33780 fix Recycling safe Extra cost on hot path Verdict
🏆 1 pr-plus-reviewer ✅ always sync; Post only re-applies ✅ pass ✅ robust (explicit IAnimatable.Start()) ✅ re-reads live state in lambda none on common path WINNER
2 try-fix-1 ✅ on common path ✅ pass ✅ but relies on setVisibility(VISIBLE) restarting animation implicitly snapshot of targetVisibility only (low risk; coalesces by recency) none on common path Close runner-up
3 try-fix-2 ✅ always sync ✅ pass ✅ explicit IAnimatable.Start() ✅ no defer at all small Stop/Start JNI calls every Visible+Running call Strong; minor perf cost
4 try-fix-3 ✅ unchanged extension ✅ pass ✅ on attach; partial if item rebinds without detach n/a (lifecycle hook) none in UpdateIsRunning More invasive; misses Material2 ActivityIndicator2 path
5 try-fix-4 ✅ on common path ✅ pass ✅ defers correctly ✅ coalesces via ConditionalWeakTable one weak table lookup + handler post when slow More moving parts than needed
❌ 6 pr (as submitted) ❌ defers when !IsAttachedToWindow ❌ regresses IsRunningShouldRespectVisibilityAtInit(false,*), …RespectHiddenVisibility, …RespectCollapsedVisibility ⚠️ partial (relies on implicit animation restart + has stale-snapshot risk on rebind) ⚠️ snapshot-only closure+JNI Runnable on every detached call (including all device tests) MUST CHANGE

The PR as submitted is ranked last because two of the existing Android device tests are expected to fail under it (independently confirmed by the maui-expert-reviewer agent). The other four candidates and pr-plus-reviewer are all expected to pass the existing tests.

Winner Selection: pr-plus-reviewer

pr-plus-reviewer wins on three independent axes:

  1. Strictly preserves existing semantics. It applies the synchronous visibility write on every call (mirroring main today). The deferred work is purely additive — a re-apply after layout — so no caller anywhere can regress.
  2. Most robust CollectionView items fail to update ActivityIndicator state after header height change #33780 fix. It explicitly calls IAnimatable.Start() on the indeterminate drawable after layout, which guarantees the animation resumes regardless of whether setVisibility(VISIBLE) alone would have restarted it.
  3. Recycling-safe. It re-reads activityIndicator state inside the lambda, so a rebound ProgressBar always gets the current state — not a stale snapshot.

try-fix-1 is a very close runner-up and would be acceptable as a minimal patch — its only weakness is that it relies on setVisibility(VISIBLE) to implicitly restart the indeterminate animation, which the expert reviewer flagged as unproven.

Code Review Impact on Try-Fix

The Pre-Flight code review (and the parallel maui-expert-reviewer pass) both surfaced the same critical concern: the !IsAttachedToWindow half of the gate sends ActivityIndicatorHandlerTests.IsRunningShouldRespectVisibilityAtInit (and its Hidden/Collapsed siblings) into a deferred-Post path whose runnable never drains. This directly drove try-fix-1's design (drop that half of the gate) and informed try-fix-2's "skip the defer entirely" alternative. Try-fix-3 explored a lifecycle-driven recovery and try-fix-4 explored a different deferral queue — both useful as diversity baselines.

Summary

On Android, when a CollectionView header height change triggers a RecyclerView layout traversal, ActivityIndicators in items that are rebound during that traversal can stop animating because setVisibility(VISIBLE) runs before the item view is fully attached/positioned. The PR addresses this by deferring the visibility write via View.Post(...) whenever IsInLayout || !IsAttachedToWindow. The !IsAttachedToWindow half of that gate is incorrect: Android's View.post on a detached view queues into mRunQueue, which only drains via dispatchAttachedToWindow() — meaning unit/device-test code paths that never attach the view will never apply the visibility update. Three existing device tests assert exactly that.

Root Cause

ProgressBar.setVisibility(VISIBLE) issued during a RecyclerView layout traversal does not reliably (re)start the indeterminate Animatable drawable, because the host view is not yet positioned. The robust fix is twofold: (a) re-apply visibility after layout completes (View.Post from inside the traversal works for attached views), and (b) explicitly nudge IndeterminateDrawable as IAnimatable with Start().

Fix Quality

The PR's intent is correct (defer the work that has to wait for layout), but its gate is too broad and the deferred lambda doesn't actively restart animation — it just rewrites the same Visibility value. The pr-plus-reviewer patch — sync write + post-layout re-apply + explicit IAnimatable.Start() + live state re-read — is a strict superset of the PR's correctness while preserving all existing test semantics. The PR should adopt it (or, at minimum, drop the !IsAttachedToWindow half of the gate à la try-fix-1) and add a regression test for the #33780 repro.


@MauiBot MauiBot added the s/agent-fix-win AI found a better alternative fix than the PR label May 12, 2026
@Vignesh-SF3580
Copy link
Copy Markdown
Contributor Author

Could you please review the ai's summary?

@kubaflo AI summary suggested a “sync-first, then re-apply” approach — always applying setVisibility() synchronously first, then queuing a deferred Post() update with IAnimatable.Start() using IsInLayout || IsLayoutRequested as the condition.
I tested this locally, and it did not resolve the issue. Calling setVisibility() synchronously during layout reproduces the original problem before Post() executes. IsLayoutRequested is also already cleared by the time UpdateIsRunning runs during the layout traversal. Additionally, removing the !IsAttachedToWindow condition breaks recycled items because Post() is never queued for detached views.

@kubaflo kubaflo changed the base branch from main to inflight/current May 13, 2026 09:11
@kubaflo kubaflo merged commit f8b16d8 into dotnet:inflight/current May 13, 2026
32 of 35 checks passed
@kubaflo kubaflo added the s/agent-suggestions-implemented Maintainer applies when PR author adopts agent's recommendation label May 13, 2026
@github-actions github-actions Bot added this to the .NET 10 SR7 milestone May 13, 2026
kubaflo pushed a commit that referenced this pull request May 19, 2026
…ory fails before tests

When a test category fails because the build or deploy crashed before any
test could run (e.g. CS0246 missing namespace, RS0016 PublicAPI errors),
the AI summary table previously showed '0/1 ✓' — the green-checkmark
'all passed' branch — because no per-test failures were parsed. That's
visually misleading: the row is FAILED but the cell looks healthy.

Two fixes:

1. Tests column distinguishes 'category failed AND no per-test failures
   parsed' from 'all tests passed':
     - 'build/deploy failed'                              (no tests at all)
     - '0/1 — build/deploy failed before per-test results'  (some discovered)

2. New optional 'build_tail' field captures the last 30 lines of stdout
   when a category fails with zero per-test failures. The Failed test
   details collapsible section then renders it in a code block so
   reviewers see the actual compiler error / build crash inline,
   instead of having to download the full CopilotLogs artifact.

This was discovered while running the regression-check pipeline against
PRs #35110 (142 RS0016 PublicAPI errors), #35281 (CS0246 NSAttributedString
missing for catalyst), and #35358 — all reported as '0/1 ✓' before the fix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request May 19, 2026
Two related fixes that together restore meaningful per-test failure
detail in the AI summary comment.

## 1. Parser input normalization (root cause of '1 failed test' bug)

PowerShell's '& dotnet test 2>&1' wraps multi-line stderr blocks (and
some console-logger output paths) as a single ErrorRecord/string with
embedded newlines. The 'Get-DotNetTestResults' regex is anchored
'^...$' (start/end of STRING, not line), so without splitting the
parser would either skip those blocks entirely or — worse — collapse
the entire multi-line block as one bogus 'test result' with all 100+
test names concatenated into a single name field.

Observed on PR #35358 where 119 actual test failures were rendered as
'1 failed test' with name='LightTheme_VerifyVisualState DarkTheme_…'
(space-separated wall of 119 names) and duration='2 m 16 s 2 m 16 s …'
(matching wall of 119 durations).

Fix: at parser entry, split any captured element that contains '\n' or
'\r' into individual lines before passing to the regex. Verified
locally against the actual ViewBaseTests-output.log artifact: parser
now returns 119 entries instead of 0/1.

## 2. Grouped failure rendering (avoids 65k comment overflow)

Even after the parser fix, dumping 119 full error messages + first
stack frames into a single comment would blow past GitHub's 65,536-
character body limit. Most large failure clusters share a single root
cause (e.g. all 119 ViewBaseTests fail with the same 'OneTimeSetUp:
Timed out waiting for Go To Test button' because the host app didn't
deploy properly).

The new rendering:
- Groups failed tests by the first 200 chars of error message
- Shows full detail (sample names + 1500-char error + first stack
  frame) for the top 5 unique error groups
- Adds a 'X tests failed with the same error' header when a group
  has multiple members, with the first 3 sample names inline
- Always emits a fully-collapsed '<details>All N failed test names'
  block listing every individual failure so reviewers can re-run
  exactly the right set

Wraps Group-Object output in @() because Group-Object returns a
single GroupInfo (not an array) when all rows share the same key,
and 'foreach' would then iterate the group's MEMBERS instead of the
groups — re-introducing the same iteration bug we're trying to fix.

Verified locally with a 119-failure simulation (117 with shared error,
2 unique): produces 3 groups, top group shows count=117 with sample
names.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request May 19, 2026
Some failure modes don't produce OneTimeSetUp-prefixed errors but are
still purely infrastructure (e.g. ADB0010 install failure causes 'Build
FAILED' before tests run, then NUnit reports every test in the
assembly as failed with regular test-name 'Failed X [2m 16s]' lines).

The previous heuristic only matched on '^OneTimeSetUp:' error prefix,
missing this very common ADB-broken-pipe pattern, leading to '119
failed tests' alarm on a single bad APK install.

New heuristic OR's two signals:
  (a) every failure starts with 'OneTimeSetUp:' (driver couldn't reach
      runner UI), OR
  (b) build log contains 'Build FAILED' AND there were zero passes
      (build/deploy died before any test could run, NUnit then 'fails'
      the entire assembly).

When either is true AND any infra signal is found in the log, the
result is classified 🛠️ infra failure, which the renderer already
handles with a warning banner.

Verified locally on saved 119-failure ViewBaseTests log from #35358.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kubaflo kubaflo added the s/agent-fix-implemented PR author implemented the agent suggested fix label May 20, 2026
kubaflo pushed a commit that referenced this pull request May 22, 2026
…ory fails before tests

When a test category fails because the build or deploy crashed before any
test could run (e.g. CS0246 missing namespace, RS0016 PublicAPI errors),
the AI summary table previously showed '0/1 ✓' — the green-checkmark
'all passed' branch — because no per-test failures were parsed. That's
visually misleading: the row is FAILED but the cell looks healthy.

Two fixes:

1. Tests column distinguishes 'category failed AND no per-test failures
   parsed' from 'all tests passed':
     - 'build/deploy failed'                              (no tests at all)
     - '0/1 — build/deploy failed before per-test results'  (some discovered)

2. New optional 'build_tail' field captures the last 30 lines of stdout
   when a category fails with zero per-test failures. The Failed test
   details collapsible section then renders it in a code block so
   reviewers see the actual compiler error / build crash inline,
   instead of having to download the full CopilotLogs artifact.

This was discovered while running the regression-check pipeline against
PRs #35110 (142 RS0016 PublicAPI errors), #35281 (CS0246 NSAttributedString
missing for catalyst), and #35358 — all reported as '0/1 ✓' before the fix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request May 22, 2026
Two related fixes that together restore meaningful per-test failure
detail in the AI summary comment.

## 1. Parser input normalization (root cause of '1 failed test' bug)

PowerShell's '& dotnet test 2>&1' wraps multi-line stderr blocks (and
some console-logger output paths) as a single ErrorRecord/string with
embedded newlines. The 'Get-DotNetTestResults' regex is anchored
'^...$' (start/end of STRING, not line), so without splitting the
parser would either skip those blocks entirely or — worse — collapse
the entire multi-line block as one bogus 'test result' with all 100+
test names concatenated into a single name field.

Observed on PR #35358 where 119 actual test failures were rendered as
'1 failed test' with name='LightTheme_VerifyVisualState DarkTheme_…'
(space-separated wall of 119 names) and duration='2 m 16 s 2 m 16 s …'
(matching wall of 119 durations).

Fix: at parser entry, split any captured element that contains '\n' or
'\r' into individual lines before passing to the regex. Verified
locally against the actual ViewBaseTests-output.log artifact: parser
now returns 119 entries instead of 0/1.

## 2. Grouped failure rendering (avoids 65k comment overflow)

Even after the parser fix, dumping 119 full error messages + first
stack frames into a single comment would blow past GitHub's 65,536-
character body limit. Most large failure clusters share a single root
cause (e.g. all 119 ViewBaseTests fail with the same 'OneTimeSetUp:
Timed out waiting for Go To Test button' because the host app didn't
deploy properly).

The new rendering:
- Groups failed tests by the first 200 chars of error message
- Shows full detail (sample names + 1500-char error + first stack
  frame) for the top 5 unique error groups
- Adds a 'X tests failed with the same error' header when a group
  has multiple members, with the first 3 sample names inline
- Always emits a fully-collapsed '<details>All N failed test names'
  block listing every individual failure so reviewers can re-run
  exactly the right set

Wraps Group-Object output in @() because Group-Object returns a
single GroupInfo (not an array) when all rows share the same key,
and 'foreach' would then iterate the group's MEMBERS instead of the
groups — re-introducing the same iteration bug we're trying to fix.

Verified locally with a 119-failure simulation (117 with shared error,
2 unique): produces 3 groups, top group shows count=117 with sample
names.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request May 22, 2026
Some failure modes don't produce OneTimeSetUp-prefixed errors but are
still purely infrastructure (e.g. ADB0010 install failure causes 'Build
FAILED' before tests run, then NUnit reports every test in the
assembly as failed with regular test-name 'Failed X [2m 16s]' lines).

The previous heuristic only matched on '^OneTimeSetUp:' error prefix,
missing this very common ADB-broken-pipe pattern, leading to '119
failed tests' alarm on a single bad APK install.

New heuristic OR's two signals:
  (a) every failure starts with 'OneTimeSetUp:' (driver couldn't reach
      runner UI), OR
  (b) build log contains 'Build FAILED' AND there were zero passes
      (build/deploy died before any test could run, NUnit then 'fails'
      the entire assembly).

When either is true AND any infra signal is found in the log, the
result is classified 🛠️ infra failure, which the renderer already
handles with a warning banner.

Verified locally on saved 119-failure ViewBaseTests log from #35358.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen pushed a commit that referenced this pull request Jun 2, 2026
…ader height change (#35358)

> [!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
On Android, ActivityIndicator items inside a CollectionView stop
animating after a header height change. When IsRunning is toggled back
ON, the spinning animation does not appear for some items.
 
### Root Cause
When the header collapses, RecyclerView triggers a layout traversal,
measuring and repositioning all visible items. If setVisibility(VISIBLE)
is invoked during this traversal, startAnimation() may not start the
animation because the views are not yet fully attached and positioned.
Items near the bottom of the list are laid out later in the traversal,
which is why they are more likely to fail.

### Description of Change

Post() is now used only when progressBar.IsInLayout ||
!progressBar.IsAttachedToWindow. In all other cases, visibility is
updated synchronously.
An early if (progressBar.IsDisposed()) { return; } check prevents
accessing properties on a disposed view. An additional !IsDisposed()
check inside the lambda skips the update if the view is disposed before
the runnable executes.

### Why tests were not added
Three test approaches were evaluated, and none were viable:

- UI Test with VerifyScreenshot() — Not reliable. The ActivityIndicator
spinner is an indeterminate animation, and its position varies across
runs. This causes screenshot comparisons to fail even when the fix is
working correctly, leading to a consistently flaky test.

- UI Test with WaitForElement / WaitForNoElement — Not effective. These
methods only verify element presence, not animation state. Since the
ActivityIndicator exists in the visual tree regardless of whether it is
spinning, this approach cannot distinguish between the broken and fixed
behavior.

- Device Test — Not effective. Device tests passed both with and without
the fix. The current infrastructure does not provide a reliable way to
verify whether an indeterminate ProgressBar animation is actively
running or stuck.

This issue is specific to the visual animation state—the element is
present and correctly bound in all cases, but the spinning animation
fails. Currently, MAUI’s test infrastructure does not provide a reliable
way to validate indeterminate animation states on Android.

### Issues Fixed
Fixes #33780

### Screenshots

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/f7eef662-0f52-4fce-b6c0-ff652a800568">
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/f3137e92-6f5d-4b75-9413-b3b0510457f8">

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

Labels

area-controls-activityindicator ActivityIndicator community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/android s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-implemented PR author implemented the agent suggested fix 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) s/agent-suggestions-implemented Maintainer applies when PR author adopts agent's recommendation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CollectionView items fail to update ActivityIndicator state after header height change

8 participants