Implementing IDisposable in View Models in MAUI Projects
In the process of developing an application MAUI 7 (.NET Multi-Platform App UI + dotnet 7) using a design pattern MVVM (Model-view-ViewModel) I had a need to clear the resources occupied by view models (view model). The problem seems to be simple, it is enough to implement the interface IDisposable
in models. But everything turned out to be not so simple. All models are embedded on pages using a standard mechanism Dependency Injection. When using time dependencies (Transient
) there is no clear understanding of when the resources will be released. As a result, the program creates new instances of models for each request, but the old ones continue to hang in memory and take up resources.
In my case, the model resources should be released when the page lifecycle ends (ContentPage
) into which the models are embedded. To solve the problem, I decided to use an event handler Unloaded
which is called after the page (component) is unloaded.
As a result, the view model implements IDisposable
when the page instance is initialized, the model is placed in BindingContext
and a handler is added to the page Unloaded
on which the method is called IDisposable.Dispose
:
public SomePage(ISomePageViewModel model)
{
BindingContext = model;
InitializeComponent();
Unloaded += (object sender, EventArgs e) =>
{
(((ContentPage)sender).BindingContext as IDisposable)?.Dispose();
};
}
Casting BindingContext
V IDisposable
using the keyword as
allows you to get the value null
if the object is in BindingContext
does not implement an interface IDisposable
. And using the conditional operator ?.
avoids errors if the value is null
. So the method Dispose
will only be called if BindingContext
implements IDisposable
.
In order not to write the same code on every page, I made a small extension method IServiceCollection
which instantiates the page and adds an event handler to it Unloaded
:
internal static class ServiceCollectionExtensions
{
public static void AddPage<T>(this IServiceCollection serviceCollection) where T : ContentPage
{
serviceCollection.AddTransient(PageWithDisposableContext<T>);
}
private static T PageWithDisposableContext<T>(IServiceProvider serviceProvider) where T : ContentPage
{
var page = ActivatorUtilities.CreateInstance<T>(serviceProvider);
page.Unloaded += (object sender, EventArgs e) =>
{
(((ContentPage)sender).BindingContext as IDisposable)?.Dispose();
};
return page;
}
}
If you use the method to get the page instance IServiceProvider.GetRequiredService
or IServiceProvider.GetService
, then this can lead to cyclic calls to the page instantiation method. To solve this problem, you can use the method ActivatorUtilities.CreateInstance
.
As a result, adding pages to the container looks like this:
builder.Services.AddPage<MainPage>();
builder.Services.AddPage<SomePage>();
builder.Services.AddPage<EtcPage>();
An example project can be found at the following link