Reputation: 168
I am having problems getting an Activity Indicator to display in my Xamarin.Forms application. I am using XAML with Code-behind and it is bound to a view model.
All the settings appear to my eye to be correct, and when I step through the code I can see the IsBusy property being set to True and False appropriately - but the actual ActivityIndicator does not display at all.
Can any see what I've got wrong?
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;
ToolbarItems.Add(new ToolbarItem("New", "addperson.png", async () =>
{
await Navigation.PushAsync(new Register());
}));
}
public string CleanResponse(string reason)
{
var str = reason;
var charsToRemove = new string[] { "[", "]", "{", "}", "\"" };
foreach (var c in charsToRemove)
{
str = str.Replace(c, string.Empty);
}
return str;
}
async void OnLogin(object sender, EventArgs e)
{
//Validations here
if (email.Text == "")
{
await DisplayAlert("Validation Error", "You must enter an Email address", "OK");
return;
}
else if (password.Text == "")
{
await DisplayAlert("Validation Error", "You must enter a Password", "OK");
return;
}
//We are good to go
else
{
this.IsBusy = true;
string APIServer = Application.Current.Properties["APIServer"].ToString();
var client = new RestClient(APIServer);
var request = new RestRequest("api/account/sign-in", Method.POST);
request.AddHeader("Content-type", "application/json");
request.AddJsonBody(new
{
email = email.Text,
password = password.Text
}
);
var response = client.Execute(request) as RestResponse;
this.IsBusy = false;
//Valid response
if (response.StatusCode.ToString() == "OK")
{
var tokenobject = JsonConvert.DeserializeObject<TokenModel>(response.Content);
Application.Current.Properties["Token"] = tokenobject.Access_token;
string token = Application.Current.Properties["Token"].ToString();
App.Current.MainPage = new NavigationPage(new MainPage());
}
//Error response
else
{
var statuscode = response.StatusCode.ToString();
var content = response.Content;
var exception = response.ErrorException;
var error = response.ErrorMessage;
var statusdesc = response.StatusDescription;
await DisplayAlert("Login Failed", "Your login has failed. Please check your details and try again.", "OK");
}
}
}
}
}
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.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="20, 40, 20, 20"
Android="20, 20, 20, 20"
WinPhone="20, 20, 20, 20" />
</ContentPage.Padding>
<ContentPage.Content>
<AbsoluteLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<StackLayout VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
Orientation="Vertical"
AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1">
<ScrollView Orientation = "Vertical" VerticalOptions="StartAndExpand">
<StackLayout VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
Orientation="Vertical">
<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="" Placeholder="Email address"/>
<Entry x:Name="password" Text="" IsPassword="true" Placeholder="Password"/>
<Button x:Name="loginButton" Text="Login" Clicked="OnLogin" Style="{StaticResource Buttons}"/>
</StackLayout>
</ScrollView>
</StackLayout>
<StackLayout IsVisible="{Binding IsBusy}" Padding="12"
AbsoluteLayout.LayoutFlags="PositionProportional"
AbsoluteLayout.LayoutBounds="0.5,0.5,-1,-1">
<ActivityIndicator IsRunning="{Binding IsBusy}" Color ="#80000000"/>
<Label Text="Loading..." HorizontalOptions="Center" TextColor="White"/>
</StackLayout>
</AbsoluteLayout>
</ContentPage.Content>
</ContentPage>
LoginViewModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace TechsportiseApp.ViewModels
{
public class LoginViewModel : INotifyPropertyChanged
{
private bool _isBusy;
public bool IsBusy
{
get { return _isBusy; }
set
{
if (_isBusy == value)
return;
_isBusy = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Upvotes: 1
Views: 2729
Reputation: 556
This Worked with me:
private async void BtnLogin_OnClickedAsync(object sender, EventArgs e)
{
activityIndicator.IsRunning = true;
await Task.Delay(1000); // This Line solved the problem with me
await LoginMethod();
activityIndicator.IsRunning = false;
}
Upvotes: 1
Reputation: 480
I know this is old, but maybe this can help someone. I too had problems with the ActivityIndicator not showing.
Here's some things I did to get it working. On my ContentPage, it contained a ContentPage.Content element. After removing this tag, it started working with the code below.
Binding property on my codebehind:
public partial class Login : INotifyPropertyChanged
{
private bool _busy = true;
public event PropertyChangedEventHandler PropertyChanged;
public bool Busy
{
get { return _busy; }
set
{
_busy = value;
RaisePropertyChanged("Busy");
}
}
private bool NotBusy => !Busy;
public Login()
{
InitializeComponent();
BindingContext = this;
Busy = false;
}
private async void BtnLogin_OnClicked(object sender, EventArgs e)
{
Busy = true;
await Task.Delay(120); // gives UI a chance to show the spinner
// do async login here
var cts = new CancellationTokenSource();
var ct = cts.Token;
var response = Task.Factory.StartNew(() => YourAuthenticationClass.YourLoginMethod(txtUserId.Text, txtPassword.Text).Result, ct);
// check response, handle accordingly
}
private void RaisePropertyChanged(string propName)
{
System.Diagnostics.Debug.WriteLine("RaisePropertyChanged('" + propName + "')");
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
ContentPage 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="MyApp.Login">
<StackLayout Margin="10,10,10,0">
<Image Source="mylogo.png"
HorizontalOptions="CenterAndExpand"></Image>
<Entry x:Name="txtUserId"
Placeholder="Login Id"></Entry>
<Entry x:Name="txtPassword"
Placeholder="Password"
IsPassword="True">
</Entry>
<Button Text="Login"
Clicked="BtnLogin_OnClicked"
IsEnabled="{Binding NotBusy}"></Button>
<ActivityIndicator
IsRunning="{Binding Busy}"
IsVisible="{Binding Busy}"
IsEnabled="True"
AbsoluteLayout.LayoutFlags="PositionProportional"
AbsoluteLayout.LayoutBounds="0.5,0.5,-1,-1"
VerticalOptions="Center"
HorizontalOptions="Center">
<ActivityIndicator.WidthRequest>
<OnPlatform x:TypeArguments="x:Double">
<On Platform="Android">100</On>
</OnPlatform>
</ActivityIndicator.WidthRequest>
<ActivityIndicator.Color>
<OnPlatform x:TypeArguments="Color">
<On Platform="Android">#ff0000</On>
</OnPlatform>
</ActivityIndicator.Color>
</ActivityIndicator>
</StackLayout>
</ContentPage>
Upvotes: 0
Reputation: 5314
You aren't notifying the changes of IsBusy
property.
Edited code:
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;
ToolbarItems.Add(new ToolbarItem("New", "addperson.png", async () =>
{
await Navigation.PushAsync(new Register());
}));
}
public string CleanResponse(string reason)
{
var str = reason;
var charsToRemove = new string[] { "[", "]", "{", "}", "\"" };
foreach (var c in charsToRemove)
{
str = str.Replace(c, string.Empty);
}
return str;
}
}
}
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.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="20, 40, 20, 20"
Android="20, 20, 20, 20"
WinPhone="20, 20, 20, 20" />
</ContentPage.Padding>
<ContentPage.Content>
<AbsoluteLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<StackLayout VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
Orientation="Vertical"
AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1">
<ScrollView Orientation = "Vertical" VerticalOptions="StartAndExpand">
<StackLayout VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
Orientation="Vertical">
<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"/>
<Button x:Name="loginButton" Text="Login" Command="{Binding OnLoginCommand}" Style="{StaticResource Buttons}"/>
</StackLayout>
</ScrollView>
</StackLayout>
<StackLayout IsVisible="{Binding IsBusy}"
Padding="12"
AbsoluteLayout.LayoutFlags="PositionProportional"
AbsoluteLayout.LayoutBounds="0.5,0.5,1,1">
<ActivityIndicator IsRunning="{Binding IsBusy}" Color ="#80000000"/>
<Label Text="Loading..." HorizontalOptions="Center" TextColor="White"/>
</StackLayout>
</AbsoluteLayout>
</ContentPage.Content>
</ContentPage>
View Model:
using System.ComponentModel;
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.ViewModels
{
public class LoginViewModel : INotifyPropertyChanged
{
public LoginViewModel()
{
OnLoginCommand = new Command(ExecuteOnLogin);
}
private bool _isBusy;
public bool IsBusy
{
get { return _isBusy; }
set
{
if (_isBusy == value)
return;
_isBusy = value;
OnPropertyChanged("IsBusy");
}
}
private string _email;
public string Email
{
get { return _email; }
set
{
if (_email == value)
return;
_email = value;
OnPropertyChanged("Email");
}
}
private bool _password;
public bool Password
{
get { return _password; }
set
{
if (_password == value)
return;
_password = value;
OnPropertyChanged("Password");
}
}
private async void ExecuteOnLogin()
{
//Validations here
if (Email == "")
{
Device.BeginInvokeOnMainThread(() => App.Current.MainPage.DisplayAlert("Validation Error", "You must enter an Email address", "OK"));
return;
}
else if (Password == "")
{
Device.BeginInvokeOnMainThread(() => App.Current.MainPage.DisplayAlert("Validation Error", "You must enter a Password", "OK"));
return;
}
//We are good to go
else
{
Device.BeginInvokeOnMainThread(() => IsBusy = true);
string APIServer = Application.Current.Properties["APIServer"].ToString();
var client = new RestClient(APIServer);
var request = new RestRequest("api/account/sign-in", Method.POST);
request.AddHeader("Content-type", "application/json");
request.AddJsonBody(new
{
email = Email,
password = Password
}
);
var response = client.Execute(request) as RestResponse;
Device.BeginInvokeOnMainThread(() => IsBusy = false);
//Valid response
if (response.StatusCode.ToString() == "OK")
{
var tokenobject = JsonConvert.DeserializeObject<TokenModel>(response.Content);
Application.Current.Properties["Token"] = tokenobject.Access_token;
string token = Application.Current.Properties["Token"].ToString();
App.Current.MainPage = new NavigationPage(new MainPage());
}
//Error response
else
{
var statuscode = response.StatusCode.ToString();
var content = response.Content;
var exception = response.ErrorException;
var error = response.ErrorMessage;
var statusdesc = response.StatusDescription;
Device.BeginInvokeOnMainThread(() => App.Current.MainPage.DisplayAlert("Login Failed", "Your login has failed. Please check your details and try again.", "OK"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public Command OnLoginCommand {get;}
}
}
Upvotes: 0
Reputation: 2899
[UPDATE]
Maybe you should separate responsibilities. Create a class called ViewModelBase
that implements INotifyPropertyChanged
like this:
public class ViewModelBase : INotifyPropertyChanged
{
bool isBusy;
/// <summary>
/// Gets or sets a value indicating whether this instance is busy.
/// </summary>
/// <value><c>true</c> if this instance is busy; otherwise, <c>false</c>.</value>
public bool IsBusy
{
get { return isBusy; }
set
{
SetProperty(ref isBusy, value);
}
}
/// <summary>
/// Sets the property.
/// </summary>
/// <returns><c>true</c>, if property was set, <c>false</c> otherwise.</returns>
/// <param name="backingStore">Backing store.</param>
/// <param name="value">Value.</param>
/// <param name="propertyName">Property name.</param>
/// <param name="onChanged">On changed.</param>
/// <typeparam name="T">The 1st type parameter.</typeparam>
protected bool SetProperty<T>(
ref T backingStore, T value,
[CallerMemberName]string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
/// <summary>
/// Occurs when property changed.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises the property changed event.
/// </summary>
/// <param name="propertyName">Property name.</param>
protected void OnPropertyChanged([CallerMemberName]string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Then creates another class called LoginViewModel
that extends from ViewModelBase
like this:
public class LoginViewModel : ViewModelBase
{
public ICommand LoginCommand { get; set; }
#region Properties
private string _email;
public string Email
{
get { return _email; }
set { SetProperty(ref _email, value); }
}
private string _password;
public string Password
{
get { return _password; }
set { SetProperty(ref _password, value); }
}
#endregion
public LoginViewModel()
{
LoginCommand = new Command(Login);
}
public string CleanResponse(string reason)
{
var str = reason;
var charsToRemove = new string[] { "[", "]", "{", "}", "\"" };
foreach (var c in charsToRemove)
{
str = str.Replace(c, string.Empty);
}
return str;
}
private async void Login()
{
//Validations here
if (Email == "")
{
await DisplayAlert("Validation Error", "You must enter an Email address", "OK");
return;
}
else if (Password == "")
{
await DisplayAlert("Validation Error", "You must enter a Password", "OK");
return;
}
//We are good to go
else
{
IsBusy = true;
string APIServer = Application.Current.Properties["APIServer"].ToString();
var client = new RestClient(APIServer);
var request = new RestRequest("api/account/sign-in", Method.POST);
request.AddHeader("Content-type", "application/json");
request.AddJsonBody(new
{
email = Email,
password = Password }
);
var response = client.Execute(request) as RestResponse;
IsBusy = false;
//Valid response
if (response.StatusCode.ToString() == "OK")
{
var tokenobject = JsonConvert.DeserializeObject<TokenModel>(response.Content);
Application.Current.Properties["Token"] = tokenobject.Access_token;
string token = Application.Current.Properties["Token"].ToString();
App.Current.MainPage = new NavigationPage(new MainPage());
}
//Error response
else
{
var statuscode = response.StatusCode.ToString();
var content = response.Content;
var exception = response.ErrorException;
var error = response.ErrorMessage;
var statusdesc = response.StatusDescription;
await DisplayAlert("Login Failed", "Your login has failed. Please check your details and try again.", "OK");
}
}
}
}
And your view like this:
<?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.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="20, 40, 20, 20"
Android="20, 20, 20, 20"
WinPhone="20, 20, 20, 20" />
</ContentPage.Padding>
<ContentPage.Content>
<AbsoluteLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<StackLayout VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
Orientation="Vertical"
AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1">
<ScrollView Orientation = "Vertical" VerticalOptions="StartAndExpand">
<StackLayout VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
Orientation="Vertical">
<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"/>
<Button x:Name="loginButton" Text="Login" Command="{Binding LoginCommand}" Style="{StaticResource Buttons}"/>
</StackLayout>
</ScrollView>
</StackLayout>
<StackLayout IsVisible="{Binding IsBusy}" Padding="12"
AbsoluteLayout.LayoutFlags="PositionProportional"
AbsoluteLayout.LayoutBounds="0.5,0.5,-1,-1">
<ActivityIndicator IsRunning="{Binding IsBusy}" Color ="#80000000"/>
<Label Text="Loading..." HorizontalOptions="Center" TextColor="White"/>
</StackLayout>
</AbsoluteLayout>
</ContentPage.Content>
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
{
LoginViewModel viewModel;
public Login ()
{
BindingContext = viewModel = new LoginPageViewModel();
InitializeComponent ();
ToolbarItems.Add(new ToolbarItem("New", "addperson.png", async () =>
{
await Navigation.PushAsync(new Register());
}));
}
}
}
I have tried this solution and it works perfectly...
Upvotes: 0