5

The async/await threading is driving me crazy here. I have done a lot of research on the possibility to do Async initialization.

Here is the problem. I am trying to make my app load into a specific page according to the current apps status. To determine the status of the app. It includes scanning into the Data folder. In order to use the Storage API in UWP. I must create an async dependency service in Xamarin Forms.

I am using a singleton pattern with an AppManager class here.

AppManager.cs

public class AppManager{
    public static AppManager Instance = new AppManager();
    public List<Jobs> JobList = new List<Jobs>();

    public async Task InitializeAsync(){
        JobList.AddRange(await DependencyService.Get<IFileService>().GetJobs());
    }

    public bool JobExists(string jobName){
        return JobList.Any(j => j.Name == jobName);
    }
}

When the App starts, it will need to load the JobList from the Data folder first. Then look into the JobList and determine which page to go navigate to.

What I am trying to do currently is calling the code below from the constructor of the App class.

App.xaml.cs

InitializeComponent(); // Default

var appManager = AppManager.Instance;
appManager .InitializeAsync();
if(appManager .JobExists("JobA")){
    MainPage = new PageA();
}else{
    MainPage = new PageB();
}

This will always bring me to PageB as the list is empty because the initialization is not finished. This is caused by running async code in synchronous thread.

I have read about multiple ways to make Async constructors in this blog. However, they still require me to make an await function in the call which is not possible in this case.

I have also tried moving the whole App() constructor code into an async Task function then call InitializeAsync().Wait() in the constructor. However, Xamarin Forms will complain at runtime. As App is having null reference on MainPage.

I would like to know if it is possible to achieve the full initialization using an awaited function without involving bad practices(e.g. async void)? I also have some idea of how to work around for the situation, such as using SystemIO instead of Storage API will still work for Xamarin Forms UWP. But I would really like to go deep into async function instead of just making it work. Thanks.

1
  • 2
    It will be logical to move your async stuff to one of the Application life cycle events instead of the constructor. Having such a login in constructor is not the best practice. Commented Apr 19, 2018 at 8:20

1 Answer 1

3

The problem is that Xamarin.Forms expects the MainPage to be set as soon as it is initialized. If you use async call, you need to wait for it to finish and that will inevitably cause XF to complain about the missing page.

There are three solutions for this problem.

You can display a temporary "loading page" on startup. In such case you first set MainPage to a page that informs the user that the app is loading:

public App()
{
   MainPage = new LoadingPage();
}

And you then proceed with the async operation in the OnStart override:

protected override async void OnStart()
{
   var appManager = AppManager.Instance;
   await appManager.InitializeAsync();
   if(appManager .JobExists("JobA"))
   {
      MainPage = new PageA();
   }
   else
   {
      MainPage = new PageB();
   }
}

Second option you have is to wire up the async initialization tasks before Xamarin.Forms is initialized. For UWP, you could go to the UWP project's App.xaml.cs file and put this in the OnLaunched method override right before the Xamarin.Forms initialization:

protected override async void OnLaunched(LaunchActivatedEventArgs e)
{
    ...

    var appManager = AppManager.Instance;
    await appManager.InitializeAsync();
    Xamarin.Forms.Forms.Init(e);

    ...
}

This will just prolong the UWP-based splash screen but when Xamarin.Forms is initialized, the AppManager will already be initialized.

Final option you have is described in this blog post by Adam Pedley. It allows you to run the task on UI thread but will block until the task is finished using a TaskHelper library.

Exrin.Common.ThreadHelper.Init(SynchronizationContext.Current);
Exrin.Common.ThreadHelper.RunOnUIThread(async () => { await MyMethod(); });

This should probably be the last option, because it can be considered a hack, than a proper solution.

Sign up to request clarification or add additional context in comments.

1 Comment

The second option can be done on iOS and Android as well right?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.