dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.

Home Page:https://dot.net/maui

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Call Dispose() on Page and ViewModel when the page is popped if they implement IDisposable

ederbond opened this issue · comments

Description

It has been 2 years since I've opened this bug, and MSFT didn't come up with a proper solution, so to save your precious time consider completely ditching MAUI Shell for now and use the following library (based on PRISM v8) to workaround this issue:

https://github.com/naveasy/Naveasy

It would be amazing if MAUI framework could call the
Dispose method on the Page and also on it's ViewModel when the page gets removed from the navigation stack so we developers can gracefully dispose object that we have used and will no longer be used.
It would be even better if the same could be done on custom controls (Views) that we create.

Steps to Reproduce

Implement IDisposable on a given page;
Also Implement IDisposable on it's page's VM

Current behavior
The Dispose() method is never called after we remove the page from the navigation stack.

Expected behavior:
Dispose() to be called when the page is removed from the navigation stack;

Version with bug

Release Candidate 2 on .NET 8

Last version that worked well

Unknown/Other

Affected platforms

iOS, Android, Windows, macOS, Other (Tizen, Linux, etc. not supported by Microsoft directly)

Affected platform versions

Android 11 and iOS, Windows, Tizen, MacOS ...

Did you find any workaround?

No

Relevant log output

No response

Isn't the disposal of the Pages and ViewModels are controlled by DI in Maui ?

Also this one seems to be related #7329

@HobDev No, MAUI doesn't care if you implement IDisposable on your page or ViewModel.
It would make our lifes way more ease if MAUI could call the Dispose() method when the page is removed from the navigation stack, on both the Page and the ViewModel if they are implementing IDisposable, similar to what they do when you implement the IQueryAttributable interface on your Page or ViewModel to handle navigation parameters.

For now I'd recommend using the Loaded event

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

For now I'd recommend using the Loaded event

Could you please elaborate more about Loaded event? I mean, this is about releasing resources once the page is no longer needed (unsubscribe subscriptions), so I'm curious how the loaded event could help.

I used the Unloaded event on ContentPage which works a treat on Android, but on iOS it is too trigger happy.

Android appears to create an instance of ContentPage, trigger Loaded until the page is popped then trigger Unloaded. If the page participates in tabs, it is not unloaded if the user switches tabs as it is at the front of its logical stack of pages within the tab.

iOS is different, it is similar to the above except if I switch tabs, the page in the tab I am switching away from triggers Unload. When I switch back the same instance of ContentPage is used but a Loaded is triggered instead.

This can make it difficult to reason about the logical lifecycle of a page/control from a memory management point of view. If I have a viewmodel in the BindingContext of a page, I want it to hang around until the page is completely thrown away. I don't know of an event or other mechanism I can rely on to determine this. It does seem to invite memory leaks if one isn't very, very careful.

I'm going to have a play with the navigation side of things to see if there's any mileage there.

Has any further information been provided per the comment below?

#7329 (comment)

Came from #7329 (comment)
Registering page (and its ViewModel) as Transient when using Shell still does not create new instances upon visit.

I think it's clear we need to provide some more clarity on how this works exactly and we're working on it.

@jfversluis Is there any "clarification" now after 1 year?

I'm still in a position where I'm struggling to clean up view models as I can't detect when a page is truly finished with. Any guidance would be very welcome!

I found some answers in the source code and by doing some testing

  • Singleton lives forever (Application level)
  • Scoped means that the services are tight to the Window which for example on Android means the Activity
    Long story short: on Mobile this is equal to Singleton
  • Transient every time it is requested, creates a new instance: you need to dispose it yourself
    I tested this with Shell and it creates a new instance every time, obviously both Page and ViewModel need to be registered as Transient

Pay attention to this though: in contrast with ASP.NET Core you can request Scoped services from Singleton pages.
The singleton context has its own Scope which is different from the Scoped one.
I highly suggest to not use the Scoped lifetime as it works in unexpected ways (again, it shouldn't be possible to request a Scoped service from Singleton scope).

@albyrock87 So the MAUI framework never ever disposes any transient items for you? That seems insane. As @ederbond suggested, there should be something in the framework that disposes views when they are no longer in the navigation stack. It will take a lot of extra code and thought to correctly dispose manually all throughout the application.

I agree, it's seemingly a gaping hole but MAUI isn't the first to do this - WPF and others are the same. You tend to find it in UI frameworks and you end up with stuff like multiple implementations of "weak event handlers" etc.

We run into the event problem where a UI widget needs to listen to something but ended up getting around most of the time using the IMessenger stuff here.

However this does not help when UI components do stuff like create timers or other objects which need cleaning up. In this case you're basically SOL unless you use app-specific trickery to implement cleanup/disposal logic yourself. This is very, very error prone and even taking care throughout our app development, I'm fairly certain there'll be things we've missed.

This was obviously identified as an issue back with Xamarin, due to things such as the LifecycleEffect (see here) being created. As MAUI has rejected effects in favour of handler-based solutions this never came across, but amazingly no alternative was provided.

When I spotted the documentation on handlers I thought I'd found a solution as I assumed that you could correlate the removal of the handler to the "disposal" of the C#/MAUI backing object. However, as per my comment above this is not quite the case as on some platforms the handler can be detached but the C#/MAUI object re-used later, having another handler attached to the same instance. In addition I have seen handlers not be removed/set to null despite the object clearly not being used any longer. This behaviour is not specifically documented so I don't know if it is a bug with the framework or simply a documentation oversight.

It's all a bit confusing!! I think we just need guidance on how to tell when individual Views/Pages can be classed as "done with" so we can tear them down properly. To be honest I get the feeling this is an area the MAUI team have had trouble with themselves due to platform differences, and even they probably don't know all the nuances of it themselves.

@ksoftllc I will probably publish a tiny library to handle all of this in the proper way in a couple of weeks. I'm building something that sits on top of Microsoft implementation, without creating a whole new framework/pattern.
As soon as it's ready I'll post here.

@albyrock87 That sounds really interesting - I'd like to see how you've overcome the issues we've been discussing. If you need testers let me know.

commented

Another one for the seemingly endless backlog. They haven't released many MAUI updates the last few months, safe to say I'm a touch nervous.

Just pinged the team and I hope the come here and tell us a workaround or something... It's almost impossible to maintain the app state healthy if we can't dispose stuffs properly on VM. Specially when you're working with https://okazuki.jp/ReactiveProperty/features/ReactiveProperty.html which I love and I've been using since 2018 on all of my apps along with prism library which allowed me to correctly dispose my VM's. But now since prism is currently not working on MAUI with Shell I have no alternative.

Can you explain more about your scenario? What do you have on a viewmodel which requires explicit Disposal? Are you experiencing memory issues because of long-lived objects? Are you experiencing them with the latest .NET preview versions?

@hartez
I do make a heavy use of reactive programing using these 2 libraries:
https://github.com/dotnet/reactive
https://okazuki.jp/ReactiveProperty/

So on my ViewModels, I have a lot of IObservable and then inside of my VM's I do subscribe to these observables to react on changes between my VM and some business services that I have. The problem becomes when I have a Service that was registered as Singleton.

Follows a sample that reproduces the issue:

https://github.com/ederbond/PleaseDisposeMe/blob/master/README.md

Steps to reproduce the issue:

  1. Set a breakpoint on Page2ViewModel line 27
  2. Run the app on debug mode
  3. Click on the button called "Notify Now"
  4. You'll see that the label with the current time is updated on the screen. (cool)
  5. Click "Go To Page 2"
  6. You'll see the app will hit your break point a single time (cool)
  7. Go back to page 1
  8. Click on the button called "Notify Now"
  9. You'll see that your break point on Page2V is still getting called wich is bad cause that page was registered as Transient on MauiProgram
  10. Then Navigate again to Page 2
  11. You'll see that your break point will be hit one time again (cool)
  12. Go back to Page 1 and click on the button called "Notify Now"

You'll see that your breakpoint will stop 2x. And if you go back and forth you'll see that your break point will be hitted several times. That's because your Transient ViewModel is still live and listening for data coming from that observable. I my guess is that tha VM will never ever be disposed because it holds subscription from my SingletonService. In the past time of Xamarin.Forms it wasn't an issue for me cause I wasn't using Shell and I was using Prism Library which offers me an interface called IDestructible that when implemented on my VM was always calling a void Destroy() method for me. But since prism is not supporting Maui nor Shell (Dan Seigel already told me that they doesn't even have a plan to support shell in the future). So if someone decides to make Reactive Programing on Maui with Shell you're on a big trouble.
This examples clearly shows that without a proper easy and reliable extension point from the MAUI framework to dispose objects inside a VM your app will have serious memory leaks.

Note that this problem has nothing to do specifically with these 3rd party libraries that I'm using. The same will happen whenever you try to implement the Observable design pattern on your own. Cause your Observable object (Usually a Singleton service) will hold references to it's observers (which are hold by VM).

Having walking dead View Models listening to events from long lived observers not just cause memory leak, but also causes serious business logic issues and random crashed depending of the state it will eventually takes.

Can you explain more about your scenario? What do you have on a viewmodel which requires explicit Disposal? Are you experiencing memory issues because of long-lived objects? Are you experiencing them with the latest .NET preview versions?

Some of our views might create graphics objects for effects, animations etc. which allocate memory and require clean up when they're done with.

In addition, view models might bind up to singleton service objects, bind to native handlers/create native objects which require clean-up, create timers, background threads or other unmanaged support components.

Not stuff which affects "Hello World" level apps, but as soon as your app goes beyond the basics, these things crop up pretty quickly.

@RobTF I haven't had enough time to finish my work as I wanted and probably I won't have time in the next few months,
So for now, I'm making my repository public and eventually accepting PRs (I've reserved the namespace on NuGet but I haven't prepared the GitHub actions to publish the library).

This is my repo if you want to take a look: https://github.com/albyrock87/maux

So for now, if you need to solve this problem in your project you just need to:

  • Register your page and view model as Scoped
    • You can do the same for other services which needs to share the page lifetime
  • In your page's constructor ask for the view model public MyPage(MyPageViewModel viewModel) and set it to the BindingContext
  • Define a ScopedRouteFactory (see here]
  • Use it:
    • In case you're using Shell => Routing.RegisterRoute("MyRoute", ScopedRouteFactory<MyPage>.Instance);
    • In case you're using custom navigation => navigation.PushAsync(ScopedRouteFactory<MyPage>.Instance.GetOrCreate())

This way all of the Scoped dependencies will be disposed when the page is unloaded, either by the Shell or the custom navigation.

Please be aware that Shell root pages will never be disposed, because Shell keeps them "cached" in the background.

@albyrock87 Consider naming your library MauiX. Maux is hard to pronounce and looks clunky. Thanks!

@albyrock87 It's great that you've provided the community with a repo to address this issue, however I'm always reluctant to use a library which doesn't describe exactly what it is doing and why...

You've mentioned in the readme that the repo addresses the problem of developers not fully understanding how or why DI scoping should be used, and at this point that includes myself...

But it would be valuable if your readme could explain what, but more importantly why the repo is doing, so as to plug the gap of understanding.

@albyrock87 It's great that you've provided the community with a repo to address this issue, however I'm always reluctant to use a library which doesn't describe exactly what it is doing and why...

You've mentioned in the readme that the repo addresses the problem of developers not fully understanding how or why DI scoping should be used, and at this point that includes myself...

But it would be valuable if your readme could explain what, but more importantly why the repo is doing, so as to plug the gap of understanding.

It is irrelevant why Dispose needs to be called. The point here is that it doesn't work properly in MAUI. There are countless options for using Dispose. Consider for example 3D rendering component or map component. Or page which should save state to hard drive when you exit.

@albyrock87 It's great that you've provided the community with a repo to address this issue, however I'm always reluctant to use a library which doesn't describe exactly what it is doing and why...
You've mentioned in the readme that the repo addresses the problem of developers not fully understanding how or why DI scoping should be used, and at this point that includes myself...
But it would be valuable if your readme could explain what, but more importantly why the repo is doing, so as to plug the gap of understanding.

It is irrelevant why Dispose needs to be called. The point here is that it doesn't work properly in MAUI. There are countless options for using Dispose. Consider for example 3D rendering component or map component. Or page which should save state to hard drive when you exit.

I didn't ask why dispose needs to be called.

I asked why I should use the repo supplied by @albyrock87 to solve this problem and why.

Did you actually read my post before you got angry about it?

@ryanlpoconnell @janseris you don't have to use my repo at all :) especially because right now it is abandoned.
I just wanted to share some code which solves the issue which actually is just a few lines of code:
https://github.com/albyrock87/maux/blob/main/Maux.Mvvm/ScopedRouteFactory.cs
Anyway, I see that Shell navigation has many limitations that would frustrate me in a real world applications, so I'm not going to use that anymore.

Verified this issue with Visual Studio Enterprise 17.8.0 Preview 5.0. Can repro on windows platform.

This isn't really a bug; it would be more of enhancement to have better control over service resolution so that you could fully leverage Service Scopes.

Implementing the dispose pattern against various navigation scenarios doesn't really scale. What if someone wants to reuse a VM or Page? We can't decide ourselves when to call dispose. If you want to use the dispose pattern, then you have to tie your life cycles to something you control, not that we control.

If you register your components as scoped service, those types will survive inside the DI container even if we were to call Dispose ourselves. The only way for something that's registered as a scoped service and that implements Idisposable to get collected by the garbage collector is to dispose the service container that you created the service from.

https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines#disposal-of-services

One of the things we'd need to look at enhancing is the service resolution here

return services.GetService(template.Type) ?? Activator.CreateInstance(template.Type);

We could also look at adding settings to shell so navigation gets scoped, but this all becomes a bit tricky with the MS.EXt.DI container because it doesn't support child scopes.

We could also look at adding life cycle interfaces that you could sprinkle around that would give contextual information to your View Models.

Let's build this issue into a spec :-)

There are probably a few other factors at play here. For example, we have been fixing various memory leaks in our components which were causing Pages to not get Garbage Collected. If you have a scenario where a popped page isn't getting Garbage collected, please log an issue with a repro and we can figure out what's causing the page to pin in memory.

@PureWeen I don't care about child scopes on msft.ext.di
Just call Dispose() on Views and ViewModels that were registered as Transient AND implements IDisposable. So when I want to have a page + vm to be disposed when I navigate away from it I'll register it as transient, and when I want to have a page or vm to be reused I'll register it as singleton. Maui just need to call dispose on transient objects that implements IDisposable. That's it

This isn't really a bug; it would be more of enhancement to have better control over service resolution so that you could fully leverage Service Scopes.

Implementing the dispose pattern against various navigation scenarios doesn't really scale. What if someone wants to reuse a VM or Page? We can't decide ourselves when to call dispose. If you want to use the dispose pattern, then you have to tie your life cycles to something you control, not that we control.

If you register your components as scoped service, those types will survive inside the DI container even if we were to call Dispose ourselves. The only way for something that's registered as a scoped service and that implements Idisposable to get collected by the garbage collector is to dispose the service container that you created the service from.

https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines#disposal-of-services

One of the things we'd need to look at enhancing is the service resolution here

return services.GetService(template.Type) ?? Activator.CreateInstance(template.Type);

We could also look at adding settings to shell so navigation gets scoped, but this all becomes a bit tricky with the MS.EXt.DI container because it doesn't support child scopes.

We could also look at adding life cycle interfaces that you could sprinkle around that would give contextual information to your View Models.

Let's build this issue into a spec :-)

There are probably a few other factors at play here. For example, we have been fixing various memory leaks in our components which were causing Pages to not get Garbage Collected. If you have a scenario where a popped page isn't getting Garbage collected, please log an issue with a repro and we can figure out what's causing the page to pin in memory.

What does scoped service mean in MAUI? In ASP.NET Core scope is HTTP request but here?

In brief I agree with @ederbond for 99.9% of scenarios I just want transient vm's and pages.

But we have to be careful about what we mean by 'navigate away'... does this mean only popping from the nav stack or, does navigating away also mean pushing another page on top?

I would say that by default whilst the page is on the nav stack, even a transient vm should remain intact, so that when you navigate back the state is maintained.

I wonder if @PureWeen 's comment needs more attention as a fuller understanding would perhaps alleviate a lot of concerns / confusion, maybe... >
'There are probably a few other factors at play here. For example, we have been fixing various memory leaks in our components which were causing Pages to not get Garbage collected'

@PureWeen could you please provide more details about what should be happening, specifically are you saying a transient page and associated transient vm should be garbage collected once the page is popped from the nav stack (assuming the developer hasn't got any strong references of course)? or something else?

Because if you are saying this, would it not make sense to call Dispose on page / vm explicitly in this transient scenario by default, to avoid further confusion?

e.g. I have experienced problems having popped the page to the root, logging in as a new user, only to find a vm with the previous user's state intact :-/ ... yes you could argue a bunch of things I could do otherwise to avoid this, but the dispose on the vm being called would help me to avoid this kind of mess

could you please provide more details about what should be happening, specifically are you saying a transient page and associated transient vm should be garbage collected once the page is popped from the nav stack (assuming the developer hasn't got any strong references of course)? or something else?

Yes

Because if you are saying this, would it not make sense to call Dispose on page / vm explicitly in this transient scenario by default, to avoid further confusion?

AFAIK it's against DI guidance and not really possible to do this. For example, if you were to ctor resolve services on your VM, we'd have to track all the services resolved and see how they are resolved. I honestly don't even know how to query the DI system to know if a service is transient. If anyone has examples of this pattern in the wild for context that'd be helpful.

What does scoped service mean in MAUI? In ASP.NET Core scope is HTTP request but here?

The service you currently get is scoped to the Window, this is how we retrieve things like Dispatchers that are unique to each window. If you want to establish a scope yourself, then you'd create your own scope and dispose of it. Shell could probably use a few better hooks to let users wire in their own scopes though.

The best hooks for what folks want here would be Navigating/Navigated/Loaded/Unloaded/Appearing
And using those in whatever combination makes sense for your scenario.

e.g. I have experienced problems having popped the page to the root, logging in as a new user, only to find a vm with the previous user's state intact :-/ ... yes you could argue a bunch of things I could do otherwise to avoid this, but the dispose on the vm being called would help me to avoid this kind of mess

Still, there's really no way that I can think of derive enough accurate context across all apps to know when to call dispose. Maybe someone else wants this to happen? Also, calling Dispose more than once isn't really recommended https://learn.microsoft.com/en-us/visualstudio/code-quality/ca2202?view=vs-2022 which might happen in this scenario. What if someone reuses a transient?

@PureWeen I think we might be overcomplicating the problem.
If you don't want to rely on the .net build-in IDisposable interface then similar to what you guys have done when MAUI introduced the IQueryAttributable (ugly name by the way 😂), do create another interface like this:

    /// <summary>
    /// Interface for objects that require cleanup of resources when removed from the navigation stack.
    /// </summary>
    public interface IDestructible
    {
        /// <summary>
        /// This method allows cleanup of any resources used by your View/ViewModel 
        /// </summary>
        void Destroy();
    }

And then every time a given page is removed from the navigation stack, MAUI could check if the page/VM is implementing this interface and then MAUI call Destroy() on that Page or VM. That's it!

So it will be up to developers to decide if they want to clean-up something inside their Page and/or VM when they fells necessary.

And just for the record:
By "page removal of the navigation stack" I mean:

  1. When we navigates backward (not forward) of a given page.
  2. When we do absolute navigation, and when this occur All the intermediate pages present on the navigation stack should also be destroyed.

Yea that's basically what I meant by this comment.

We could also look at adding life cycle interfaces that you could sprinkle around that would give contextual information to your View Models.

Let's build this issue into a spec :-)

I don't think "Destroy" is the right idea here though. When pages are popped they might not be destroyed, Shell won't really be able to determine if a VM/V will be destroyed so we'd probably do something like "INavigationEvents" and that would have context that something is getting popped.

Any solution or workaround about this?

I don't know if this inline with this topic. In my situation I need to update again the user details after logout since if it's different user so the details would be different.

What I did is use the NavigationTo from the page.

<ContentPage
    x:Class="PSSuki.Views.SettingsPage"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:converters="clr-namespace:PSSuki.Converters"
    Title="SettingsPage" NavigatedTo="ContentPage_NavigatedTo">
    <ContentPage.Resources>
    ......

In the back I call the method in my ViewModel.

using PSSuki.ViewModels;

namespace PSSuki.Views;

public partial class SettingsPage : ContentPage
{
    private readonly SettingsViewModel vm;

    public SettingsPage(SettingsViewModel  vm)
    {
	InitializeComponent();
        this.vm = vm;
        BindingContext = this.vm;
    }

    private async void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e)
    {
        await vm.GetLoginDetailsASync();
    }
}

I hope this helps.

I don't know if this inline with this topic. In my situation I need to update again the user details after logout since if it's different user so the details would be different.

What I did is use the NavigationTo from the page.

<ContentPage
    x:Class="PSSuki.Views.SettingsPage"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:converters="clr-namespace:PSSuki.Converters"
    Title="SettingsPage" NavigatedTo="ContentPage_NavigatedTo">
    <ContentPage.Resources>
    ......

In the back I call the method in my ViewModel.

using PSSuki.ViewModels;

namespace PSSuki.Views;

public partial class SettingsPage : ContentPage
{
    private readonly SettingsViewModel vm;

    public SettingsPage(SettingsViewModel  vm)
    {
	InitializeComponent();
        this.vm = vm;
        BindingContext = this.vm;
    }

    private async void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e)
    {
        await vm.GetLoginDetailsASync();
    }
}

I hope this helps.

This misses the point of MVVM. Separating UI logic from the UI.
The UI ("View") should know exactly nothing about the logic that is controlling it. And the MVVM should know exactly nothing about the view except the public properties whose values it is modificating in the VM and the values are transferred to View thanks to the framework via binding using reflection and that's driving the changes in the UI.

@PureWeen have you ever used Prism with XF? I think PRISM Navigation system alongside its navigation aware c# interfaces should be a good source of inspiration to create a robust spec around this. Please take a look at INavigationAware, Indestructible, INavigatingTo, INavigatedTo, INavigatingFrom and IInitilize. These set of interfaces makes following MVVM so pleasing, I would love to have a clean and elegant navigation system like that built in Maui. And before someone asks me why should I just go and use prism? The answer is:

1st) Prism is now a paid product, this might be a problem for budget constrained projects.

2nd) Last time I've checked (around 3 months ago) it was not working with MAUI at all.

3rd) Prism team said they have no interest on AppShell and have no plans to support it.

@PureWeen have you ever used Prism with XF? I think PRISM Navigation system alongside its navigation aware c# interfaces should be a good source of inspiration to create a robust spec around this. Please take a look at INavigationAware, Indestructible, INavigatingTo, INavigatedTo, INavigatingFrom and IInitilize. These set of interfaces makes following MVVM so pleasing, I would love to have a clean and elegant navigation system like that built in Maui. And before someone asks me why should I just go and use prism? The answer is:

1st) Prism is now a paid product, this might be a problem for budget constrained projects.

2nd) Last time I've checked (around 3 months ago) it was not working with MAUI at all.

3rd) Prism team said they have no interest on AppShell and have no plans to support it.

@ederbond 1 up from me. Sounds like I'm facing the same issues as you except with WeakReferenceMessenger registrations, although I haven't tested it yet. Have you found a solution?

  • Hi @memis1970 , my current solution is to stay away from AppShell until msft fixes this gap. My suggestion it's to ignore shell and just use NavigationPage without shell with some custom Navigation System that I have created inspired by Prism Library, cause last time I've checked, prism was also far from stable to work with MAUI. @hartez @davidortinau @PureWeen IMO you guys must make this among the top priority.

We have a production Xamarin Forms app that uses Prism. With Xamarin support ending on May 1, 2024 that really puts us on a tight timeline (especially since Android 14 isn't going to be supported and it has at least two showstopper bugs). Not having this lifecycle management means that shell isn't a viable option. Prism isn't a viable option. That leave a custom implementation and even more work for the migrating to MAUI (which I still don't seem much benefit).

@ederbond Thanks for the reply. I did try without Shell and got all tied up with DI, MVVM, Navigation...then saw H.A.H's comment on this thread (https://stackoverflow.com/questions/76247721/how-to-create-a-non-shell-app-in-net-maui#comment134465515_76247721) and got put off trying. Coming from XF/Prism I just couldn't get it working the way I'm used to. Since then I've found this library matt-goldman/Maui.Plugins.PageResolver#25 which looks useful. Now in a bit of a quandry as I persevered with Shell and have more or less got my app working the way I need but I'm not keen. Feels like a square peg in a round hole. I'd love to see a demo solution on how to use a Flyout without Shell but with as much of the niceties of Prism as can be achieved. I can't be the only one. There must be thousands of us in this boat!

@chrisg32 Same here. A lot of my difficulties with Shell have been due to a lack of understanding about how to utilise it but I don't like it and it is restrictive. I'm still only a couple of weeks into using it so familarity is a factor.

Actually, I think they should just buy Prism....reward the 2 developers for their efforts and stick it in Maui.

I would also like this, for precisely the same use case of unsubscribing to observables in RX.NET.

Just thinking out loud here, but I think the navigation system would be responsible for disposing the page, not the ViewModel. The page would then be responsible for calling dispose on the ViewModel.

You could do this with a navigation extension method. Again just thinking out loud so don't know if or how well this would work (and would definitely need to be fleshed out a bit more).

public static class NavigationExtensions
{
    public static async Task PopDisposableAsync(this INavigation navigation)
    {
        if (navigation.NavigationStack.LastOrDefault() is IDisposable disposable)
        {
            disposable.Dispose();
        }

        await navigation.PopAsync();
    }
}

I haven't tested this, as I'm said just thinking out loud. I could implement something like this in my PageResolver library, although I think it's a little out of scope; possibly a good fit for the community toolkit? (cc: @bijington). I'm a bit busy preparing for NDC Sydney (and other commitments) but I could take a look at it after that.

Hey, I'm happy to join the conversation.

I appreciate the complexity around service lifetimes to make the framework calling Dispose a difficult thing to make safe for everyone. This makes the extension method sound like a good option for now.

I know in the past I've wrapped navigation logic into a class that handles more things and then also controlled when to call Dispose in a similar fashion.

I like that @PureWeen suggested we used this issue as a method to build the spec for the overall change here. The Toolkit is a place to test out ideas before they can potentially make it into MAUI. Shane did you have any thoughts on what you would like to see in MAUI eventually? Perhaps we can find a stepping stone towards that which could be added to the toolkit?

I don't care about the community toolkit! This is a critical and must have feature for MAUI that is missing. We shouldn't be forced to use community toolkit in order to get notified when a page has been removed/closed/removed from the navigation stack. MSFT must fix it on MAUI itself. It's a basic expected feature IMO.

Hey @PureWeen any news around this? Are we going to have to wait until .NET 100 for this?

I gotta be honest with you...there are far more pressing "THE TEAM MUST ABSOLUTELY DROP EVERYTHING AND ADD/FIX THIS FUNDAMENTAL FEATURE RIGHT NOW!!!" issues than this.

Suggesting the Community Toolkit isn't saying that's where the feature belongs and where it should remain. The turnaround on the MCT is a lot quicker than it is in the main platform, and can be a stepping-stone for some features into the main product too.

If I were you, I'd start caring about the Community Toolkit.

Hey there, I finally published my Nalu.Maui NuGet package with a comprehensive solution to this and other navigation problems.

You can read more and check it out here: https://nalu-development.github.io/nalu/

Anyway, if you prefer to stay with what MAUI offers out of the box, you can find a workaround for the disposable issue at the end of the readme (and obviously in the source code).

Cheers!

I am making a MAUI app that uses Shell. I don't use MVVM and I don't like it. When the app starts, the user have to make the login. When the user perform a logout, I want to destroy all Pages (ContentPage) and then navigate to the Login page, as if the app was just started. I managed to do this in Xamarin Forms without Shell, but now in MAUI I need to use the Shell menu, because the secondary menus aren't consistent and have bugs in all platforms. Is it possible to dispose the pages from the Shell as I wish?

@Auto72 you can handle Login even with Shell by:

  • hiding the ShellContent from the flyout with FlyoutItemIsVisible="False"
  • set the Shell.FlyoutBehavior="Disabled" in your LoginPage.xaml

But if you really don't want to use MVVM pattern, just know you can destroy pages manually by setting Content and ContentTemplate to null in each of the shell.Items[x][y][z] ShellContents.

I already use FlyoutBehavior.Disabled, thanks. What I need is to destroy the pages manually.
I tryed the following code, but I can't find .Content or .ContentTemplate in the item object.
🤔

if (Application.Current?.MainPage is not Shell shell) { return; }
foreach(var item in shell.Items)
{
	item.
}

@Auto72 this is what I meant with shell.Items[x][y][z]

foreach (var item in shell.Items)
        {
            foreach (var section in item.Items)
            {
                foreach (var content in section.Items)
                {
                    content.Content = null;
                    content.ContentTemplate = null;

The loop above works, thanks.
I found content.Content is always null for my pages.
Anyway, after that, if I try to navigate to the LoginPage, I get the following error:

await Current.GoToAsync("///login");

"No Content found for ShellContent, Title:My App Login, Route login"

Instaed of null, should I put new LoginPage();

@Auto72 after clearing you have to set new ones obviously.

Anyway, let's not make this thread a support page :)
Especially because you're trying to do something not provided and not documented by Microsoft - one of the reason I created Nalu.Maui package.

I'm calling Dispose by overriding OnParentSet in a base class for all my pages since popping is setting parent to null causing bindings to be reevaluated and I need Dispose to be called before that happens

        protected override void OnParentSet()
        {
            if (Parent == null)
            {
                Dispose();
                if (BindingContext is IDisposable bindingContextDisposable)
                {
                    bindingContextDisposable.Dispose();
                }
            }
            base.OnParentSet();
        }
System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
Microsoft.Maui.Controls.BindingExpression.BindingExpressionPart.TryGetValue(Object source, Object& value)
Microsoft.Maui.Controls.BindingExpression.ApplyCore(Object sourceObject, BindableObject target, BindableProperty property, Boolean fromTarget, SetterSpecificity specificity)
Microsoft.Maui.Controls.BindingExpression.Apply(Boolean fromTarget)
Microsoft.Maui.Controls.Binding.Apply(Boolean fromTarget)
Microsoft.Maui.Controls.BindableObjectExtensions.RefreshPropertyValue(BindableObject self, BindableProperty property, Object value)
Microsoft.Maui.Controls.VisualElement.Microsoft.Maui.Controls.IPropertyPropagationController.PropagatePropertyChanged(String propertyName)
Microsoft.Maui.Controls.Internals.PropertyPropagationExtensions.PropagatePropertyChanged(String propertyName, Element element, IEnumerable children)
Microsoft.Maui.Controls.VisualElement.Microsoft.Maui.Controls.IPropertyPropagationController.PropagatePropertyChanged(String propertyName)
Microsoft.Maui.Controls.Internals.PropertyPropagationExtensions.PropagatePropertyChanged(String propertyName, Element element, IEnumerable children)
Microsoft.Maui.Controls.VisualElement.Microsoft.Maui.Controls.IPropertyPropagationController.PropagatePropertyChanged(String propertyName)
Microsoft.Maui.Controls.Internals.PropertyPropagationExtensions.PropagatePropertyChanged(String propertyName, Element element, IEnumerable children)
Microsoft.Maui.Controls.VisualElement.Microsoft.Maui.Controls.IPropertyPropagationController.PropagatePropertyChanged(String propertyName)
Microsoft.Maui.Controls.Internals.PropertyPropagationExtensions.PropagatePropertyChanged(String propertyName, Element element, IEnumerable children)
Microsoft.Maui.Controls.VisualElement.Microsoft.Maui.Controls.IPropertyPropagationController.PropagatePropertyChanged(String propertyName)
Microsoft.Maui.Controls.Internals.PropertyPropagationExtensions.PropagatePropertyChanged(String propertyName, Element element, IEnumerable children)
Microsoft.Maui.Controls.VisualElement.Microsoft.Maui.Controls.IPropertyPropagationController.PropagatePropertyChanged(String propertyName)
Microsoft.Maui.Controls.Internals.PropertyPropagationExtensions.PropagatePropertyChanged(String propertyName, Element element, IEnumerable children)
Microsoft.Maui.Controls.VisualElement.Microsoft.Maui.Controls.IPropertyPropagationController.PropagatePropertyChanged(String propertyName)
Microsoft.Maui.Controls.Internals.PropertyPropagationExtensions.PropagatePropertyChanged(String propertyName, Element element, IEnumerable children)
Microsoft.Maui.Controls.VisualElement.Microsoft.Maui.Controls.IPropertyPropagationController.PropagatePropertyChanged(String propertyName)
Microsoft.Maui.Controls.Element.OnParentSet()
Microsoft.Maui.Controls.NavigableElement.OnParentSet()
Microsoft.Maui.Controls.Page.OnParentSet()
Microsoft.Maui.Controls.Element.SetParent(Element value)
Microsoft.Maui.Controls.Element.OnChildRemoved(Element child, Int32 oldLogicalIndex)
Microsoft.Maui.Controls.Element.RemoveLogicalChild(Element element, Int32 index)
Microsoft.Maui.Controls.Element.RemoveLogicalChild(Element element)
Microsoft.Maui.Controls.Platform.ModalNavigationManager.PopModalAsync(Boolean animated)
Microsoft.Maui.Controls.Shell.NavigationImpl.OnPopModal(Boolean animated)
Microsoft.Maui.Controls.ShellSection.PrepareCurrentStackForBeingReplaced(ShellNavigationRequest request, ShellRouteParameters queryData, IServiceProvider services, Nullable`1 animate, List`1 globalRoutes, Boolean isRelativePopping)
Microsoft.Maui.Controls.ShellSection.GoToAsync(ShellNavigationRequest request, ShellRouteParameters queryData, IServiceProvider services, Nullable`1 animate, Boolean isRelativePopping)
Microsoft.Maui.Controls.ShellNavigationManager.GoToAsync(ShellNavigationParameters shellNavigationParameters, ShellNavigationRequest navigationRequest)
Microsoft.Maui.Controls.Shell.NavigationImpl.OnPopModal(Boolean animated)
CadabraMobile.Services.Common.NavigationService.PopModal(Boolean animate)

#7329

Approaching 2 years ago @jfversluis mentioned providing more clarity on this issue was being worked on. Can someone direct me to this?

Thanks

@memis1970 after a lot of research I can tell you ShellContents work like this by design, that's because while using Tabs or TabBar, the content on each tab needs to stay alive.
Ok top of that each ShellSection keeps its own navigation stack, also by design.

@albyrock87 Thanks for the comment. This is true for the Flyout also. It's a major headache for me coming from Prism and being unable to get a Flyout Page working without Shell the way I need.

I've found this library https://github.com/BurkusCat/Burkus.Mvvm.Maui which has managed it but it's an absurd amount of hoops to jump through to implement the navigation.

Is there any progress, or at least any hope? Reminder: we just need, at the very least, the Transient ViewModels to act as a Transient, nothing more, nothing less. That's not the only problem, but it's a big one. When I register a Transient service or VM, I don't want it to act as a Singleton, that means I want a new instance each time. Definition of Transient. It's as simple as that.
And I don't want to just have convoluted explanations telling me why it doesn't work the way it should but that it's "normal".
So, any news after so many years, or is MAUI dead?

A very raw "maybe":

  1. add new properties to any Element like
  • bool AutoDisposeChildren
  • bool AutoDisposeBindingContext
  1. when Handler goes null trigger the
  • disposing of the binding context (bye bye ViewModel) if AutoDisposeBindingContext
(if BindingContext is IDisposable disposable)
{
disposable.Dispose();
}

  • dispose children if AutoDisposeChildren
    could either iterate through InternalChildren or Children for ILayour or other options

Possible problems:

  • Handler could go null several times (Shell loved to kill/reinstate handler for same VirtualView several times i guess) (?)
  • Disposing already disposed objects (?)

Might resolve possible problems by using custom interface IMauiDisposable : IDisposable, to have IsDisposed etc..

I found a sort of solution by just adding the following code into AppShell.cs :

ShellContent _previousShellContent;
	protected override void OnNavigated(ShellNavigatedEventArgs args)
	{
		base.OnNavigated(args);
		if (CurrentItem?.CurrentItem?.CurrentItem is not null &&
			_previousShellContent is not null)
		{
			var property = typeof(ShellContent)
				.GetProperty("ContentCache", BindingFlags.Public |
                                 BindingFlags.NonPublic | BindingFlags.Instance |
                                 BindingFlags.FlattenHierarchy);
			property.SetValue(_previousShellContent, null);
		}
		_previousShellContent = CurrentItem?.CurrentItem?.CurrentItem;
	}

Far from being perfect, I think there's a problem with tabs (but I don't use them in this application), and the only way to solve this problem will be to get an official fix from Microsoft. But when ?

@odahan that way of clearing the shell content is the same I'm using in Nalu.Navigation and as you mentioned it doesn't work while switching between tabs, and that's by design: from an UX point of view it is incorrect to destroy the state of a tab because the user usually expect that to persist while switching between tabs.
Not to mention the bottom navigation bar.

The topic is really complex, that's why I created that abstraction on top of Shell: to simplify the usage while keeping the good behaviors provided by Shell.

At this point we shouldn't expect something from Microsoft, simply because people are using Shell the way it is, so any change would become a breaking one. Also remember that Shell is something ported from Xamarin, which is another reason for them to avoid changes.

@odahan that way of clearing the shell content is the same I'm using in Nalu.Navigation and as you mentioned it doesn't work while switching between tabs, and that's by design: from an UX point of view it is incorrect to destroy the state of a tab because the user usually expect that to persist while switching between tabs.

Not to mention the bottom navigation bar.

The topic is really complex, that's why I created that abstraction on top of Shell: to simplify the usage while keeping the good behaviors provided by Shell.

At this point we shouldn't expect something from Microsoft, simply because people are using Shell the way it is, so any change would become a breaking one. Also remember that Shell is something ported from Xamarin, which is another reason for them to avoid changes.

If folks want to use the IDisposable pattern I'd highly recommend using Nalu.Navigation. Implementing IDiposable, and not creating your own service scopes, will most likely cause memory leaks https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines#disposable-transient-services-captured-by-container. Even if you manually call dispose, afaik, that won't really accomplish anything, other then your code getting called. The instance will still be rooted in the container by design of how the Microsoft containers work. If you want to roll your own thing, and you aren't going to use service scopes, then don't implement IDisposable, just make something up like IDestroy.

The tabbed page conversation is a great example of why we can't make the decisions of all cases where we should be calling 'destroy'.
It comes down to providing enough information and hooks to establish the rules for your app.

Most fixes here that I'd envision are based around adding better hooks for IServiceProvider to setup your own IServiceProvider scopes and make the navigation args less useless. If navigation args had better information about push/pop/and pages involved you could make some of your own decisions.

We're primarily not focusing on this issue because we're heavily focused on quality and Xamarin migration. Nothing changed here between Xamarin and MAUI, so it's just not a priority right now. That being said, I think there's some easy net9 wins here.

Some areas folks can create PRs for. I'm hoping we get time to look into these things but it's hard to know.

  • Roll back this pr and make it so if a page comes from the SP the SP is always queried when creating content so it can be transient. There was a bug with the net7 service provider they fixed in net8.
    #4281

  • make navigating args less awful and have more information

    public sealed class NavigatingFromEventArgs : EventArgs

  • add better resolver hooks so you could more easily apply your own scoped service over different areas of the shell.

I would also be really curious to know any APIs or behavior improvements we could add to make @albyrock87's life easier. @albyrock87, if you're open to chat more actively message me on twitter or discord https://aka.ms/dotnet-discord

Honestly your easiest bet is to use Prism where you have helpers like IActiveAware, IPageLifeCycleAware, INavigationAware, and IDestructible. But if you really want to implement something to help you here I would actually advise against using IDisposable because .NET itself calls it in weird times/places which is why Prism created a unique interface to deal with disposing resources.

If you want to go the difficult route and not use Prism you could create a behavior that invokes your cleanup method when the behavior is detaching from the View.

public class DisposableBehavior : Behavior
{
    protected override void OnDetachingFrom(BindableObject bindable)
    {
        base.OnDetachingFrom(bindable);
        if (bindable.BindingContext is IDisposable disposable)
            disposable.Dispose();
    }
}
<ContentPage xmlns:behaviors="using:MyApp.Behaviors">
  <ContentPage.Behaviors>
    <behaviors:DisposableBehavior />
  </ContentPage.Behaviors>
</ContentPage>

It becomes very clear to me now that a MAUI-specific interface should be implemented for that purpose, in a way they did with IQueryAttributable etc, then if people wish they can use it without breaking anything existing.
This might be done deep in Core on a VisualElement level and Shell etc should just adapt to it, people should not be forced to use Shell and similar to profit from basic features.

It's okay this is still in discussion, no magic MS prince/princess would come until he/she reads all the opinions. :)

Wait so how is this done in other XAML UI frameworks with DI? Is it a general issue of the DI container?

+1 for an interface. This is the lowest hanging fruit: A reliable way knowing when a view is removed and then we can handle the rest e.g. IViewLifecycleAware. If we could get an this interface, I think that would solve all our problems.

One step further with be a way to decorate the shell with a service that manages the lifecycle of the dependency objects e.g. IViewLifecycleManager. The default lifecycle manager could be the current behavior so as not to introduce breaking changes and then provide a second lifecycle manager that calls Dispose on the view model when the pages are removed.

This week I am starting the port of our application to Maui. In our application, memory management is critical. We have been bitten by third party navigation stacks in the past and for this iteration we were hoping to keep navigation and DI as simply as possible. That is looking less and less like a possibility.

This is currently the 2nd most commented issue here on their GitHub, which has been opened like 2 years ago, and MSFT does nothing about it... It's unbelievable! This is what makes us developers to loose faith in MAUI. How can they ignore their developer community for so long?
Last year they marketed Shell as the new way to go by setting it as default on the new project template of VS even knowing it suffers from this bad design architecture that doesn't allow us to easily create a memory/reactive efficient app, and then keep ignoring us like if it wasn`t a big deal. This is what happens when you just create "Hello World" apps to showcase on conference shows instead of dogfooding your own stuff.

A very interesting material to consider when thinking about a core-level solution: #18366 (comment)

Any chance this is going to be ever looked at? It's such a fundamental issue, any serious app can't be made without it. We started moving our huge enterprise app from Xamarin.Forms to Maui just because Microsoft is forcing everyone, but issues like this makes us very nervous.

Ask @davidortinau, he was supposed to know it.

Looking at what @davidortinau is doing these days, I found his project where there is an interesting call:

https://github.com/davidortinau/SentenceStudio/blob/main/src/SentenceStudio/MauiProgram.cs

builder.Services.AddTransientWithShellRoute<TPage, TViewModel>()
Maybe that could be of your interest. Don't have time to test it.

Another thing that you can notice is that the project uses Service Locator antipattern. I wonder why.
Example (https://github.com/davidortinau/SentenceStudio/blob/main/src/SentenceStudio/Pages/Vocabulary/AddVocabularyPageModel.cs):

public AddVocabularyPageModel(IFilePickerService picker, IServiceProvider service)
{
    this.picker = picker;
    _vocabService = service.GetRequiredService<VocabularyService>();
}

builder.Services.AddTransientWithShellRoute<TPage, TViewModel>()

Doesn't help at all.

I keep seeing big issues like this getting moved to the Backlog and it makes me nervous, too. I'm 3 months into converting a rather complex app from Xamarin Forms to MAUI and I am not even close to having it production-ready. I've lost a lot of features and functionality that I had in the Xamarin Forms app, and I am spending way too much time figuring out and implementing workarounds. Now I'm trying to get a handle on this memory management problem because it is definitely going to be a show stopper.

Tried suggestion by @dansiegel just out of interest, but OnDetachingFrom never gets triggered?

I also tried using Unloaded event, but on iOS it calls it even when you push another page top (actually calls event twice!) and also when navigating back, so no idea when going forward or backwards.

I also thought I could use OnNavigateFrom event, but for some dumb reason NavigatedFromEventArgs does not dispose "DestinationPage" property, it's marked as Internal :(

@eder-em I'd love to know if you've managed to get it working using Flyout, DI and MVVM (Community Toolkit) without Shell. After many frustrating hours trying, I still haven't managed it.

I see you're all asking to use Shell with IDisposable and I fully understand your frustration/concerns, so I wonder if you've read this message by PureWeen (Microsoft) where the situation is well explained.

For now - as suggested - if you don't want to spend countless hours (trust me) to cook a solution yourself, you can try the library I've built to solve this situation and many other things: it also has an embedded leak detector while debugger is attached.
It is still based on Shell but it provides Scoped context on every page so that you can dispose what you have to.

If you don't like that for some reasons, I'd like to hear your opinion by opening an issue there.

And obviously you can always try out many of the other fantastic libraries out there which have their own navigation handling strategy (not based on Shell).

We're primarily not focusing on this issue because we're heavily focused on quality and Xamarin migration. Nothing changed here between Xamarin and MAUI, so it's just not a priority right now. That being said, I think there's some easy net9 wins here.

@PureWeen

If the team priority is not shifted to critical issues/needs like this case sooner or later there is no point of team to work on Xamarin migration. You can just partner with Google or Facebook to make a new partially working tool to migrate apps from Xamarin to Flutter or React Native. No joke...

What is the point of migrating to MAUI when you can't even guarantee one of the most critical things like page lifecycle? People want literally simple things that work reliable in supported platforms. I don't know what your metrics tell you (also don't care because they are mostly misleading the management) but developers care only about 2 platforms: iOS and Android.

  • Page is created. (ctor)
  • Page is navigated to. (I was somewhere else, now this is the page I see)
  • Page is navigated away. (I was here, now I'm going somewhere else. I may come back to it or not)
  • Page is destroyed. (I'm done with this)

Finito. Prism does this well. I wonder why this new shiny framework cannot manage something that 10+ years library is handling so well. Treat each NavigationPage as Frame and handle the stack gracefully.

I know you guys working hard to on the issues, lack of resources and bad management/architectural decisions already. For once, please stop listening people who has no idea about what they do in Xamarin/MAUI and give an ear to those who are trying to do things systematically in 10 levels deep.

Thanks @eder-em for the recommendation and confidence. @davidortinau you can send the job offer letter to @dansiegel and myself to support@prismlibrary.com. We'll be happy to help improve things 😄

I would like to point out a few clarifications about the statements made about Prism. First, the Patterns and Practices team, which was made up of MS employees, contractors, and community members like myself, were responsible for the initial creation of Prism for WPF. Prism later had a brief attempt at a Windows Store (UWP) version which failed spectacularly along with the UWP framework itself. I then took complete ownership of the project and personally created the XF version from scratch. This is the time Dan came onboard and he is responsible for the MAUI version we have today using the XF version as inspiration. It's been a fun and interesting journey.

It is true that Prism does now have a paid commercial license. However, it is still free for any company/person that meets the community license requirements. We understand this doesn't help everyone, but Prism does solve a lot of the problems that are being mentioned in this thread, which is a strong motivation for companies purchasing a commercial license.

As to this issue specifically, Prism was not able to use the existing IDisposible interface due to the complexities around the intricacies of .NET and garbage collection. Which is why we had to introduce a new interface called IDestructible. Maybe the MAUI team can take a similar approach.

Better yet, Dan and I can sell Prism to Microsoft and then just make it the default behavior. What do you say MS? Ready for another acquisition? LOL

I came up with this solution/hack for now until Maui team can implement something better internally. Seems to be working fine for our project.

Not using IDispose, but created our own IDestructible interface. Basically in our base page when OnNavigatedFrom is called I check if page is still present in either navigation or modal stacks, if yes do nothing as it means moving forward, otherwise call Destroy if page/viewmodel implements interface.

protected override void OnNavigatedFrom( NavigatedFromEventArgs args )
{
    base.OnNavigatedFrom( args );
    
    // is page still present
    if (Shell.Current.Navigation.NavigationStack.Any( p => p == this ) || 
        Shell.Current.Navigation.ModalStack.Any( p => p == this )) 
        return;

    var page = this as IDestructible;
    var vm = _viewModel as IDestructible;
    
    // nothing to destroy
    if (page is null && vm is null)
        return;

    vm?.Destroy();
    BindingContext = null;
    page?.Destroy();
}

Hi @memis1970 I'm @eder-em (I have 2 accounts here).
Here is the framework that I have created with my friend Leo to workaround the issue.

https://github.com/naveasy/Naveasy

Tried suggestion by @dansiegel just out of interest, but OnDetachingFrom never gets triggered?

I also tried using Unloaded event, but on iOS it calls it even when you push another page top (actually calls event twice!) and also when navigating back, so no idea when going forward or backwards.

I also thought I could use OnNavigateFrom event, but for some dumb reason NavigatedFromEventArgs does not dispose "DestinationPage" property, it's marked as Internal :(

Try with the PlatformBevahior, by default Behaviors doesn't call OnDetaching at framework level (since XF). The new PlatformBehavior does, so it will best to use it instead. The API for xaml will not change.

Hi @pictos, thank you, PlatformBevahior works, but I found an issue (same in my code above) - OnDetachedFrom is called on current page when you push another page on top and also when changing TabBar tabs. No idea what it is detaching from if it's still in navigation stack.

Looks like you can't win with Maui.

An acquisition of PRISM including the current maintainers of PRISM to help lead the project instead of @davidortinau would be my dream 😂.

Seconded! :-)

@ederbond Thanks for pointing out your library! I'll take a look. Appreciate it.

Hi @memis1970 I'm @eder-em (I have 2 accounts here). Here is the framework that I have created with my friend Leo to workaround the issue.

https://github.com/naveasy/Naveasy

If you're going to copy Prism code, you can at least give Prism the credit and not to try to pass it off as your own work. I understand that the official Prism project is no longer MIT, and businesses that make more than $1M a year want an alternative free version because they don't want to support the official framework. However, assuming you at least used the v8 MIT version of the code and didn't purposefully break the new Prism license, it would be great if you could at least acknowledge the code you are using from Prism. Thank you.

Thanks all for the good discussion. As written, this proposal is not aligned with where we are currently investing.

To narrow the conversation, @PureWeen has drafted 2 specs to consider for .NET 9.

#21814
#21816

Hi @memis1970 I'm @eder-em (I have 2 accounts here). Here is the framework that I have created with my friend Leo to workaround the issue.

https://github.com/naveasy/Naveasy

If you're going to copy Prism code, you can at least give Prism the credit and not to try to pass it off as your own work. I understand that the official Prism project is no longer MIT, and businesses that make more than $1M a year want an alternative free version because they don't want to support the official framework. However, assuming you at least used the v8 MIT version of the code and didn't purposefully break the new Prism license, it would be great if you could at least acknowledge the code you are using from Prism. Thank you.

Hi @brianlagunas I have updated the readme.md of Naveasy to give the proper credits to the PRISM team.