diff --git a/src/Controls/src/Core/Compatibility/Handlers/ListView/Windows/CellControl.cs b/src/Controls/src/Core/Compatibility/Handlers/ListView/Windows/CellControl.cs index 5fd98f5fc260..145a51ac231b 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/ListView/Windows/CellControl.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/ListView/Windows/CellControl.cs @@ -69,6 +69,8 @@ void OnUnloaded(object sender, RoutedEventArgs e) Cell.SendDisappearing(); // 🚀 unsubscribe from propertychanged Cell.PropertyChanged -= _propertyChangedHandler; + // Allows the Cell to unsubscribe from Parent.PropertyChanged + Cell.Parent = null; } diff --git a/src/Controls/tests/DeviceTests/Elements/ListView/ListViewTests.cs b/src/Controls/tests/DeviceTests/Elements/ListView/ListViewTests.cs index 219afdc4b270..ffb9f825f0e5 100644 --- a/src/Controls/tests/DeviceTests/Elements/ListView/ListViewTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/ListView/ListViewTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; @@ -272,5 +273,82 @@ await CreateHandlerAndAddToWindow(layout, async _ => ValidatePlatformCells(listView); }); } + + [Fact("Cells Do Not Leak" +#if !WINDOWS + , Skip = "Skip for now on other platforms, due to how cells are recycled this does not pass." +#endif + )] + public async Task CellsDoNotLeak() + { + SetupBuilder(); + + var references = new List(); + var listView = new ListView + { + ItemTemplate = new DataTemplate(() => + { + var cell = new TextCell(); + references.Add(new(cell)); + return cell; + }) + }; + + await CreateHandlerAndAddToWindow(listView, async _ => + { + listView.ItemsSource = new[] { 1, 2, 3 }; + await Task.Delay(100); + ValidatePlatformCells(listView); + listView.ItemsSource = null; + await Task.Delay(100); + ValidatePlatformCells(listView); + }); + + await AssertionExtensions.WaitForGC(references.ToArray()); + foreach (var reference in references) + { + Assert.False(reference.IsAlive, "Cell should not be alive!"); + } + } + + [Fact("Cells Repopulate After Null ItemsSource")] + public async Task CellsRepopulateAfterNullItemsSource() + { + SetupBuilder(); + + List cells = null; + + var listView = new ListView + { + ItemTemplate = new DataTemplate(() => + { + var cell = new TextCell(); + cell.SetBinding(TextCell.TextProperty, new Binding(".")); + cells?.Add(cell); + return cell; + }) + }; + + await CreateHandlerAndAddToWindow(listView, async _ => + { + listView.ItemsSource = new[] { 1, 2, 3 }; + await Task.Delay(100); + ValidatePlatformCells(listView); + listView.ItemsSource = null; + await Task.Delay(100); + ValidatePlatformCells(listView); + + // Now track the new cells + cells = new(); + listView.ItemsSource = new[] { 4, 5, 6 }; + await Task.Delay(100); + ValidatePlatformCells(listView); + + Assert.Equal(3, cells.Count); + Assert.Equal("4", cells[0].Text); + Assert.Equal("5", cells[1].Text); + Assert.Equal("6", cells[2].Text); + }); + } } } \ No newline at end of file diff --git a/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs b/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs index b50506f98fab..74b06b3cd080 100644 --- a/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs +++ b/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Handlers.Compatibility; using Microsoft.Maui.Handlers; using Microsoft.Maui.Hosting; using Xunit; @@ -22,6 +23,7 @@ void SetupBuilder() handlers.AddHandler(); handlers.AddHandler(); handlers.AddHandler(); + handlers.AddHandler(); handlers.AddHandler(); handlers.AddHandler(); handlers.AddHandler();