Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Xamarin ContentView/View Lifecycle

Version

Xamarin.Android 8.2

Xamarin.Forms 2.5

Short question

How do I, or should I let a ContentView know the life cycle state of its containing Page (e.g. Appearing, Disappearing, Sleeping)

Long question

I am learning how to create a reusable Xamarin ContentView. I decide to create a control <LabelCarousel/> that will display its children label one after another. The problem is I have no idea how to stop the background timer that switches the content

Sample

Sample

Usage

<local:LabelCarousel>
    <Label>Hello</Label>
    <Label>Hola</Label>
</local:LabelCarousel>

Implementation

namespace XamarinStart.Views
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    [ContentProperty("LabelContainer")]
    public partial class LabelCarousel : ContentView
    {
        [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
        [Description("Labels"), Category("Data")]
        public List<Element> LabelContainer { get; } = new List<Element>();

        private int _index = 0;
        private Timer _timer = new Timer(2000);

        public LabelCarousel ()
        {
            InitializeComponent ();

            _timer.Elapsed += TimerEvent;
            _timer.Start();
            
        }

        private void TimerEvent(object sender, ElapsedEventArgs e)
        {
            if (_index >= LabelContainer.Count)
            {
                _index = 0;
            }

            var selected = LabelContainer[_index++];
            ChangeLabel((Label)selected);
        }

        private void ChangeLabel(Label lbl)
        {
            Device.BeginInvokeOnMainThread(async () =>
            {
                await this.FadeTo(0);
                Content = lbl;
                await this.FadeTo(1);
            });
        }
    }
}

Problem

When I put this app into background, or push another activity on the top of this one, the timer is still running, wasting the CPU resource. Is there any good idea to let the reusable ContentView notified when the parent page changes the state?

Related Posts

https://forums.xamarin.com/discussion/38989/contentview-lifecycle https://forums.xamarin.com/discussion/65140/we-need-a-way-for-contentview-and-viewcells-to-monitor-their-own-lifecycle

Thank you!

like image 717
Yuanyi Wu Avatar asked Mar 21 '18 00:03

Yuanyi Wu


1 Answers

I figured out one possibility myself by using the class extension to add AttachLifecycleToPage method to all Element object.

Basically, the principle behind the extension is tracing up the Parent along the UI tree. However, when the ContentView is initiated (i.e., ctor is called), it may not be attached to a parent yet, or its parent (if any) is not attached to a page. That's why I listen to the PropertyChanged event of the Element whose Parent is temporarily null. Once the Element gets attached, the searching process continues. Note that the AttachLifecycleToPage should not be synchronous in the Element ctor, which will causes a dead lock.

Extension code

namespace XamarinStart.Extensions
{
    public static class PageAwareExtension
    {
        public static void AttachLifecycleToPage(this Element element, EventHandler onAppearing = null,
            EventHandler onDisappearing = null)
        {
            var task = new Task(() =>
            {
                var page = GetPage(element);
                if (page == null)
                {
                    return;
                }

                if (onAppearing != null)
                {
                    page.Appearing += onAppearing;
                }

                if (onDisappearing != null)
                {
                    page.Disappearing += onDisappearing;
                }
            });
            task.Start();
            return;
        }
        public static Page GetPage(this Element element, int timeout = -1)
        {
            if (element is Page) return (Page)element;

            Element el = element;
            // go up along the UI tree
            do
            {
                if (el.Parent == null)
                {
                    // either not attached to any parent, or no intend to attach to any page (is that possible?)
                    var signal = new AutoResetEvent(false);
                    PropertyChangedEventHandler handler = (object sender, PropertyChangedEventArgs args) =>
                    {
                        // https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Core/Element.cs
                        // Setting Parent property is tracked
                        Element senderElement = (Element) sender;
                        if (args.PropertyName == "Parent" && senderElement.Parent != null)
                        {
                            signal.Set();
                        }
                    };
                    el.PropertyChanged += handler;

                    var gotSignal = signal.WaitOne(timeout);
                    if (!gotSignal)
                    {
                        throw new TimeoutException("Cannot find the parent of the element");
                    }

                    el.PropertyChanged -= handler;
                } // if parent is null

                el = el.Parent;

            } while (! (el is Page));

            return (Page) el;
        }
    }
}

Modified LabelCarousel

    public LabelCarousel ()
    {
        InitializeComponent ();

        _timer.Elapsed += TimerEvent;
        _timer.Start();

        this.AttachLifecycleToPage(OnAppearing, OnDisappearing); 
    }
    private void OnDisappearing(object sender, EventArgs eventArgs)
    {
        _timer.Stop();
    }

    private void OnAppearing(object sender, EventArgs eventArgs)
    {
        _timer.Start();
    }

Well, I am not sure if this dirty hack can cause some side effect. I hope that eventually the official release will have the ContentView with full life cycle support.

like image 132
Yuanyi Wu Avatar answered Sep 29 '22 13:09

Yuanyi Wu