Xamarin Forms – Add lifecycle events to views

For those of us who come to the Xamarin Forms world from WPF, the learning curve is not as terrible as for others.
After all, it is the same old xaml which we learned to love and a lot of concepts that we already know from WPF migrated successfully to Xamarin Forms.
However, sometimes, there are things that you take for granted and you pay a high price for it later on in your development.
Today, I’m going to talk about one such example: Lifecycle events on views.

Let me tell you, this was a real slap in the face for me.
I got so used to the fact that the Loaded and Unloaded events are something so common and easy to use that I didn’t even stop to question their existence in Xamarin Forms.
After a short period of disbelief and trying to search the web for answers, I came to the horrifying conclusion:
Xamarin Forms visual base classes don’t have lifecycle events!

 


In Short (TL;DR Version)

After calming myself down and searching the web for some more ideas and solutions,
I’ve created a Xamarin Forms Effect, with platform specific implementations, which will expose those events.

If you feel confident and just want the code, feel free to visit my GitHub page for the full solution.
If you want the complete solution and explanations, you can find it in the next sections.


 

The Long Version

Each platform provides us with different options to register to the specific lifecycle events that are provided by the specific platform.
Our mission? Unify everything under one single effect, which can be used across Xamarin Forms project.
Pretty straight forward.

Before we dig in, I strongly recommend that you get yourself familiar with Xamarin Forms Effects first. My entire implementation relies on it.

 

Xamarin Forms Project

Let’s start from the main Xamarin Forms project.
I want to keep the example as simple as possible, so no MVVM and no clever approaches (Other than the effect, obviously). Just a straight forward example.

The Routing Effect

We are going to start with the creation of our routing effect in the main Xamarin Forms project.
The effect is pretty simple and contains no logic. Just the two events and two methods that will raise those events.

public class ViewLifecycleEffect : RoutingEffect
{
    public const string EffectGroupName = "XFLifecycle";
    public const string EffectName = "LifecycleEffect";

    public event EventHandler<EventArgs> Loaded;
    public event EventHandler<EventArgs> Unloaded;

    public ViewLifecycleEffect() : base($"{EffectGroupName}.{EffectName}") { }

    public void RaiseLoaded(Element element) => Loaded?.Invoke(element, EventArgs.Empty);
    public void RaiseUnloaded(Element element) => Unloaded?.Invoke(element, EventArgs.Empty);
}

The only thing that I feel the need to explain here are the two consts.
I’ve created those simply because I don’t like literals and prefer to use consts whenever I can. 🙂

MainPage.xaml

Our main page will consist of a single button inside a grid, with a click event that will be handled in the code behind.

We will attach to the button the ViewLifecycleEffect and handle the Loaded and Unloaded events in the code behind as well.

<Grid x:Name="MainContainer" Margin="20">
    <Button Text="CLICK TO REMOVE" Clicked="Button_OnClicked" HorizontalOptions="Center" VerticalOptions="Center">
        <Button.Effects>
            <effects:ViewLifecycleEffect Loaded="ViewLifecycleEffect_OnLoaded" Unloaded="ViewLifecycleEffect_OnUnloaded"/>
        </Button.Effects>
    </Button>
</Grid>

The code behind will also be very simple and straight to the point:

  1. When the button is clicked, remove it.
  2. Upon Load, display the message: Loaded
  3. Upon Unload, display the message: Unloaded
public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    private void Button_OnClicked(object sender, EventArgs e)
    {
        MainContainer.Children.Clear();
    }

    private void ViewLifecycleEffect_OnLoaded(object sender, EventArgs e)
    {
        DisplayAlert("LOADED", "Button was added", "OK");
    }

    private void ViewLifecycleEffect_OnUnloaded(object sender, EventArgs e)
    {
        DisplayAlert("UNLOADED", "Button was removed", "OK");
    }
}

As you can see, the Loaded and Unloaded events are handled only via the effect. But no one is raising them.

Yet…

 

UWP

Coming from the WPF world, the UWP platform was the easiest for me, since there are already Loaded and Unloaded events on the FrameworkElement object.

So the solution is pretty straight forward:

  1. When the effect is attached, register to the Loaded and Unloaded events of the native view.
  2. When the effect is detached, remove the registrations to the events.
  3. The event handlers will call the raising events method on the Xamarin Forms effect.
[assembly:ResolutionGroupName(ViewLifecycleEffect.EffectGroupName)]
[assembly:ExportEffect(typeof(UwpViewLifecycleEffect), ViewLifecycleEffect.EffectName)]
namespace XFLifecycle.UWP.Effects
{
    public class UwpViewLifecycleEffect : PlatformEffect
    {
        private FrameworkElement _nativeView;
        private ViewLifecycleEffect _viewLifecycleEffect;

        protected override void OnAttached()
        {
            _viewLifecycleEffect = Element.Effects.OfType<ViewLifecycleEffect>().FirstOrDefault();
            _nativeView = Control ?? Container;
            
            _nativeView.Loaded += NativeViewOnLoaded;
            _nativeView.Unloaded += NativeViewOnUnloaded;
        }

        protected override void OnDetached()
        {
            _viewLifecycleEffect?.RaiseUnloaded(Element);
            _nativeView.Loaded -= NativeViewOnLoaded;
            _nativeView.Unloaded -= NativeViewOnUnloaded;
        }

        private void NativeViewOnLoaded(object sender, RoutedEventArgs routedEventArgs) => _viewLifecycleEffect?.RaiseLoaded(Element);
        private void NativeViewOnUnloaded(object sender, RoutedEventArgs routedEventArgs) => _viewLifecycleEffect?.RaiseUnloaded(Element);
    }
}

Note the resolution group and the effect export above the namespace. Note the UWP specific type that is exported.

You may notice that I’ve also included the raise of the unloaded event in the OnDetached method. This is due to the fact that when removing the button completely from the Xamarin Forms side, it might actually not call the Unloaded event at all but will call the OnDetached method.

 

Android

The android platform turned out to be almost as easy as UWP. Digging around the web a bit, I found out that the lifecycle events that are called on the Android platform are a bit different, but their timing is very similar to the lifecycle events of the UWP platform.

In native android, this is handled by passing in a listener implementation, but Xamarin is kind enough to provide us with good old C# events. 🙂

So after everything combined, the code looks very similar to the UWP platform code:

[assembly: ResolutionGroupName(ViewLifecycleEffect.EffectGroupName)]
[assembly: ExportEffect(typeof(AndroidViewLifecycleEffect), ViewLifecycleEffect.EffectName)]
namespace XFLifecycle.Droid.Effects
{
    public class AndroidViewLifecycleEffect : PlatformEffect
    {
        private View _nativeView;
        private ViewLifecycleEffect _viewLifecycleEffect;

        protected override void OnAttached()
        {
            _viewLifecycleEffect = Element.Effects.OfType<ViewLifecycleEffect>().FirstOrDefault();

            _nativeView = Control ?? Container;
            _nativeView.ViewAttachedToWindow += OnViewAttachedToWindow;
            _nativeView.ViewDetachedFromWindow += OnViewDetachedFromWindow;
        }

        protected override void OnDetached()
        {
            _viewLifecycleEffect.RaiseUnloaded(Element);
            _nativeView.ViewAttachedToWindow -= OnViewAttachedToWindow;
            _nativeView.ViewDetachedFromWindow -= OnViewDetachedFromWindow;
        }

        private void OnViewAttachedToWindow(object sender, View.ViewAttachedToWindowEventArgs e) => _viewLifecycleEffect?.RaiseLoaded(Element);
        private void OnViewDetachedFromWindow(object sender, View.ViewDetachedFromWindowEventArgs e) => _viewLifecycleEffect?.RaiseUnloaded(Element);
    }
}

Note the resolution group and the effect export above the namespace. Note the Android specific type that is exported.

 

iOS

Now comes the tricky part. At least it was that way for me.

iOS exposes the lifecycle events as overridable method in the derived type. These methods are actually very useful in case you actually derive from one of the iOS classes.
But sadly, this is not our case, so I had to be creative.

Digging around the web, I’ve stumbled upon this interesting answer on SO.

Basically speaking, we can assume that once the Superview property is initialized, the view is loaded. This is great! But how can we observe it?

Experienced iOS developers would probably yell right away: “KVO”! But for me it was something completely new. If this is also new to you, I recommend to read this article: Key Value Observing (KVO) on iOS for Xamarin, by David Seilaff. It gives a great overview on the topic and helped me understand what is going on with it.

This lead me to the following solution on the iOS platform:

  1. When the effect is attached, observe the superview property on the view.
  2. When the effect is detached, stop observing the property.
  3. On the observer callback, check the change on the superview property and act accordingly.
[assembly:ResolutionGroupName(ViewLifecycleEffect.EffectGroupName)]
[assembly:ExportEffect(typeof(IosLifecycleEffect), ViewLifecycleEffect.EffectName)]
namespace XFLifecycle.iOS.Effects
{
    public class IosLifecycleEffect : PlatformEffect
    {
        private const NSKeyValueObservingOptions ObservingOptions = NSKeyValueObservingOptions.Initial | NSKeyValueObservingOptions.OldNew | NSKeyValueObservingOptions.Prior;

        private ViewLifecycleEffect _viewLifecycleEffect;
        private IDisposable _isLoadedObserverDisposable;

        protected override void OnAttached()
        {
            _viewLifecycleEffect = Element.Effects.OfType<ViewLifecycleEffect>().FirstOrDefault();
            
            UIView nativeView = Control ?? Container;
            _isLoadedObserverDisposable = nativeView?.AddObserver("superview", ObservingOptions, IsViewLoadedObserver);
        }

        protected override void OnDetached()
        {
            _viewLifecycleEffect.RaiseUnloaded(Element);
            _isLoadedObserverDisposable.Dispose();
        }

        private void IsViewLoadedObserver(NSObservedChange nsObservedChange)
        {
            if (!nsObservedChange.NewValue.Equals(NSNull.Null))
                _viewLifecycleEffect?.RaiseLoaded(Element);
            else if (!nsObservedChange.OldValue.Equals(NSNull.Null))
                _viewLifecycleEffect?.RaiseUnloaded(Element);
        }
    }
}

Note the resolution group and the effect export above the namespace. Note the iOS specific type that is exported.

 

The Result – Xamarin Forms Views Lifecycle Events

If everything went well, you should be able to see the highly anticipated messages on all 3 platforms:

Lifecycle Events

Also, after clicking the button (Which is hidden underneath the message), you should be able to see the unloaded message as well:Lifecycle events

This concludes the journey to achieve lifecycle events in Xamarin Forms.

I realize that this solution is not perfect, and to be honest, it is very far from it. But I think it might suit a lot of initial needs.

I know that for my current needs, it is exactly what I need. 🙂

Feel free to browse the complete solution on my GitHub. 🙂

See you in my next blog post!

16 thoughts on “Xamarin Forms – Add lifecycle events to views”

  1. Hi! Thanks for your code, it’s been really helpful. Hard to believe that this isn’t supported natively for controls – great work!

    The Android version works perfectly for me, but in my solution the iOS newValue and oldValue are both always null – so although the function IsViewLoadedObserver is getting triggered at the right moment, the raising loaded and unloaded functions never get hit. Do you have any hints on how to fix this?

    Thanks

    1. Hi, thanks for the kind comment.
      I’m not sure why you are not getting the values. Probably something have changed since I wrote the article and the superview is no longer getting the actual new value?

      I would start by checking other properties to observe. Maybe something else is being updated. Let me know if you found a solution, I would love to update the article with your findings. 🙂

  2. Hi Pshul
    Very informative article!
    Can you pleae let me know do we have any such class to track the Pages? like I navigate between the pages I would like to get callbacks at one place.

    Thanks in advance

    1. Hi Chandra, thanks for the kind comment. 🙂
      I don’t know if there is a built in solution for that or not, maybe something that let’s you “listen” to the current displayed path of your app.
      But if there isn’t, I don’t think I would use an effect for that. I would probably just derive from the used page class and add some logic to it based on the actions/events it already has. This way, you will be able to “send” those events to a single place where you can handle them.

      Best of luck!

Leave a Comment

Your email address will not be published.