dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.28k stars 1.76k forks source link

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

Closed ederbond closed 7 months ago

ederbond commented 2 years ago

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

HobDev commented 2 years ago

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

HobDev commented 2 years ago

Also this one seems to be related https://github.com/dotnet/maui/issues/7329

ederbond commented 2 years ago

@HobDev No, MAUI doesn't care if you implement IDisposableon 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 IQueryAttributableinterface on your Page or ViewModel to handle navigation parameters.

PureWeen commented 2 years ago

For now I'd recommend using the Loaded event

ghost commented 2 years ago

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.

Sebosek commented 2 years ago

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.

RobTF commented 1 year ago

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.

robreno commented 1 year ago

Has any further information been provided per the comment below?

https://github.com/dotnet/maui/issues/7329#issuecomment-1134349911

janseris commented 1 year ago

Came from https://github.com/dotnet/maui/issues/7329#issuecomment-1134349911 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?

RobTF commented 1 year ago

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!

albyrock87 commented 1 year ago

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

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).

ksoftllc commented 1 year ago

@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.

RobTF commented 1 year ago

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.

albyrock87 commented 1 year ago

@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.

RobTF commented 1 year ago

@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.

Quaybe commented 1 year ago

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.

ederbond commented 1 year ago

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.

hartez commented 1 year ago

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?

ederbond commented 1 year ago

@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.

RobTF commented 1 year ago

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.

albyrock87 commented 1 year ago

@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:

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.

akhanalcs commented 1 year ago

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

ryanlpoconnell commented 1 year ago

@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.

janseris commented 1 year ago

@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.

ryanlpoconnell commented 1 year ago

@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?

albyrock87 commented 1 year ago

@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.

homeyf commented 1 year ago

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

ederbond commented 12 months ago

Any plans around this @davidortinau @hartez @PureWeen @StephaneDelcroix ?

PureWeen commented 12 months ago

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 https://github.com/dotnet/maui/blob/1269d81a741a8672894c0cf79b2b0cf84602731d/src/Controls/src/Core/Shell/ShellContent.cs#L76

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.

ederbond commented 12 months ago

@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

janseris commented 12 months ago

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

https://github.com/dotnet/maui/blob/1269d81a741a8672894c0cf79b2b0cf84602731d/src/Controls/src/Core/Shell/ShellContent.cs#L76

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?

ryanlpoconnell commented 12 months ago

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

PureWeen commented 12 months ago

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?

ederbond commented 12 months ago

@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.

PureWeen commented 11 months ago

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.

Mercally commented 11 months ago

Any solution or workaround about this?

bryanjez commented 11 months ago

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.

janseris commented 11 months ago

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.

ederbond commented 11 months ago

@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.

memis1970 commented 10 months ago

@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?

ederbond commented 10 months ago
chrisg32 commented 10 months ago

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).

memis1970 commented 10 months ago

@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 https://github.com/matt-goldman/Maui.Plugins.PageResolver/issues/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!

memis1970 commented 10 months ago

@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.

matt-goldman commented 10 months ago

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.

bijington commented 10 months ago

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?

matt-goldman commented 10 months ago

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.

albyrock87 commented 10 months ago

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!

Auto72 commented 10 months ago

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?

albyrock87 commented 10 months ago

@Auto72 you can handle Login even with Shell by:

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.