Frank
Frank

Reputation: 1080

Xamarin - How should I handle UI state changes in a Command prior to an async API call

I typically use the Command pattern with Xamarin forms. Recently the UIs I have been working on involve commands that call a remote API, and while they do so they indicate that something is going on in a number of ways. For example, an ActivityIndicator should start to spin, when the API call returns the page should disable the button, etc.

What I would intuitively code up is an async ICommand like this:

VerifyCodeCommand = new Command(async () =>
            {
                await VerifyCode(_email,_code);
            });

while in the VerifyCode function I would change the property bound to the IsRunning property of the ActivityIndicator to true, await the API call, then on completion change the IsRunning/Visible of the ActivityIndicator back to false. For example like this: Another example is showing an API failed state (eg: red warnings) and then resetting it immediately before the API call.

Unfortunately this does not work, and neither does setting the bound property on Device.BeginInvokeOnMainThread, as (presumably) the ICommand is holding the UI thread (?) . What I do with this simple case is set the property to true in the command, then Task.Run the remainder of the function body, i.e. the API call and resetting the indicator, like this:

 Sending = true;

            (VerifyCodeCommand as Command).ChangeCanExecute();

            await Task.Run(async () =>

            {

                try

                {

                  // await api call here

                }

                catch (Exception ex)

                {

                    App.TrackError(ex, "Login", "VerifyCode API call failed.");

                }

                finally

                {

                    Sending = false;

                    (VerifyCodeCommand as Command).ChangeCanExecute();

                }

            });







        }

Is this approach necessary or am I missing some area of functionality in Xamarin (Forms)? If it is always necessary, to update the UI this way, what would be a sensible approach for 'patternising' this? For example, should Commands always have two actual lambdas, one for initial UI state set up, one for background invocation? Why is it that a simple single threaded async approach does not work here - the API call is being awaited after all.

I suppose why question distills down to: why is it that invoking an async API call with await on the main thread appears as though the main thread is not being freed to do UI updates (this seems like Xamarin UI lifecycle is introducing some limitation) and what is the standard solution for this?

Upvotes: 0

Views: 434

Answers (1)

ChrisBD
ChrisBD

Reputation: 9209

I think that you're issue is that you're over laying an await Task onto the asynchronous nature of the Command.

As an example from a current app: All of my ViewModels have a property bool IsBusy

Property public ICommand RefreshStoredDataCommand {get;}

In the constructor : RefreshStoredDataCommand = new Command(async () => await ExecuteRefreshDataCommand(), () => !IsBusy);

private async Task ExecuteRefreshDataCommand()
{
    IsBusy = true;
    try
    {
      //Long calls to asynchronous processes
    }
    catch (Exception e)
    {
        Debug.WriteLine($"ProjectViewModel:ExecuteRefreshDataCommand exception{e.Message}");
    }
    finally
    {
        IsBusy = false;
    }

}

For the example shown I have in my View a ListView control where I set

RefreshCommand ="{Binding RefreshStoredDataCommand}"
IsRefreshing="{Binding IsBusy, Mode=OneWay}"

Obviously for your code you'd bind your activity indicator instead.

Upvotes: 1

Related Questions