Reputation: 8927
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();
}
Upvotes: 1
Views: 2360
Reputation: 2617
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();
Upvotes: 2