DazzledKid
DazzledKid

Reputation: 396

Set ProgressBar visibility from ViewModel before and after retrieving data from webservice service

I am creating a Windows Phone 8.1 app using an MVVM design pattern and the MVVM Light toolkit.

I am trying to create a simple login page that takes a username and password from a user and authenticates with a webservice. During this authentication process I want to show a ProgressBar (loading dots) at the top of the page to indicate that something is happening.

I have successfully created my ViewModel and bound my View to allow me to control the visibility of the ProgressBar from a Command attached to my login button. This works, however, the UI only updates to show the ProgressBar once the entire Login process is completed, making the use of a progress indicator pointless.

How do I first set the visibility of the ProgressBar (and have the UI update) then go off an perform my Login process?

Here is some code:

xaml

<ProgressBar IsIndeterminate="True" Height="1" Visibility="{Binding Login.ProgressVisibility, UpdateSourceTrigger=PropertyChanged, FallbackValue=Collapsed}" />

ViewModel

public class LoginViewModel : ViewModelBase
{
    private IDialogService dialogService;
    private INavigationService navigationService;

    private string _username;
    private string _password;

    private Visibility _progressVisibility = Visibility.Collapsed;

    /// <summary>
    /// User login name / email address
    /// </summary>
    public string Username
    {
        get
        {
            return _username;
        }
        set
        {
            Set(() => Username, ref _username, value);
        }
    }

    /// <summary>
    /// User password
    /// </summary>
    public string Password
    {
        get
        {
            return _password;
        }
        set
        {
            Set(() => Password, ref _password, value);
        }
    }

    /// <summary>
    /// Is loading in progress
    /// </summary>
    public Visibility ProgressVisibility
    {
        get
        {
            return _progressVisibility;
        }
        set
        {
            Set(() => ProgressVisibility, ref _progressVisibility, value);
        }
    }

    /// <summary>
    /// Perform login action
    /// </summary>
    public RelayCommand LoginCommand { get; private set; }

    /// <summary>
    /// constructor
    /// </summary>
    /// <param name="dialogService"></param>
    public LoginViewModel(IDialogService dialogService, INavigationService navigationService)
    {
        this.LoginCommand = new RelayCommand(this.Login, CanLogin);
        this.dialogService = dialogService;
        this.navigationService = navigationService;

    }

    public void Login()
    {
        this.ProgressVisibility = Visibility.Visible;

        //check the credentials have been provided
        if (this._username == null || this._password == null)
        {
            //show dialogue
            dialogService.ShowMessage("Please enter you login credentials", "Login");
        }
        else
        {

            //perform an authentication request with the service
            ValidateLoginRequest request = new ValidateLoginRequest();

            request.Device = new Device();
            request.UserName = this.Username;
            request.Password = this.Password;

            var response = Task.Run(() => LoginAsync(request)).GetAwaiter().GetResult();

            if (response != null)
            {
                if (response.IsSuccessful)
                {                        
                    navigationService.NavigateTo("Main");
                }
                else
                {

                    dialogService.ShowMessage(response.ErrorCode + " :" + response.ErrorMessage, "Login");
                }
            }
            else
            {
                //failure
                dialogService.ShowMessage("ECOM000: An unknown error has occurred", "Login");
            }
        }



    }

    async Task<ValidateLoginResponse> LoginAsync(ValidateLoginRequest request)
    {
        Model.RuntimeDataService proxy = new Model.RuntimeDataService();
        ValidateLoginResponse response = (ValidateLoginResponse)await proxy.PostAsync(request, typeof(ValidateLoginResponse), "ValidateLogin");
        return response;
    }

    public bool CanLogin()
    {
        return true;
    }
}

Upvotes: 3

Views: 4268

Answers (2)

Alexiscanny
Alexiscanny

Reputation: 579

I solved this using Kotlin by adding my own CustomBindingAdapter:

@BindingAdapter("android:visibility")
fun setVisibility(view: View, visible: Boolean) {
view.visibility = if (visible) View.INVISIBLE else View.VISIBLE
}

Inside your view.xml: First add data variable:

<data>
    <variable name="loginViewModel" type="yourpackage.viewmodel.LoginViewModel"/>
</data>

<ProgressBar
        android:id="@+id/login_progress_bar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:indeterminate="true"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:visibility="@{!loginViewModel.loginProgress}" />

Your Activity must have:

val binding = DataBindingUtil.setContentView<LoginViewBinding>(this, R.layout.login_view)
val loginViewModel = LoginViewModel(this)
binding.setLoginViewModel(loginViewModel)

At the end your ViewModel needs to handle the visibility:

    var loginProgress = ObservableField<Boolean>()
fun doSomething(){
        this.loginProgress.set(true)
}

And set loginProgress.set(false) wherever you need to stop the progressBar.

I hope this can help someone else ;)

Upvotes: 2

Glen Thomas
Glen Thomas

Reputation: 10764

You are creating a background Task and then causing the UI thread to wait for the response.

Try awaiting the Task returned from the LoginAsync method:

var response = await LoginAsync(request);

This should allow your UI thread to continue, with the rest of the method acting as a callback.

You will probably need to add the async keyword to the Login method.

Upvotes: 0

Related Questions