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 Unloadedwhich is called after the page (component) is unloaded.

As a result, the view model implements IDisposablewhen the page instance is initialized, the model is placed in BindingContext and a handler is added to the page Unloadedon 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 nullif 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 IServiceCollectionwhich 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

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *