2
votes

I would like to perform next using Xamarin:

  • when application starts splashScreen is immediately shown (I've achieved this following next tutorial https://channel9.msdn.com/Blogs/MVP-Windows-Dev/Using-Splash-Screen-with-Xamarin-Forms)

  • execute db migrations if any (in case user updated app and runs it the first time)

  • read user data (username and password) from db, call a REST webservice to check if user data are still valid. If user data is valid redirect user to MainPage otherwise redirect to LoginPage

I've read next good post about Xamarin.Forms Async Task On Startup. Current code:

public class MainActivity :global::Xamarin.Forms.Platform.Android.FormsApplicationActivity
{
  protected override void OnCreate (Bundle bundle)
  {
    base.OnCreate (bundle);
    global::Xamarin.Forms.Forms.Init (this, bundle);
    LoadApplication (new App ()); // method is new in 1.3
  }
}

// shared code - PCL lib for Android and iOS
public partial class App : Application
{
  public App()
  {
    InitializeComponent();
    // MainPage = new LoadingPage();
  }
}    
protected override async void OnStart()
{
  // Handle when your app starts
  await App.Database.Migrations();
  if( await CheckUser()) // reads user data from db and makes http request
    this.MainPage = new Layout.BrowsePage();
  else
    this.MainPage = new LoginPage();
 }

If MainPageis not set in constructor Exception is thrown on iOS and Android. I know that async void doesn't wait if it does'not explicitly have .Wait() - Async void, but does this mean that executing thread still continues it's work.

When executing thread hits await App.Database.Migrations(); it pauses the execution and await for await Task to complete. Meanwhile it continues it's work (i.e. LoadApplication() continues to execute and expects that App.MainPage is set by now). Is my assumption correct?

I just want to avoid LoadingPage because three screens are shown:

  • splash screen (right when app is started)
  • LoadingPage (db migrations, http request, ..)
  • BrowsePage or LoginPage

Desirable would be just two pages, for user experience.

I ended up like this, but I believe there is a better approach:

protected override void OnStart()
{
  Page startPage = null;
  Task.Run(async() =>
  {
    await App.Database.Migrations();
    startPage = await CheckUser() ? new Layout.BrowsePage() : new LoginPage();
  }.Wait();
  this.MainPage = startPage();
}
2
What are you looking for in a 'better approach'? And why do you consider showing a loading page to be an inferior user experience?David Oliver
I suppose if application takes 5-8 seconds to load then loading page is ok. It shows the user that something is going on (migrations in progress, ...). I measured time and it takes 7 seconds to start the application (Samsung Galaxy A5).broadband

2 Answers

2
votes

Well you can just place everything into the App constructor, and the splashscreen will just show longer, while you load the app, but I actually believe this is an inferior solution.

Having a Splashscreen, then a Loading Page, that looks exactly like the splashscreen, but with a progress bar, or loading icon, telling the user what it is doing, is a much better way.

If you have to do this before the user sees the first page, then a loading screen is a good option to let the user know why its taking so long.

The only other thing I would add, is to store a boolean or version number of the database that already has loaded, so you don't need to run that migration code, unless your app version increases.

By doing this you could place this boolean check in your App constructor, and load the first page straight away. If migrations are needed, then load the loading page, and let the OnStart complete the migrations and then load the first app screen.

0
votes

An alternative way would be to add each of the items you want to run concurrently to a list of tasks and then wait for all. Then all items will run async but you can still get the result of CheckUser. I don't know if it is any better than what you used but it is another way. Otherwise if you are awaiting each function then they aren't running concurrently.

    protected async Task OnStart()
    {
        Page startPage = null;
        //create a list of the items you want to run concurrently
        List<Task> list = new List<Task>();
        list.Add(App.Database.Migrations());
        list.Add(CheckUser());
        //wait for all of them to complete
        Task.WaitAll(list.ToArray());

        //get the result from the CheckUser call
        Task<bool> checkUsertask = list[1] as Task<bool>;
        if (checkUsertask.Result == true)
            this.MainPage = new Layout.BrowsePage();
        else
            this.MainPage = new LoginPage();
    }

If you don't like the list[1] then you can create the one task before adding it to the list so you can refer to it once complete.

protected async Task OnStart()
{
    Page startPage = null;
    //create a list of the items you want to run concurrently
    List<Task> list = new List<Task>();
    list.Add(App.Database.Migrations());
    var checkUserTask = CheckUser();
    //create this task directly so we can refer to it later to get the result
    list.Add(checkUserTask);
    //wait for all of them to complete
    Task.WaitAll(list.ToArray());

    //get the result from the CheckUser call
    if (checkUserTask.Result == true)
        this.MainPage = new Layout.BrowsePage();
    else
        this.MainPage = new LoginPage();
}