I am making some reusable ContentView
components in purely C#. In some cases, I want to subscribe to events to some of the elements I've added to the View. ie:
public class MyEntry : ContentView
{
private readonly Entry _entry;
public MyEntry()
{
_entry = new Entry();
_entry.Focused += EntryFocused;
}
private void EntryFocused(object? sender, FocusEventArgs e)
{
FocusCommand?.Execute(sender);
}
}
But I don't really see any Dispose
method to override, or any other relevant lifecycle methods for that matter. So how should I properly unsubscribe from such events to avoid creating memory leaks?
If I change my class to inherit IDisposable
, will the dispose method be called by the framework automatically?
I ended up having to write my own logic to dispose of IDisposable
views, which I manually call from my Navigation Service. If others read this and directly call the Navigation
property on your content pages instead of using a NavigationService
from your ViewModels, you'll have to find a way to hook into that.
In the NavigationService, I created this method:
private void DisposeChildElements(IView view)
{
if (view is Layout layout)
{
foreach (var child in layout.Children)
{
DisposeChildElements(child);
if (child is IDisposable disposable)
{
disposable.Dispose();
}
}
} else if (view is IContentView {Content: View viewContent})
{
DisposeChildElements(viewContent);
}
if (view is IDisposable disposableView)
{
disposableView.Dispose();
}
}
And then I call it in the GoBack method:
public async Task GoBackAsync()
{
var currentPage = Shell.Current.CurrentPage;
await Shell.Current.GoToAsync("..");
if (currentPage != Shell.Current.CurrentPage)
{
if (currentPage is ContentPage contentPage)
DisposeChildElements(contentPage.Content);
if (currentPage is IAsyncDisposable asyncDisposable)
await asyncDisposable.DisposeAsync();
else if (currentPage is IDisposable disposable)
disposable.Dispose();
if (currentPage.BindingContext is IAsyncDisposable vmAsyncDisposable)
await vmAsyncDisposable.DisposeAsync();
else if (currentPage.BindingContext is IDisposable disposable)
disposable.Dispose();
}
}
If you also have a PopToRoot method in your NavigationService, you'd also need to implement something similar to call the same for all pages being removed.
Another thing to note: If you have any pages registered as Singletons in your DI, then you'll also need special handling for that. I don't use Singleton pages, so it's not something I had to solve, but if I were to implement it, I'd just make an ISingletonPage
interface and inherit from it on your singleton pages. Then in GoBackAsync
, I'd check if the page is ISingletonPage
and skip the disposal if it is.