When developing a MAUI 7 application (.NET Multi-Platform App UI + dotnet 7) using the MVVM (Model-view-ViewModel) design pattern, I had to clear the resources occupied by view models. I am using Dependency Injection for all view model in my project. All models are implemented in the Transient
mode, i.e. creates a new instance of the model whenever needed. The implementation of the IDisposable
interface in the models did not give any result. 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, model resources should be released when the life cycle of the page (ContentPage
) on which the models are used ends. To solve the problem, I decided to use the Unloaded
event handler, 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 the BindingContext
and the Unloaded
handler is added to the page, in which the IDisposable.Dispose
method is called:
public SomePage(ISomePageViewModel model)
{
BindingContext = model;
InitializeComponent();
Unloaded += (object sender, EventArgs e) =>
{
(((ContentPage)sender).BindingContext as IDisposable)?.Dispose();
};
}
Casting the BindingContext
to IDisposable
using the as
keyword returns null if the object in the BindingContext
does not implement the IDisposable
interface. Using the conditional operator ?.
avoids errors if the value is null
. Thus, the Dispose
method will only be called if the BindingContext
implements IDisposable
.
In order not to write the same code on every page, I made a small IServiceCollection
extension method that instantiates the page and adds an Unloaded
event handler:
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;
}
}
I used the ActivatorUtilities.CreateInstance
method instead of IServiceProvider.GetRequiredService
or IServiceProvider.GetService
to avoid looping.
As a result, adding pages to the container looks like this:
builder.Services.AddPage<MainPage>();
builder.Services.AddPage<SomePage>();
builder.Services.AddPage<EtcPage>();
The sample project can be found at the following link:
https://github.com/alekseynemiro/knowledgebase/tree/master/csharp/maui/MauiViewModelDisposable
Aleksey Nemiro
2023-04-23
https://medium.com/maui-stories/how-to-implement-idisposable-in-view-models-in-maui-projects-7a78c766f90c