Skip to content

SwipeItemView.Command leaks row views and command parameters through CanExecuteChanged #35498

Description

@AdamEssenmacher

Description

SwipeItemView.Command subscribes directly to ICommand.CanExecuteChanged when a command is assigned, but the subscription is only removed when the Command property changes. If the command is long-lived, for example from an app shell, service, toolbar model, or shared page view model, the command retains each SwipeItemView after the page is closed.

Retained path:

long-lived ICommand
  -> CanExecuteChanged
  -> SwipeItemView.OnCommandCanExecuteChanged
  -> SwipeItemView
  -> CommandParameter / Content / BindingContext
  -> row view model and page-local data

This is separate from #35481. That issue is rooted in stale SwipeItems.CollectionChanged / PropertyChanged handlers when SwipeItems are reused or replaced. This repro creates fresh SwipeItems for each row and does not replace them. The leak is caused by the SwipeItemView.Command subscription itself.

Repro Defaults

Setting Value
Pages per run 25
Swipe rows per page 40
Payload per row 128 KB
Total rows 1,000
Total simulated row payload 125 MiB

Observed Results

iOS: iPhone 17e simulator, iOS 26.4

Run Command subscribers Alive row view models Retained row payload Heap delta
Leaky SwipeItemView.Command 1,000 1,000 125 MiB ~148.7 MiB
Control SwipeItem.Command 0 0 0 MiB ~0.5 MiB
Mitigation: clear SwipeItemView.Command 0 0 0 MiB ~0.6 MiB

Android: Medium_Phone AVD, Android 7.0 / API 24

Run Command subscribers Alive row view models Retained row payload Heap delta
Leaky SwipeItemView.Command 1,000 1,000 125 MiB ~161.3 MiB
Control SwipeItem.Command 160 160 20 MiB negative after prior release
Mitigation: clear SwipeItemView.Command 0 160 20 MiB ~0.6 MiB

On Android, the platform/control baseline retained 160 rows, but the leaky SwipeItemView.Command path retained all 1,000 rows and all 125 MiB of row payload.

Steps to Reproduce

  1. Clone the repro branch:
git clone https://github.com/AdamEssenmacher/maui.git
cd maui
git checkout repro/swipeitemview-command-leak
  1. Build MAUI build tasks:
dotnet build Microsoft.Maui.BuildTasks.slnf
  1. Run the repro project:
dotnet run --project src/Controls/samples/SwipeItemViewCommandLeakRepro/SwipeItemViewCommandLeakRepro.csproj -f net10.0-ios

or:

dotnet run --project src/Controls/samples/SwipeItemViewCommandLeakRepro/SwipeItemViewCommandLeakRepro.csproj -f net10.0-android
  1. Leave the default settings:
Pages per run: 25
Swipe rows per page: 40
Payload KB per row: 128
Dwell ms per page: 40
  1. Tap Run leaky SwipeItemView.

  2. After the run completes, observe retained rows/payload/subscribers.

  3. Tap Run control SwipeItem.

  4. Tap Run mitigation.

Expected Result

The plain SwipeItem control and command-clearing mitigation should release the row payload after forced GC.

Actual Result

SwipeItemView.Command retains the row graph through the shared command’s CanExecuteChanged subscription.

Observed iOS result:

Run leaky SwipeItemView:
Command subscribers: 1,000
Alive row view models: 1,000
Retained row payload: 125 MiB

Run control SwipeItem:
Command subscribers: 0
Alive row view models: 0
Retained row payload: 0

Run mitigation:
Command subscribers: 0
Alive row view models: 0
Retained row payload: 0

For log-based collection, run with:

dotnet run --project src/Controls/samples/SwipeItemViewCommandLeakRepro/SwipeItemViewCommandLeakRepro.csproj -f net10.0-ios -p:ReproAutorun=true

or Android:

dotnet run --project src/Controls/samples/SwipeItemViewCommandLeakRepro/SwipeItemViewCommandLeakRepro.csproj -f net10.0-android -p:ReproAutorun=true

The autorun emits SWIPE_REPRO_RESULT lines.

Link to public reproduction project repository

https://github.com/AdamEssenmacher/maui/tree/repro/swipeitemview-command-leak/src/Controls/samples/SwipeItemViewCommandLeakRepro

Version with bug

10.0.60

Is this a regression from previous behavior?

Not sure, did not test other versions

Last version that worked well

Unknown/Other

Affected platforms

Android, iOS, I was not able test on other platforms

Affected platform versions

No response

Did you find any workaround?

No response

Relevant log output

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions