JordanMarr / ReactiveElmish.Avalonia

Static Avalonia views for Elmish programs
Other
92 stars 8 forks source link

Terminate not terminating? #35

Closed houstonhaynes closed 8 months ago

houstonhaynes commented 8 months ago

I'm not sure if this is me not understanding what should be happening, or if I have a case of improper "copy-pasta" but I just noticed that my subscriptions don't terminate even though I have it set with Program.mkStoreWithTerminate this Terminate

https://github.com/JordanMarr/ReactiveElmish.Avalonia/assets/8174976/76f4c459-62d4-40d9-ad36-31fda23da44b

My understanding was that the "Terminate" was the magic pill that killed off those subs for me so I wouldn't have to do a full-retail state management effort that's there with the sub trigger "AutoUpdate" in the Chart View. Can you clue me in on what I might be missing in this picture?

houstonhaynes commented 8 months ago

I also don't get the GeoMap visual on return to the View (so I'm assuming the init isn't getting sorted out). This is the error code that surfaces when I exit the app. Nothing shows up in console while it runs (other than the subs continuing to surface their activity) - this error only shows up after app exit. This may not be related - but I didn't want it to slip by in case it has some bearing on the view/VM state management and re-init on return to the view.

Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
   at LiveChartsCore.GeoMap`1.Unload()
   at LiveChartsCore.SkiaSharpView.Avalonia.GeoMap.GeoMap_DetachedFromVisualTree(Object sender, VisualTreeAttachmentEventArgs e)
   at Avalonia.Visual.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Input.InputElement.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Controls.Control.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Visual.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Input.InputElement.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Controls.Control.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Visual.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Input.InputElement.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Controls.Control.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Visual.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Input.InputElement.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Controls.Control.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Visual.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Input.InputElement.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Controls.Control.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Visual.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Input.InputElement.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Controls.Control.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Visual.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Input.InputElement.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Controls.Control.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Visual.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Input.InputElement.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Controls.Control.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Visual.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Input.InputElement.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Controls.Control.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Visual.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Input.InputElement.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Controls.Control.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Visual.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Input.InputElement.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Controls.Control.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Visual.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Input.InputElement.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Controls.Control.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Visual.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Input.InputElement.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Controls.Control.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Visual.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Input.InputElement.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Controls.Control.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
   at Avalonia.Controls.TopLevel.HandleClosed()
   at Avalonia.Controls.WindowBase.HandleClosed()
   at Avalonia.Controls.Window.HandleClosed()
   at Avalonia.Win32.WindowImpl.AppWndProc(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam)
   at Avalonia.Win32.WindowImpl.WndProc(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam)
JordanMarr commented 8 months ago

This probably has to do with your view registration (Transient or Singleton). The main thing to remember here is that Program.mkStoreWithTerminate only works if your view is registered as Transient.

Transient Views

If you register a view as Transient, its view model will be automatically Disposed when the view is unloaded, and a new VM will be instantiated anytime the view is loaded or reloaded. (Terminate, assuming Program.mkStoreWithTerminate has been configured, is called when the VM is Disposed.)

Singleton Views

The VM is only created once, and the VM is not Disposed when the view is unloaded. So, Program.mkStoreWithTerminate will do nothing here because the VM will never be disposed, and Terminate will never be called. If you are using a Singleton view and you want to stop registrations, you will need to manually stop them yourself by subscribing to changes to the selected view.

houstonhaynes commented 8 months ago

Really interesting - I assumed non-Main views are transient by default.

JordanMarr commented 8 months ago

At the moment, view/VM registrations have to be done manually, so there really isn't a default. However, I plan on adding some "register by convention" strategies in the future so that you don't have to manually wire-up all your view/vm pairs. Conventions meaning something like, auto-register "ProductView" to "ProductViewModel". That would probably be the default convention, but then you could change it to whatever you want, and specify the lifetime default as Transient or Singleton.

houstonhaynes commented 8 months ago

The Chart view works as a Singleton because of that "SetIsAutoUPdateChecked" logic that turns it off if you switch away from the view. I switched it to "Transient" and the AutoUpdate never shut off so I went back to switched in Program.mkStoreWithTerminate and it worked.

So for my case it was copy-pasta as I copied and pasted the "ChartViewModel" Key in AppCompositionRoot.fs -- now that I've made them both Transient I won't make that mistake again. 😄