Forum Xamarin.Forms

ContentView lifecycle

Hi,

First I'm a huge fan of Xamarin & Xamarin.Forms, it is simply amazing !

My question is :

I am creating a XAML ContentView which has animations looping while the IsVisible property is true and it works fine.

However, how can I detect from this custom control that its containing page has been removed or is not visible anymore when navigating back or forward to another page ? It seems that when navigating back to my first page my component (on the 2nd page) is not destroyed.

Thank you very much,

Have a nice day :-) !

Best Answers

Answers

  • RafaelMouraRafaelMoura BRInsider, University, Developer Group Leader ✭✭✭

    override OnAppearing :)

  • Thanks for your answer :-) !

    Yes I know I can use OnAppearing from my Page containing my ContentView, but what I want is to be able to do this from the inside of my ContentView. In other words I want it to be independant and start animating by itself and stop animating by itself too.

  • To be a little more specific :

    it seems that there is no WPF's UserControl Loaded & Unloaded events equivalent in Xamarin.Forms ContentView. Is that correct :-) ?

  • JahnOttoAndersenJahnOttoAndersen NOMember ✭✭

    I have the same problem. I'd like to start and stop a timer inside a view when the view is closed.

    I can listen to the PropertyChanged. There is a "Renderer" property changed event which is fired every time the view appears or disappears. However, this renderer is not exposed in the API. Hence, I can't tell whether the view is appearing or disappearing.

  • MelbourneDeveloperMelbourneDeveloper AUMember ✭✭✭

    When is this going to be fixed?

  • +1

  • @AlexP,
    Nicely done. Maybe you can contact Xamarin and ask them to add this to their next version ;-)

  • JamesEshJamesEsh USMember ✭✭

    I found that overriding OnParentSet() works great.

  • ZaneCampbellZaneCampbell USMember ✭✭

    @jonathannichols thank you! Your solution worked great for my situation.

  • XamtasticXamtastic Member ✭✭✭

    OnParentSet() fires BEFORE the OnAppearing so if you wanted something in the Nav Stack or anything you're out of luck.

    B) This worked for me:

                this.LayoutChanged += (s, e) =>
                {
                    Device.BeginInvokeOnMainThread(async () =>
                    {
                        .............
                    });
                };
    

    Thanks @jonathannichols bang on!

  • clevertreeclevertree Member ✭✭
    edited March 2020

    the layout changed thing works great @Xamtastic @jonathannichols - is there an equivalent for unloading we can use so we can -= out the +=? Otherwise I'm worried we'll get multiple event handlers attached as users continue using the app.

  • NMackayNMackay GBInsider, University admin

    @clevertree said:
    the layout changed thing works great @Xamtastic @jonathannichols - is there an equivalent for unloading we can use so we can -= out the +=? Otherwise I'm worried we'll get multiple event handlers attached as users continue using the app.

    You could test if the bindingcontext has been set to null perhaps and unhook your event handlers, I would suspect the code above causes leaks (pretty certain), Prism allows hooks into ContentViews if they are registered as PartialViews so you can implement IDestructible and unhook subscribers, handlers, behaviors etc easily, if not using Prism you might have to get the parent page to call a cleanup method on the child contentview. Prism mantains an internal dictionary of partial views for pages so it knows to call cleanup, if the control is a reusable composite control, checking the binding context has been released may be an alternative.

  • clevertreeclevertree Member ✭✭
    edited March 2020

    Thanks @NMackay, I always appreciate your thoughtful answers on this forum, nice to get one to my own question.

    The Prism approach sounds great, but the project is too far down the line for a refactor to use that, so I've ended up going for a combination of your "binding context changed" and "clean up in the parent page" approaches.

    I probably should have mentioned this, but the ContentView I need to unregister is used as the template for a collectionview item, so we have to throw recycle-ability into the mix. The binding context can change a lot, when the user scrolls, rather than just getting assigned and unassigned once each.

    On top of this complexity, it's the binding context for a picker view inside the collectionview item that I'm working with. (I'd like to set the items and selected index changed logic in a pure MVVM way but I'm finding I'm having to do it inside the view instead of my viewmodel, because otherwise the collection view recycling nukes my binding context when I scroll up down the list and the items get thrown away, and the picker becomes empty)

    My solution boils down to this:

    In the ContentView.xaml.cs

            protected override void OnBindingContextChanged()
            {
                base.OnBindingContextChanged();
                if (this.BindingContext != null)
                {
                      SetupPicker();  // includes  -= SpecialPicker_SelectedIndexChanged, then change items, then += again.
                }
                else
                {
                     SpecialPicker.SelectedIndexChanged -= SpecialPicker_SelectedIndexChanged;
                }
            }
    

    Then in the parent page xaml.cs

            protected override void OnDisappearing()
            {
                base.OnDisappearing();
    
                var children = MyParentCollectionView.LogicalChildren
                    .OfType<MyCollectionViewItemClass>()
                    .ToList();
    
                if (!logicalChildren.Any()) { return; }
                foreach (var item in logicalChildren)
                {
                    item.CleanUp(); // in here I deregister the event with -=
                }
    
            }
    

    It seems quite verbose but I think I'm covering all my bases, plus it works and it's fast. Do you reckon I've missed anything fundamental / would you do it differently?

    I'd love to avoid all this by having access to loading / unloading consistent lifecycle events for contentviews but neither of the two most popular workarounds online work for me in my case reliably on android and ios https://www.pshul.com/2018/03/27/xamarin-forms-add-views-lifecycle-events/ and https://www.ston.is/xamarin-forms-secrets-renderer-property/) perhaps because of the collectionview / picker combination. And it seems that the github ticket to add this into Xamarin.Forms core is a ways off.

    Thanks again!

  • NMackayNMackay GBInsider, University admin

    @clevertree said:
    Thanks @NMackay, I always appreciate your thoughtful answers on this forum, nice to get one to my own question.

    The Prism approach sounds great, but the project is too far down the line for a refactor to use that, so I've ended up going for a combination of your "binding context changed" and "clean up in the parent page" approaches.

    I probably should have mentioned this, but the ContentView I need to unregister is used as the template for a collectionview item, so we have to throw recycle-ability into the mix. The binding context can change a lot, when the user scrolls, rather than just getting assigned and unassigned once each.

    On top of this complexity, it's the binding context for a picker view inside the collectionview item that I'm working with. (I'd like to set the items and selected index changed logic in a pure MVVM way but I'm finding I'm having to do it inside the view instead of my viewmodel, because otherwise the collection view recycling nukes my binding context when I scroll up down the list and the items get thrown away, and the picker becomes empty)

    My solution boils down to this:

    In the ContentView.xaml.cs

            protected override void OnBindingContextChanged()
            {
                base.OnBindingContextChanged();
                if (this.BindingContext != null)
                {
                      SetupPicker();  // includes  -= SpecialPicker_SelectedIndexChanged, then change items, then += again.
                }
                else
                {
                     SpecialPicker.SelectedIndexChanged -= SpecialPicker_SelectedIndexChanged;
                }
            }
    

    Then in the parent page xaml.cs

            protected override void OnDisappearing()
            {
                base.OnDisappearing();
    
                var children = MyParentCollectionView.LogicalChildren
                    .OfType<MyCollectionViewItemClass>()
                    .ToList();
    
                if (!logicalChildren.Any()) { return; }
                foreach (var item in logicalChildren)
                {
                    item.CleanUp(); // in here I deregister the event with -=
                }
    
            }
    

    It seems quite verbose but I think I'm covering all my bases, plus it works and it's fast. Do you reckon I've missed anything fundamental / would you do it differently?

    I'd love to avoid all this by having access to loading / unloading consistent lifecycle events for contentviews but neither of the two most popular workarounds online work for me in my case reliably on android and ios https://www.pshul.com/2018/03/27/xamarin-forms-add-views-lifecycle-events/ and https://www.ston.is/xamarin-forms-secrets-renderer-property/) perhaps because of the collectionview / picker combination. And it seems that the github ticket to add this into Xamarin.Forms core is a ways off.

    Thanks again!

    Yeah, cell recycling makes things more complicated, I think your parent cleanup is probably the safest approach, the renderer for the control would have to take care of the unhooking of handlers if it can't be handled in shared code.

    Good way to test if to take a snapshot once the page is in memory
    Pop the page
    Force garbage collection (a snapshot in the Xamarin profiler does a GC.Collect), usually it takes 2 or three snapshots
    If the memory doesn't reduce and there's life instances of bound objects you know you have an issue

    You can use the VS memory profiler if you have a UWP project to do this but you have to call GC.Collect with that profiler from memory.

    There's no page lifecycle events built in to child sub views so unfortunately you have to take care of it, means a more stable app.

Sign In or Register to comment.