2
votes

Hi have a seemingly fairly simply Login form which I have bound to a viewmodel.

Everything works swimmingly aside from the activity indicator. I have tried all manner of things to get it to appear.

As I understand it, the IsVisible and IsRunning properties when set to true should make the indicator appear. I have these bound to a bool property which is getting set to true/false throughout the login command.

What do I need to do to get it to display?

Login.xaml

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="TechsportiseApp.Views.Login">
    <ContentPage.ToolbarItems>
        <ToolbarItem Name="Register" Order="Primary" Icon="addperson.png" Text="Register" Priority="0" Command="{Binding RegisterCommand}" />
        <ToolbarItem Name="Help" Order="Primary" Icon="help.png" Text="Help" Priority="1" Command="{Binding HelpCommand}" />
    </ContentPage.ToolbarItems>
    <ContentPage.Content>
        <RelativeLayout VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
            <StackLayout HorizontalOptions="FillAndExpand">
                <StackLayout HorizontalOptions="FillAndExpand" BackgroundColor="Red" Padding="2" IsVisible="{Binding IsOffline}">
                    <Label Text="OFFLINE" BackgroundColor="Red" TextColor="White" FontAttributes="Bold" FontSize="Small" HorizontalOptions="FillAndExpand" HorizontalTextAlignment="Center" />
                </StackLayout>
                <StackLayout VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" Orientation="Vertical">
                    <ScrollView Orientation="Vertical" VerticalOptions="StartAndExpand">
                        <StackLayout VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" Orientation="Vertical" Margin="10">
                            <Image Source="splash.png" HorizontalOptions="Center" />
                            <Label Text="Race Director" FontAttributes="Bold" FontSize="Large" HorizontalTextAlignment="Center" HorizontalOptions="CenterAndExpand" />
                            <Label Text="by Techsportise" HorizontalTextAlignment="Center" HorizontalOptions="CenterAndExpand" />
                            <BoxView HeightRequest="20" HorizontalOptions="FillAndExpand" />
                            <Entry x:Name="email" Text="{Binding Email}" Placeholder="Email address" />
                            <Entry x:Name="password" Text="{Binding Password}" IsPassword="true" Placeholder="Password" />
                            <StackLayout Padding="3" Orientation="Horizontal">
                                <Label Style="{StaticResource EntryFormLabels}" Text="REMEMBER ME" FontSize="Small" HorizontalOptions="StartAndExpand" VerticalTextAlignment="Center" />
                                <Switch IsToggled="{Binding RememberMe}" HorizontalOptions="End" />
                            </StackLayout>
                            <Button x:Name="loginButton" Text="Login" Command="{Binding LoginCommand}" Style="{StaticResource Buttons}" />
                            <ActivityIndicator IsVisible="{Binding IsBusy}" IsRunning="{Binding IsBusy}" Color="#80000000" />
                            <Label Text="{Binding ValidationError}" HorizontalTextAlignment="Center" HorizontalOptions="CenterAndExpand" TextColor="Red" FontSize="Small" IsVisible="{Binding Invalid}" />
                        </StackLayout>
                    </ScrollView>
                </StackLayout>
            </StackLayout>
            <ActivityIndicator IsVisible="{Binding IsBusy}"
                               IsRunning="{Binding IsBusy}"
                               Color="Black"
                               VerticalOptions="CenterAndExpand"
                               HorizontalOptions="CenterAndExpand"
                               RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent,
                                        Property=Width,
                                        Factor=1}"
                               RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent,
                                        Property=Height,
                                        Factor=1}" />
        </RelativeLayout>
    </ContentPage.Content>
</ContentPage>

Login.xaml.cs

using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TechsportiseApp.Views;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using TechsportiseApp.API;
using TechsportiseApp.ViewModels;
using TechsportiseApp.Models;
using Newtonsoft.Json;

namespace TechsportiseApp.Views
{
    public partial class Login : ContentPage
    {
        public Login ()
        {
            InitializeComponent ();
            var viewModel = new LoginViewModel();
            BindingContext = viewModel;
        }

        public Login(string email)
        {
            InitializeComponent();
            var viewModel = new LoginViewModel(email);
            BindingContext = viewModel;
        }
    }
}

Loginviewmodel

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Input;
using TechsportiseApp.API;
using TechsportiseApp.Helpers;
using TechsportiseApp.Models;
using TechsportiseApp.Views;
using Xamarin.Forms;

namespace TechsportiseApp.ViewModels
{
    public class LoginViewModel : INotifyPropertyChanged
    {
        public INavigation Navigation { get; set; }
        public ICommand LoginCommand { get; set; }
        public ICommand HelpCommand { get; set; }
        public ICommand RegisterCommand { get; set; }
        public LoginViewModel()
        {
            LoginCommand = new Command(Login);
            HelpCommand = new Command(Help);
            RegisterCommand = new Command(Register);
            Invalid = false;
        }

        public LoginViewModel(string email)
        {
            LoginCommand = new Command(Login);
            HelpCommand = new Command(Help);
            RegisterCommand = new Command(Register);
            Invalid = true;
            ValidationError = "the account for " + email + " has been created.";
        }


        private bool _isBusy;
        public bool IsBusy
        {
            get { return _isBusy; }
            set
            {
                if (_isBusy == value)
                    return;

                _isBusy = value;
                OnPropertyChanged("IsBusy");
            }
        }

        private bool _isOffline;
        public bool IsOffline
        {
            get
            {
                _isOffline = !GlobalFunctions.CheckForInternetConnection();
                return _isOffline;
            }
        }

        private bool _rememberMe;
        public bool RememberMe
        {
            get
            {
                if (Application.Current.Properties.ContainsKey("IsRemembered"))
                {
                    _rememberMe = bool.Parse(Application.Current.Properties["IsRemembered"].ToString());
                    return _rememberMe;

                }
                else
                {
                    return _rememberMe;
                }
            }
            set
            {
                if (_rememberMe == value)
                    return;

                _rememberMe = value;
                Application.Current.Properties["IsRemembered"] = _rememberMe;
                OnPropertyChanged("RememberMe");
            }
        }

        private bool _invalid;
        public bool Invalid
        {
            get { return _invalid; }
            set
            {
                if (_invalid == value)
                    return;

                _invalid = value;
                OnPropertyChanged("Invalid");
            }
        }

        private string _validationError;
        public string ValidationError
        {
            get { return _validationError; }
            set
            {
                if (_validationError == value)
                    return;

                _validationError = value;
                OnPropertyChanged("ValidationError");
            }
        }

        private string _email;
        public string Email
        {
            get { return _email; }
            set
            {
                if (_email == value)
                    return;

                _email = value;
                OnPropertyChanged("Email");
            }
        }

        private string _password;
        public string Password
        {
            get { return _password; }
            set
            {
                if (_password == value)
                    return;

                _password = value;
                OnPropertyChanged("Password");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            var changed = PropertyChanged;
            if (changed != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        void Login()
        {
            IsBusy = true;
            var isvalidemail = GlobalFunctions.IsValidEmail(Email);
            var IsThereConnection = GlobalFunctions.CheckForInternetConnection();

            if (IsThereConnection == false)
            {
                Invalid = true;
                ValidationError = "You cannot login whilst you are offline";
                IsBusy = false;
                return;
            }
            else if (Email == "")
            {
                Invalid = true;
                ValidationError = "You must enter an email address";
                IsBusy = false;
                return;
            }
            else if (Password == "")
            {
                Invalid = true;
                ValidationError = "You must enter a password";
                IsBusy = false;
                return;
            }
            else if (isvalidemail == false)
            {
                Invalid = true;
                ValidationError = "You must enter a valid email address";
                IsBusy = false;
                return;
            }
            //We are good to go
            else
            {
                IsBusy = true;
                var LoginStatus = AccountsAPI.Login(Email, Password);
                if (LoginStatus.Code == "OK")
                {
                    var tokenobject = JsonConvert.DeserializeObject<TokenModel>(LoginStatus.Content);
                    Application.Current.Properties["Token"] = tokenobject.Access_token;
                    Application.Current.Properties["IsRemembered"] = RememberMe;
                    string token = Application.Current.Properties["Token"].ToString();
                    if ((bool)Application.Current.Properties["ShowHelpOnStartup"] == true)
                    {
                        App.Current.MainPage = new StartupHelp(true);
                    }
                    else
                    {
                        App.Current.MainPage = new MainMenuMasterDetail();
                    }


                }
                //Error response
                else
                {
                    IsBusy = false;
                    Invalid = true;
                    ValidationError = "Your login has failed. Please check your details and try again.";
                }
            }
        }

        void Register()
        {
            Xamarin.Forms.Application.Current.MainPage.Navigation.PushAsync(new Register());
        }

        void Help()
        {
            Xamarin.Forms.Application.Current.MainPage.Navigation.PushAsync(new LoginHelp());
        }
    }
}
1
Hi Matthew. It's because the code you run at the viewModel probably is freezing the screen while is running. The activity indicators are being shown and hidden at the same time when the code of Loginmethod ends. - Diego Rafael Souza
Try change the method to async. It should do the trick - Diego Rafael Souza
Thanks Diego - any advice on the best way to accomplish this? I've changed the void Login() to void DoLogin() and then added a new method protected async Task Login() { await Task.Run(() => DoLogin()); } However the command I am using in my constructor says it the async method has the wrong return type? - Matthew Warr
See the suggestion I've posted as answer. A clearest code with the behavior you want. - Diego Rafael Souza

1 Answers

3
votes

Try to change your ViewModel method Login to be like this:

private void Login()
{
    Task.Run(() => 
    {
        try
        {
            Device.BeginInvokeOnMainThread(() => IsBusy = true);
            var isvalidemail = GlobalFunctions.IsValidEmail(Email);
            var IsThereConnection = GlobalFunctions.CheckForInternetConnection();

            if (IsThereConnection == false)
                throw new Exception("You cannot login whilst you are offline");
            else if (Email == "")
                throw new Exception("You must enter an email address");
            else if (Password == "")
                throw new Exception("You must enter a password");
            else if (isvalidemail == false)
                throw new Exception("You must enter a valid email address");
            //We are good to go
            else
            {
                var LoginStatus = AccountsAPI.Login(Email, Password);
                if (LoginStatus.Code == "OK")
                {
                    var tokenobject = JsonConvert.DeserializeObject<TokenModel>(LoginStatus.Content);
                    Application.Current.Properties["Token"] = tokenobject.Access_token;
                    Application.Current.Properties["IsRemembered"] = RememberMe;
                    string token = Application.Current.Properties["Token"].ToString();
                    if ((bool)Application.Current.Properties["ShowHelpOnStartup"] == true)
                        Device.BeginInvokeOnMainThread(() => App.Current.MainPage = new StartupHelp(true));
                    else
                        Device.BeginInvokeOnMainThread(() => App.Current.MainPage = new MainMenuMasterDetail());
                }
                //Error response
                else
                    throw new Exception("Your login has failed. Please check your details and try again.");         
            }
        }
        catch(Exception ex)
        {

            Device.BeginInvokeOnMainThread(() => 
            {
                ValidationError = ex.Message;
                Invalid = true;
            });
        }
        finally
        {
            Device.BeginInvokeOnMainThread(() => IsBusy = true);
        }
    });
}