简体   繁体   中英

How to show progress during app startup in Xamarin Forms

I have a Xamarin Forms app that needs to perform a sequence of long-running operations during startup. I would like to inform the user of progress, using a page with a simple status message which changes as the sequence progresses.

The sequence of operations is implemented as a set of asynchronous methods, which I have wrapped in an async container method that also contains the code to update the UI. Here's a simplified version:

private async Task DoStartupSequence()
{
    SetStatusMessage("Step 1");
    await DoStep1();
    SetStatusMessage("Step 2");
    await DoStep2();
    SetStatusMessage("Step 3");
    await DoStep3();
    SetStatusMessage("Completed");
}

However, regardless of how I invoke the sequence, the UI doesn't get updated until execution of the sequence is complete.

I have tried running the sequence on its own thread from within the App constructor:

public App()
{
    InitializeComponent();
    Task.Run(() => DoStartupSequence()).Wait();
}

I have also tried invoking the sequence asynchronously from within the OnStart method:

protected override async void OnStart()
{
    await DoStartupSequence();
}

In a WinForms app, this problem might be solved using Application.DoEvents (although I understand that this approach has its own problems). In any case, Xamarin Forms doesn't appear to support anything like this.

This seems like such a common requirement that I imagine there must be a standard pattern for its implementation. Does anyone know what it is?

Many thanks,

Tim

Update : In response to suggestions from @BraveHeart, I am able to further simplify my test code to show what's not working properly.

The following sample references 2 static pages, each containing just a label. The 1st page announces the start of a long-running sequence and the 2nd page announces its completion. My goal is to see these 2 pages rendered with a 1-second delay between the first and the second. In practice, what happens is that I never see page 1, because both pages are rendered together.

public App()
{
    InitializeComponent();

    var navigationPage = new NavigationPage();

    MainPage = navigationPage;

    navigationPage.Navigation.PushAsync(new TestPage1(), false).Wait();

    Task.Delay(1000).Wait();

    navigationPage.Navigation.PushAsync(new TestPage2(), false).Wait();
}

Changing in the UI should be only via the UI thread .

public App()
{
    InitializeComponent();
    Task.Run(() => DoStartupSequence()).Wait();
}

This piece of code here tells the program to create another Task (thread) to perform DoStartupSequence() including SetStatusMessage() which I assume it just sets a content of a label or something like that and DOES NOT have other awaiting stuff in it.

So try to make it like this

public App()
{
    InitializeComponent();
    StartupSequence(); 
}

private async void StartupSequence()
{
    SetStatusMessage("Step 1");
    await DoStep1();
    SetStatusMessage("Step 2");
    await DoStep2();
    SetStatusMessage("Step 3");
    await DoStep3();
    SetStatusMessage("Completed");
}

I think this would solve your problem. However, My recommendation is to put the code of "DoSteps" and in the viewmodel level, and bind the content of the label to a property in the viewmodel this way your code behind would be much cleaner and you do not have to worry about the threads, I think .

Another point I have managed to note is the naming conventions. In short words Anything that is awaitalble should get a name+"Async" as postfix so it would be

await DoStep1Async();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM