9

I have a ContentPage which requires some "heavy lifting" before its data can be shown.

So, my idea was to have an ActivityIndicator on the page visible until the data is ready to be displayed.

I'm trying to figure out a suitable event to use for this purpose.

I can't use the Appearing event as that happens right before the page becomes visible.

Same goes for events like Loaded or NavigatedTo, as they all fire before the page becomes visible.

So, my question is: is there a suitable event for performing some long-running task after a page has loaded and become visible to the user?

Edit: The long-running operation is async, but it seems the data-binding to a CollectionView on the page is what is causing perceived lag when the page loads, which is why I'd like to hold off on data-binding until the page is visible with the ActivityIndicator spinning.

Edit 2: I might have been chasing a red herring. The UI stutters seem to be caused by some SVGs that were loaded in the CollectionView template. Not sure why, perhaps related to the PNG conversion. Either way, I don't think I actually need the type of event I was originally looking for.

Ted Nyberg
  • 7,001
  • 7
  • 41
  • 72
  • Well if the long-running task is Async(obviously) then why can't you run it OnAppearing? – FreakyAli Oct 07 '22 at 05:37
  • 1
    It is async, but I'm trying to figure out why this specific page causes stuttering and a delay when navigating to it, so I wanted to see if I could rectify it by loading a "blank loader page" and then initiate the async operation. For the impression of performance, I want all processing power to go the UI thread and hold off on everything else, if that makes sense. – Ted Nyberg Oct 07 '22 at 06:05
  • So what you should do in such situations is Run this through Task.Run which isn't awaited so its Fire and Forget but through that method, you raise a bool flag that shows an indicator for the loading. I hope I am making sense. – FreakyAli Oct 07 '22 at 06:08
  • I see what you mean, but I think one of the culprits is the data-binding to a `CollectionView`, that's what seems to be causing the delay, not the data-fetching itself. So, I'd like to hold off on the data-binding until after the page is visible and the `ActivityIndicator` is spinning. – Ted Nyberg Oct 07 '22 at 06:13
  • Why don't you add your View and ViewModel and let's see what we can do ? – FreakyAli Oct 07 '22 at 06:16

2 Answers2

6

One approach that I sometimes use for this is the following.

The heavy-lifting (loading) goes into your ViewModel, but you get a handle to the LoadAsync() method before even instantiating the View. You can bind your ActivityIndicator to an IsBusy property. Once everything is loaded, you can hide the ActivityIndicator.

ViewModel

private bool _isBusy;
public bool IsBusy
{
    get => _isBusy;
    set
    {
        _isBusy = value;
        OnPropertyChanged();
    }
}

public async Task LoadAsync()
{
    IsBusy = true;
    //Some long running operation 
    IsBusy = false;
}

View

<ActivityIndicator IsRunning="{Binding IsBusy}"/>

Navigation

In the code where you instantiate the View and ViewModel, do the following:

var viewModel = new ViewModel();
var loadTask = viewModel.LoadAsync();
await Navigation.PushAsync(new View(viewModel));
await loadTask;

Edits:

Alternative 1: Simplification

var viewModel = new ViewModel();
await Navigation.PushAsync(new View(viewModel));
await viewModel.LoadAsync();

Alternative 2: Defer Data Binding

Do not set the Binding for the ItemsSource property in XAML, but only in the code-behind, like follows:

Code Behind

private readonly ViewModel _vm;

public View MyView(ViewModel vm)
{
    InitializeComponent();
    BindingContext = _vm = vm;
    LoadAfterConstruction();
}

private async void LoadAfterConstruction()
{
    _vm.IsBusy = true;
    await _vm.LoadAsync();

    //only set the binding of the CollectionView after loading completed
    MyCollectionView.SetBinding(ItemsView.ItemsSourceProperty, nameof(MyCollection));

    _vm.IsBusy = false;
}

XAML:

<CollectionView x:Name="MyCollectionView" />

https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/collectionview/populate-data#populate-a-collectionview-with-data

In either case, your view becomes visible and then loading sets in.

Note: This code is not written for optimization or MVVM-correctness.

Julian
  • 5,290
  • 1
  • 17
  • 40
  • Thanks for the suggestion! That's conceptually close to what I'm doing now, although it doesn't solve the problem of deferring the data-binding of the `CollectionView` until the page (and the `ActivityIndicator`) is visible on screen. I've moved the data-fetching to app start for now (just to take it out of the equation) to focus on a "snappier" way of loading the page even if the `CollectionView` data-binding takes time. – Ted Nyberg Oct 07 '22 at 06:57
  • So, basically, your goal is to only show the CollectionView once it's binding is set and the CollectionView is fully loaded with the bound data? – Julian Oct 07 '22 at 07:29
  • @TedNyberg I've updated my answer with a possible way to defer the data binding until after the rest of the data is loaded. Now, I don't know whether this will actually allow to wait for the CollectionView to be fully populated before the ActivityIndicator disappears. – Julian Oct 07 '22 at 07:41
  • Thanks again! I've tried data-binding async in event handlers for both `Loaded` and `Appearing`, which would be similar to your example, but I can't quite get around the stuttering. I need to test some more to rule out if it's specific to the emulator or debug builds. – Ted Nyberg Oct 07 '22 at 08:20
  • 1
    Alright, you're welcome. I think if the problem is related to the CollectionView itself, then there is not much to do about from this end. Good luck! – Julian Oct 07 '22 at 08:45
3

This was a red herring.

The problem wasn't related to CollectionView, but rather the SVG images (icons) that were rendered on the page.

Because of the SVG's having large view boxes, the Resizetizer in .NET MAUI converted them to very large PNGs, which when rendered caused the UI to become choppy.

Setting BaseSize attributes to more reasonable icon sizes on the <MauiImage /> elements in .csproj made the UI snappy.

Ted Nyberg
  • 7,001
  • 7
  • 41
  • 72