Yes, you read it right, async UI. After years of developing mobile apps, I am fully aware that mixing those two words is a big no no, but bear with me. Recently I found myself in a situation where after migrating a Xamarin.Forms application to .NET MAUI, a page which was made of multiple complex views, was taking a significantly long time to load (see image below). After a bit of testing I was sure that InitializeComponent (the parsing of the xaml) was the bottleneck. To address this, I decided to experiment with asynchronous loading for the UI components and I will share my findings with you now.

Before we continue, let’s note for the record, that I would strongly advise you to always try to optimize your views by flattening out the hierarchy and reducing the number of nested views where possible, and by reducing the amount of work that is done during the instantiation of your views. However, if you find yourself in a situation where you cannot avoid complex views, or lacking the resources to optimize the views, this little trick might help you out. So let’s dive in.

The Problem: Slow View Initialization

Let’s take a look at VerySlowView, a simple ContentView that introduces a delay during its initialization.

Example: VerySlowView

public partial class VerySlowView : ContentView
{
    public static readonly BindableProperty ColorProperty =
          BindableProperty.Create(nameof(Color),
                                  typeof(Color),
                                  typeof(VerySlowView));

    public Color Color
    {
        get => (Color)GetValue(ColorProperty);
        set => SetValue(ColorProperty, value);
    }

    public VerySlowView()
    {
        // Simulating a delay in the view's instantiation
        var delay = Task.Delay(1000);
        delay.Wait();  // Synchronous delay
        InitializeComponent();
    }
}

In this example, the VerySlowView introduces a 1-second delay upon initialization (Task.Delay(1000)), blocking the UI thread and causing the interface to appear unresponsive. When multiple instances of VerySlowView are added to a page, like in SlowUIPage, this issue compounds.

Example: SlowUIPage with Multiple Slow Views

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:asyncuiexample="clr-namespace:AsyncUIExample"
             x:Class="AsyncUIExample.SlowUIPage"
             Title="SlowUIPage">
    <VerticalStackLayout Spacing="10" Margin="5">
        <asyncuiexample:VerySlowView Color="#88c0e3"/>
        <asyncuiexample:VerySlowView Color="#d2e388"/>
        <asyncuiexample:VerySlowView Color="#e38888"/>
    </VerticalStackLayout>
</ContentPage>

In SlowUIPage, we use three instances of VerySlowView. Since the initialization is synchronous, it severely impacts performance, making the page feel sluggish to the user.



The Solution: Asynchronous View Creation

A good approach to mitigate this issue is to instantiate VerySlowView objects on a background thread, avoiding blocking the main UI thread. Once the views are ready, they can be assigned to the page on the main thread.

Here’s how we can implement this solution.

Step 1: Use Bindable Properties for Lazy Loading

First, we define BindableProperty for each view in SlowUIPage. This allows us to set the views later, once they are fully initialized.

Solution: SlowUIPage with Asynchronous View Creation

public partial class SlowUIPage : ContentPage
{
    public static readonly BindableProperty FirstViewProperty =
          BindableProperty.Create(nameof(FirstView),
                                  typeof(View),
                                  typeof(SlowUIPage),
                                  new LoadingView());

    public View FirstView
    {
        get => (View)GetValue(FirstViewProperty);
        set => SetValue(FirstViewProperty, value);
    }

    public static readonly BindableProperty SecondViewProperty =
          BindableProperty.Create(nameof(SecondView),
                                  typeof(View),
                                  typeof(SlowUIPage),
                                  new LoadingView());

    public View SecondView
    {
        get => (View)GetValue(SecondViewProperty);
        set => SetValue(SecondViewProperty, value);
    }

    public static readonly BindableProperty ThirdViewProperty =
          BindableProperty.Create(nameof(ThirdView),
                                  typeof(View),
                                  typeof(SlowUIPage),
                                  new LoadingView());

    public View ThirdView
    {
        get => (View)GetValue(ThirdViewProperty);
        set => SetValue(ThirdViewProperty, value);
    }

    public SlowUIPage()
    {
        InitializeComponent();
    }

    protected override void OnAppearing()
    {
        base.OnAppearing();
        // Offload view creation to a background thread
        Task.Run(CreateViews);
    }

    private void CreateViews()
    {
        // Initialize views on a background thread
        var firstView = new VerySlowView { Color = Color.FromArgb("#88c0e3") };
        var secondView = new VerySlowView { Color = Color.FromArgb("#d2e388") };
        var thirdView = new VerySlowView { Color = Color.FromArgb("#e38888") };

        // Assign views to the bindable properties on the main thread
        MainThread.BeginInvokeOnMainThread(() =>
        {
            FirstView = firstView;
            SecondView = secondView;
            ThirdView = thirdView;
        });
    }
}

Step 2: Offloading View Creation

The key here is the Task.Run(CreateViews) method, which executes the view instantiation in the background. By offloading this work, we keep the UI thread responsive.

Step 3: Updating the UI on the Main Thread

Once the views are created in the background, they need to be assigned to the page’s UI elements. Since only the main thread can update the UI, we use MainThread.BeginInvokeOnMainThread() to perform the assignment.

Bonus: LoadingView as a Placeholder

The LoadingView serves as a placeholder until the VerySlowView instances are ready. You can design LoadingView to show a spinner or a message to the user, improving the overall experience.



The Benefits

  • Responsiveness: The UI remains responsive while the views are being initialized.
  • User Experience: Users see a loading indicator or placeholder instead of a frozen interface.

Conclusion

UI optimization is very important when developing mobile applications, but when not possible, offloading heavy operations to a background thread can significantly improve the user experience. By using bindable properties and updating the UI on the main thread, we can create responsive and smooth interfaces even when working with complex or time-consuming views.

You can find the example code here.