How to keep UWP UI responsive?

268 views Asked by At

I have a UWP app in which one of the pages needs to do three tasks - the first is to load the main content for the page (a collection of 'Binders' objects retrieved from our API) then after that load some other content which are not dependent on the first task in any way.

My page is backed with a ViewModel (I'm using the default Template10 MVVM model) and when the page is navigated to I do this in the VM OnNavigatedToAsync method:

public async override Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> state)
{
    if (mode == NavigationMode.New || mode == NavigationMode.Refresh)
    {
        IsBusy = true;      //Show progress ring
        CreateServices();   //Create API service

        //Download binders for board and populate ObservableCollection<Binder>
        //This has a cover image and other info I want to show in the UI immediately 
        await PopulateBinders();

        //Get files and calendar events for board
        //Here I want to run this on a different thread so it does 
        //not stop UI from updating when PopulateBinders() is finished
        await Task.WhenAll(new[]
        {
            PopulateBoardFiles(),
            PopulateBoardEvents()
        });               

        IsBusy = false;
        await base.OnNavigatedToAsync(parameter, mode, state);
        return;          
    }
}

So the main task is PopulateBinders() - this calls the API, returns the data and loads it into an ObservableCollection of Binder. When this has run I want the UI to update it's bindings and show the Binder objects immediately but instead it waits until the two other tasks in the WhenAll Task) have run before updating the UI. (All three of these Tasks are defined as private async Task&lt;bool&gt;...)

I realise I'm missing something basic here - but I thought calling a Task from an async method would allow the UI to update? Since it clearly doesn't how do I refactor this to make my page bindings update after the first method?

I tried Task.Run(() => PopulateBinders()); but it made no difference.

2

There are 2 answers

0
Dishant On

I hope this code will help you to update the UI as expected:

public async override Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> state)
{
    if (mode == NavigationMode.New || mode == NavigationMode.Refresh)
    {
        IsBusy = true;      //Show progress ring
        CreateServices();   //Create API service

        //Download binders for board and populate ObservableCollection<Binder>
        //This has a cover image and other info I want to show in the UI immediately 
        await PopulateBinders();

        await PouplateBoardData();

        await base.OnNavigatedToAsync(parameter, mode, state);
        return;          
    }
}

private async void PopulateBoardData()
{
    await Task.WhenAll(new[]
    {
            PopulateBoardFiles(),
            PopulateBoardEvents()
    });               

    IsBusy = false;
}
0
Little doe On

Instead of running it inside OnNavigatedToAsync(), run the asynchronous task when the page is already loaded since you are unintentionally "block" the app to run base.OnNavigatedToAsync() for several seconds untill Task.WhenAll finished running.

Running on loaded event in MVVM can be achieved by implementing Microsoft.Xaml.Interactivity to bind Page.Loaded event with a DelegateCommand class in your viewmodel.

XAML Page ( assuming you are using Prism as your MVVM framework )

<Page ...
    xmlns:core="using:Microsoft.Xaml.Interactions.Core"
    xmlns:interactivity="using:Microsoft.Xaml.Interactivity">
    <interactivity:Interaction.Behaviors>
        <core:EventTriggerBehavior EventName="Loaded">
            <core:InvokeCommandAction Command="{x:Bind Path=Vm.PageLoaded}" />
        </core:EventTriggerBehavior>
    </interactivity:Interaction.Behaviors>
</Page>

and inside your viewmodel:

public class PageViewModel : ... //some interface or else 
{
    public DelegateCommand PageLoaded;
    public PageViewModel(...)
    {
         PageLoaded = new DelegateCommand(async () =>
         {
                IsBusy = true;      
                CreateServices();  

                await PopulateBinders();

                await Task.WhenAll(new[]
                {
                    PopulateBoardFiles(),
                    PopulateBoardEvents()
                });               

                IsBusy = false;
         });
    }
}

Read more : Binding UWP Page Loading/ Loaded to command with MVVM