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
- Clone the repro branch:
git clone https://github.com/AdamEssenmacher/maui.git
cd maui
git checkout repro/swipeitemview-command-leak
- Build MAUI build tasks:
dotnet build Microsoft.Maui.BuildTasks.slnf
- 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
- Leave the default settings:
Pages per run: 25
Swipe rows per page: 40
Payload KB per row: 128
Dwell ms per page: 40
-
Tap Run leaky SwipeItemView.
-
After the run completes, observe retained rows/payload/subscribers.
-
Tap Run control SwipeItem.
-
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
Description
SwipeItemView.Commandsubscribes directly toICommand.CanExecuteChangedwhen a command is assigned, but the subscription is only removed when theCommandproperty changes. If the command is long-lived, for example from an app shell, service, toolbar model, or shared page view model, the command retains eachSwipeItemViewafter the page is closed.Retained path:
This is separate from #35481. That issue is rooted in stale
SwipeItems.CollectionChanged/PropertyChangedhandlers whenSwipeItemsare reused or replaced. This repro creates freshSwipeItemsfor each row and does not replace them. The leak is caused by theSwipeItemView.Commandsubscription itself.Repro Defaults
Observed Results
iOS: iPhone 17e simulator, iOS 26.4
SwipeItemView.CommandSwipeItem.CommandSwipeItemView.CommandAndroid:
Medium_PhoneAVD, Android 7.0 / API 24SwipeItemView.CommandSwipeItem.CommandSwipeItemView.CommandOn Android, the platform/control baseline retained 160 rows, but the leaky
SwipeItemView.Commandpath retained all 1,000 rows and all 125 MiB of row payload.Steps to Reproduce
git clone https://github.com/AdamEssenmacher/maui.git cd maui git checkout repro/swipeitemview-command-leakor:
Tap
Run leaky SwipeItemView.After the run completes, observe retained rows/payload/subscribers.
Tap
Run control SwipeItem.Tap
Run mitigation.Expected Result
The plain
SwipeItemcontrol and command-clearing mitigation should release the row payload after forced GC.Actual Result
SwipeItemView.Commandretains the row graph through the shared command’sCanExecuteChangedsubscription.Observed iOS result:
For log-based collection, run with:
or Android:
The autorun emits
SWIPE_REPRO_RESULTlines.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