Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make sure to unsubscribe from LayoutChanged on CV #26138

Merged
merged 2 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public abstract partial class ItemsViewHandler<TItemsView> : ViewHandler<TItemsV
protected override void DisconnectHandler(UIView platformView)
{
ItemsView.ScrollToRequested -= ScrollToRequested;
Controller?.DisposeItemsSource();
Controller?.Disconnect();
base.DisconnectHandler(platformView);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ public void UpdateLayout(ItemsViewLayout newLayout)
}
}

internal virtual void Disconnect()
{
DisposeItemsSource();
}

protected override void Dispose(bool disposing)
{
if (_disposed)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,37 @@ public StructuredItemsViewController(TItemsView structuredItemsView, ItemsViewLa
{
}


internal override void Disconnect()
{
base.Disconnect();

if (_headerViewFormsElement is not null)
{
_headerViewFormsElement.MeasureInvalidated -= OnFormsElementMeasureInvalidated;
}

if (_footerViewFormsElement is not null)
{
_footerViewFormsElement.MeasureInvalidated -= OnFormsElementMeasureInvalidated;
}

if (_headerUIView is MauiView hv)
{
hv.LayoutChanged -= HeaderViewLayoutChanged;
}

if (_footerUIView is MauiView fv)
{
fv.LayoutChanged -= FooterViewLayoutChanged;
}

_headerUIView = null;
_headerViewFormsElement = null;
_footerUIView = null;
_footerViewFormsElement = null;
}

protected override void Dispose(bool disposing)
{
if (_disposed)
Expand All @@ -36,30 +67,7 @@ protected override void Dispose(bool disposing)

if (disposing)
{
if (_headerViewFormsElement != null)
{
_headerViewFormsElement.MeasureInvalidated -= OnFormsElementMeasureInvalidated;
}

if (_footerViewFormsElement != null)
{
_footerViewFormsElement.MeasureInvalidated -= OnFormsElementMeasureInvalidated;
}

if (_headerUIView is MauiView hv)
{
hv.LayoutChanged -= HeaderViewLayoutChanged;
}

if (_footerUIView is MauiView fv)
{
fv.LayoutChanged -= FooterViewLayoutChanged;
}

_headerUIView = null;
_headerViewFormsElement = null;
_footerUIView = null;
_footerViewFormsElement = null;
Disconnect();
}

base.Dispose(disposing);
Expand Down Expand Up @@ -112,6 +120,11 @@ public override void ViewWillLayoutSubviews()

internal void UpdateFooterView()
{
if (_footerUIView is MauiView mvPrevious)
{
mvPrevious.LayoutChanged -= FooterViewLayoutChanged;
}

UpdateSubview(ItemsView?.Footer, ItemsView?.FooterTemplate, FooterTag,
ref _footerUIView, ref _footerViewFormsElement);
UpdateHeaderFooterPosition();
Expand All @@ -124,6 +137,11 @@ internal void UpdateFooterView()

internal void UpdateHeaderView()
{
if (_headerUIView is MauiView mvPrevious)
{
mvPrevious.LayoutChanged -= HeaderViewLayoutChanged;
}

UpdateSubview(ItemsView?.Header, ItemsView?.HeaderTemplate, HeaderTag,
ref _headerUIView, ref _headerViewFormsElement);
UpdateHeaderFooterPosition();
Expand Down
55 changes: 55 additions & 0 deletions src/Controls/tests/DeviceTests/Memory/MemoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,61 @@ await InvokeOnMainThreadAsync(async () =>
await AssertionExtensions.WaitForGC(viewReference, handlerReference, platformViewReference);
}

[Fact("CollectionView Header/Footer Doesn't Leak")]
public async Task CollectionViewHeaderFooterDoesntLeak()
{
SetupBuilder();

WeakReference viewReference = null;
WeakReference handlerReference = null;
WeakReference controllerReference = null;

var observable = new ObservableCollection<int> { 1 };
var navPage = new NavigationPage(new ContentPage { Title = "Page 1" });

await CreateHandlerAndAddToWindow(new Window(navPage), async () =>
{
var cv = new CollectionView
{
Footer = new VerticalStackLayout(),
Header = new VerticalStackLayout(),
ItemTemplate = new DataTemplate(() =>
{
var view = new Label
{
};
view.SetBinding(Label.TextProperty, ".");
return view;
}),
ItemsSource = observable
};
viewReference = new WeakReference(cv);
handlerReference = new WeakReference(cv.Handler);
await navPage.Navigation.PushAsync(new ContentPage
{
Content = cv
});
#if IOS
var controller = (cv.Handler as CollectionViewHandler).Controller;
controllerReference = new WeakReference(controller);
controller = null;
#else
controllerReference = new WeakReference(new object());
#endif
cv = null;
await navPage.Navigation.PopAsync();
});

await AssertionExtensions.WaitForGC(viewReference, handlerReference, controllerReference);
}

[Theory("Gesture Does Not Leak")]
[InlineData(typeof(DragGestureRecognizer))]
[InlineData(typeof(DropGestureRecognizer))]
Expand Down
Loading