Xamarin ActivityIndicator Not Working - c#

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));
}
}
}
}

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;
}

[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...

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;}
}
}

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>

Related

Schedule Local Notification in xamarin forms

I want to make a schedule notification app and I have followed this document : https://www.c-sharpcorner.com/article/how-to-send-local-notification-with-repeat-interval-in-xamarin-forms/
The problem is notification is not showing and also no error or exception is coming.
LocalNotification.cs--- xamarin.android file
using System;
using System.IO;
using System.Xml.Serialization;
using Android.App;
using Android.Content;
using Android.Media;
using AndroidX.Core.App;
using Java.Lang;
using Test.Droid;
using Test.Models;
using Test.Views;
[assembly: Xamarin.Forms.Dependency(typeof(LocalNotificationService))]
namespace Test.Droid
{
public class LocalNotificationService : ILocalNotificationService
{
int _notificationIconId { get; set; }
readonly DateTime _jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
internal string _randomNumber;
public void LocalNotification(string title, string body, int id, DateTime notifyTime)
{
//long repeateDay = 1000 * 60 * 60 * 24;
long repeateForMinute = 60000; // In milliseconds
long totalMilliSeconds = (long)(notifyTime.ToUniversalTime() - _jan1st1970).TotalMilliseconds;
if (totalMilliSeconds < JavaSystem.CurrentTimeMillis())
{
totalMilliSeconds = totalMilliSeconds + repeateForMinute;
}
var intent = CreateIntent(id);
var localNotification = new LocalNotification();
localNotification.Title = title;
localNotification.Body = body;
localNotification.Id = id;
localNotification.NotifyTime = notifyTime;
if (_notificationIconId != 0)
{
localNotification.IconId = _notificationIconId;
}
else
{
localNotification.IconId = Resource.Drawable.icon;
}
var serializedNotification = SerializeNotification(localNotification);
intent.PutExtra(ScheduledAlarmHandler.LocalNotificationKey, serializedNotification);
Random generator = new Random();
_randomNumber = generator.Next(100000, 999999).ToString("D6");
var pendingIntent = PendingIntent.GetBroadcast(Application.Context, Convert.ToInt32(_randomNumber), intent, PendingIntentFlags.Immutable);
var alarmManager = GetAlarmManager();
alarmManager.SetRepeating(AlarmType.RtcWakeup, totalMilliSeconds, repeateForMinute, pendingIntent);
}
public void Cancel(int id)
{
var intent = CreateIntent(id);
var pendingIntent = PendingIntent.GetBroadcast(Application.Context, Convert.ToInt32(_randomNumber), intent, PendingIntentFlags.Immutable);
var alarmManager = GetAlarmManager();
alarmManager.Cancel(pendingIntent);
var notificationManager = NotificationManagerCompat.From(Application.Context);
notificationManager.CancelAll();
notificationManager.Cancel(id);
}
public static Intent GetLauncherActivity()
{
var packageName = Application.Context.PackageName;
return Application.Context.PackageManager.GetLaunchIntentForPackage(packageName);
}
private Intent CreateIntent(int id)
{
return new Intent(Application.Context, typeof(ScheduledAlarmHandler))
.SetAction("LocalNotifierIntent" + id);
}
private AlarmManager GetAlarmManager()
{
var alarmManager = Application.Context.GetSystemService(Context.AlarmService) as AlarmManager;
return alarmManager;
}
private string SerializeNotification(LocalNotification notification)
{
var xmlSerializer = new XmlSerializer(notification.GetType());
using (var stringWriter = new StringWriter())
{
xmlSerializer.Serialize(stringWriter, notification);
return stringWriter.ToString();
}
}
}
[BroadcastReceiver(Enabled = true, Label = "Local Notifications Broadcast Receiver")]
public class ScheduledAlarmHandler : BroadcastReceiver
{
public const string LocalNotificationKey = "LocalNotification";
[Obsolete]
public override void OnReceive(Context context, Intent intent)
{
var extra = intent.GetStringExtra(LocalNotificationKey);
var notification = DeserializeNotification(extra);
//Generating notification
var builder = new NotificationCompat.Builder(Application.Context)
.SetContentTitle(notification.Title)
.SetContentText(notification.Body)
.SetSmallIcon(notification.IconId)
.SetSound(RingtoneManager.GetDefaultUri(RingtoneType.Ringtone))
.SetAutoCancel(true);
var resultIntent = LocalNotificationService.GetLauncherActivity();
resultIntent.SetFlags(ActivityFlags.NewTask | ActivityFlags.ClearTask);
var stackBuilder = AndroidX.Core.App.TaskStackBuilder.Create(Android.App.Application.Context);
stackBuilder.AddNextIntent(resultIntent);
Random random = new Random();
int randomNumber = random.Next(9999 - 1000) + 1000;
var resultPendingIntent =
stackBuilder.GetPendingIntent(randomNumber, (int)PendingIntentFlags.Immutable);
builder.SetContentIntent(resultPendingIntent);
// Sending notification
var notificationManager = NotificationManagerCompat.From(Application.Context);
notificationManager.Notify(randomNumber, builder.Build());
}
private LocalNotification DeserializeNotification(string notificationString)
{
var xmlSerializer = new XmlSerializer(typeof(LocalNotification));
using (var stringReader = new StringReader(notificationString))
{
var notification = (LocalNotification)xmlSerializer.Deserialize(stringReader);
return notification;
}
}
}
}
LocalNotification.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="Test.Views.LocalNotificationPage"
BackgroundColor="#533F95">
<ContentPage.Content>
<Grid VerticalOptions="FillAndExpand" Padding="25,40,25,30" RowSpacing="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<StackLayout HorizontalOptions="FillAndExpand" Orientation="Horizontal">
<Label Text="Notifications ON/OFF" TextColor="White" FontSize="16" HorizontalOptions="StartAndExpand" VerticalOptions="Center"/>
<Switch IsToggled="{Binding NotificationONOFF}" HorizontalOptions="EndAndExpand" VerticalOptions="Center"/>
</StackLayout>
</Grid>
<Grid Grid.Row="1">
<StackLayout HorizontalOptions="FillAndExpand" Orientation="Horizontal">
<Label Text="SET TIME" HorizontalOptions="StartAndExpand" FontSize="15" TextColor="White" VerticalOptions="Center"/>
<TimePicker HorizontalOptions="EndAndExpand" Time="{Binding SelectedTime}" TextColor="White" BackgroundColor="Transparent" Format="t"/>
</StackLayout>
</Grid>
<Grid Grid.Row="2">
<StackLayout HorizontalOptions="FillAndExpand" Orientation="Horizontal">
<Label Text="SET DATE" TextColor="White" FontSize="15" VerticalOptions="Center" HorizontalOptions="StartAndExpand"/>
<DatePicker HorizontalOptions="EndAndExpand" Date="{Binding SelectedDate}" TextColor="White" BackgroundColor="Transparent" Format="MM-dd-yyyy"/>
</StackLayout>
</Grid>
<Grid Grid.Row="3">
<StackLayout HorizontalOptions="FillAndExpand" Spacing="10">
<Label Text="Enter Message" FontSize="15" HorizontalOptions="StartAndExpand" TextColor="White" VerticalOptions="Center"/>
<Editor HeightRequest="120" Text="{Binding MessageText}" TextColor="Purple" BackgroundColor="White" HorizontalOptions="FillAndExpand"/>
</StackLayout>
</Grid>
<Grid Grid.Row="4">
<Button Text="Save" Command="{Binding SaveCommand}" FontSize="15" TextColor="White" BackgroundColor="Purple" HorizontalOptions="FillAndExpand" BorderRadius="15"/>
</Grid>
</Grid>
</ContentPage.Content>
</ContentPage>
LocalNotification.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Test.Views
{
public partial class LocalNotificationPage : ContentPage
{
public LocalNotificationPage()
{
InitializeComponent();
BindingContext = new LocalNotificationPageViewModel();
}
}
}
LocalNotificationviewmode.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
using Xamarin.Forms;
namespace Test.Views
{
public class LocalNotificationPageViewModel : INotifyPropertyChanged
{
Command _saveCommand;
public Command SaveCommand
{
get
{
return _saveCommand;
}
set
{
SetProperty(ref _saveCommand, value);
}
}
bool _notificationONOFF;
public bool NotificationONOFF
{
get
{
return _notificationONOFF;
}
set
{
SetProperty(ref _notificationONOFF, value);
Switch_Toggled();
}
}
void Switch_Toggled()
{
if (NotificationONOFF == false)
{
MessageText = string.Empty;
SelectedTime = DateTime.Now.TimeOfDay;
SelectedDate = DateTime.Today;
DependencyService.Get<ILocalNotificationService>().Cancel(0);
}
}
DateTime _selectedDate = DateTime.Today;
public DateTime SelectedDate
{
get
{
return _selectedDate;
}
set
{
SetProperty(ref _selectedDate, value);
}
}
TimeSpan _selectedTime = DateTime.Now.TimeOfDay;
public TimeSpan SelectedTime
{
get
{
return _selectedTime;
}
set
{
SetProperty(ref _selectedTime, value);
}
}
string _messageText;
public string MessageText
{
get
{
return _messageText;
}
set
{
SetProperty(ref _messageText, value);
}
}
public LocalNotificationPageViewModel()
{
SaveCommand = new Command(() => SaveLocalNotification());
}
void SaveLocalNotification()
{
if (NotificationONOFF == true)
{
var date = (SelectedDate.Date.Month.ToString("00") + "-" + SelectedDate.Date.Day.ToString("00") + "-" + SelectedDate.Date.Year.ToString());
var time = Convert.ToDateTime(SelectedTime.ToString()).ToString("HH:mm");
var dateTime = date + " " + time;
var selectedDateTime = DateTime.ParseExact(dateTime, "MM-dd-yyyy HH:mm", CultureInfo.InvariantCulture);
if (!string.IsNullOrEmpty(MessageText))
{
DependencyService.Get<ILocalNotificationService>().Cancel(0);
DependencyService.Get <ILocalNotificationService>().LocalNotification("Local Notification", MessageText, 0, selectedDateTime);
App.Current.MainPage.DisplayAlert("LocalNotificationDemo", "Notification details saved successfully ", "Ok");
}
else
{
App.Current.MainPage.DisplayAlert("LocalNotificationDemo", "Please enter meassage", "OK");
}
}
else
{
App.Current.MainPage.DisplayAlert("LocalNotificationDemo", "Please switch on notification", "OK");
}
}
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;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Ilocalnotificationservice.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Test.Views
{
public interface ILocalNotificationService
{
void LocalNotification(string title, string body, int id, DateTime notifyTime);
void Cancel(int id);
}
}
You can continue to schedule notification, where I can set the date, time and notification text and keep the previous style (LocalNotificationPage.xaml and LocalNotificationviewmode.cs). Only need to replace the DependencyService.
I built a new project. The only difference from yours is that I used the official Local notifications DependencyService. And then remove the previous DependencyService in LocalNotificationviewmode.cs:
...
void Switch_Toggled()
{
if (NotificationONOFF == false)
{
MessageText = string.Empty;
SelectedTime = DateTime.Now.TimeOfDay;
SelectedDate = DateTime.Today;
//DependencyService.Get<ILocalNotificationService>().Cancel(0);
}
}
...
void SaveLocalNotification()
{
if (NotificationONOFF == true)
{
...
if (!string.IsNullOrEmpty(MessageText))
{
//DependencyService.Get<ILocalNotificationService>().Cancel(0);
//DependencyService.Get <ILocalNotificationService>().LocalNotification("Local Notification", MessageText, 0, selectedDateTime);
//use new DependencyService from official doc
DependencyService.Get<INotificationManager>().SendNotification("Local Notification", MessageText, selectedDateTime);
App.Current.MainPage.DisplayAlert("LocalNotificationDemo", "Notification details saved successfully ", "Ok");
}
...
}
...
}

I am getting this error: System.MissingMethodException Message=Default constructor not found for type AbuseAlert.Views.Hotels

When I tap on a menu item, it is supposed to display a page containing several menu and sub-menu items, but it is giving the above error.
This is how I am trying to access the content page from my appshell.xaml:
<FlyoutItem Title="Services" Icon="icon_feed.png">
<ShellContent Route="Hotels" ContentTemplate="{DataTemplate local:Hotels}" />
This is the Hotels.xaml.cs file I am trying to display in the Views Folder:
namespace AbuseAlert.Views
{
public partial class Hotels : ContentPage
{
private HotelsGroupViewModel ViewModel
{
get { return (HotelsGroupViewModel)BindingContext; }
set { BindingContext = value; }
}
private List<Hotels> ListHotel = new List<Hotels>();
protected override void OnAppearing()
{
try
{
base.OnAppearing();
if (ViewModel.Items.Count == 0)
{
ViewModel.LoadHotelsCommand.Execute(null);
}
}
catch (Exception Ex)
{
Debug.WriteLine(Ex.Message);
}
}
public Hotels(HotelsGroupViewModel viewModel)
{
InitializeComponent();
this.ViewModel = viewModel;
}
}
}
This is the Hotels.xaml file:
<?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:Name="currentPage"
x:Class="AbuseAlert.Views.Hotels">
<ContentPage.Content>
<Grid >
<StackLayout x:Name="hotelStack" Padding="1,0,1,0" >
<ListView
x:Name="HotelsList"
BackgroundColor="White"
IsGroupingEnabled="True"
IsPullToRefreshEnabled="true"
IsRefreshing="{Binding IsBusy, Mode=OneWay}"
ItemsSource="{Binding Items}"
RefreshCommand="{Binding LoadHotelsCommand}"
>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" VerticalOptions="Center">
<Label
VerticalOptions="Center"
FontAttributes="Bold"
FontSize="Medium"
Text="{Binding RoomName}"
TextColor="Black"
VerticalTextAlignment="Center" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<Grid >
<Label
FontAttributes="Bold"
FontSize="Small"
Text="{Binding Name}"
TextColor="Gray"
VerticalTextAlignment="Center" />
<Image x:Name="ImgA" Source="{Binding StateIcon}" Margin="0,0,5,0" HeightRequest="20" WidthRequest="20" HorizontalOptions="End"/>
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={x:Reference currentPage}, Path=BindingContext.RefreshItemsCommand}" NumberOfTapsRequired="1" CommandParameter="{Binding .}"/>
</Grid.GestureRecognizers>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
</ListView>
</StackLayout>
</Grid>
</ContentPage.Content>
So, what do I do next to clear this error and get the content page to display?
This is HotelsGroupViewModel.cs.
using AbuseAlert.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using Xamarin.Forms;
namespace AbuseAlert.ViewModels
{
public class HotelsGroupViewModel : BaseViewModel2
{
private HotelViewModel _oldHotel;
private ObservableCollection<HotelViewModel> items;
public ObservableCollection<HotelViewModel> Items
{
get => items;
set => SetProperty(ref items, value);
}
public Command LoadHotelsCommand { get; set; }
public Command<HotelViewModel> RefreshItemsCommand { get; set; }
public HotelsGroupViewModel()
{
items = new ObservableCollection<HotelViewModel>();
Items = new ObservableCollection<HotelViewModel>();
LoadHotelsCommand = new Command(async () => await ExecuteLoadItemsCommandAsync());
RefreshItemsCommand = new Command<HotelViewModel>((item) => ExecuteRefreshItemsCommand(item));
}
public bool isExpanded = false;
private void ExecuteRefreshItemsCommand(HotelViewModel item)
{
if (_oldHotel == item)
{
// click twice on the same item will hide it
item.Expanded = !item.Expanded;
}
else
{
if (_oldHotel != null)
{
// hide previous selected item
_oldHotel.Expanded = false;
}
// show selected item
item.Expanded = true;
}
_oldHotel = item;
}
async System.Threading.Tasks.Task ExecuteLoadItemsCommandAsync()
{
try
{
if (IsBusy)
return;
IsBusy = true;
Items.Clear();
List<Room> Hotel1rooms = new List<Room>()
{
new Room("Record Audio", 1), new Room("Take a Photo", 1), new Room("Record Video", 1), new Room("Watch a LiveStream", 1), new Room("Broadcast a LiveStream", 1)
};
List<Room> Hotel2rooms = new List<Room>()
{
new Room("View RED Zone", 1), new Room("Manage RED Zone", 1)
};
List<Room> Hotel3rooms = new List<Room>()
{
};
List<Room> Hotel4rooms = new List<Room>()
{
};
List<Room> Hotel5rooms = new List<Room>()
{
};
List<Room> Hotel6rooms = new List<Room>()
{
new Room("Support Tips", 1), new Room("Counselling Tips", 1), new Room("Chat", 1)
};
List<Room> Hotel7rooms = new List<Room>()
{
};
List<Hotel> items = new List<Hotel>()
{
new Hotel("Report Abuse", Hotel1rooms), new Hotel("RED Zone", Hotel2rooms), new Hotel("Location Inquiry", Hotel3rooms), new Hotel("Notify", Hotel4rooms), new Hotel("Voice2Text", Hotel5rooms), new Hotel("Support & Counselling", Hotel6rooms), new Hotel("Options", Hotel7rooms)
};
if (items != null && items.Count > 0)
{
foreach (var hotel in items)
Items.Add(new HotelViewModel(hotel));
}
else { IsEmpty = true; }
}
catch (Exception ex)
{
IsBusy = false;
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
}
}
The error message tells you exactly what the problem is - default constructor not found
your page has a constructor with a parameter
public Hotels(HotelsGroupViewModel viewModel)
{
InitializeComponent();
this.ViewModel = viewModel;
}
but XAML pages require a default constructor, one that has no parameters
public Hotels()
{
InitializeComponent();
}

Why won't changes reflect in my Xamarin.Forms project or SQLite database

UPDATE - Issue #1 is Solved, Issue#2 is still unsolved
You can view a very crude demonstration video of my issue at https://www.youtube.com/watch?v=5_6KJ0QJouM
I am building have a Xamarin.Forms app with an SQLite database using the MVVM design pattern and C#
When try to Save a record to the database from a View the update/save does not appear to be saving to the SQLite database or reflect in other Views.
I know the database Save method does work as I have created some dummy data when the application first loads (in App.xaml.cs) using the DeveloperData.cs file.
I have two issues.
(SOLVED) Issue 1 - Data not Saving to Database
when I call the Save command from the MerchandiserEditPage.xaml, which uses the MerchandiserEditPageViewModel.cs ViewModel, the record does not appear to save.
Issue 2 - Changes Reflecting in other Views
Once the updated data is saved to the database, how can I reflect that change in other views? After I Save a record from the MerchandiserEditPage that View is "Popped" off the stack and the user is returned to the MerchandiserProfileView. I want the updated data to be reflected in all other views on the stack. But this doesn't appear to be happening? (I tested this using hardcoded data and the same issue occurred, so problem is not directly related to issue 1)
There are many files in my project, that can be viewed/downloaded from my GitHub repository but I will concentrate on the following in this question.
MerchandiserEditPage.xaml (View)
MerchandiserProfilePage.xaml (View)
MerchandiserDatabase.cs (Database Functions)x
MerchandiserEditPageViewModel.cs x
View my GitHub repository for the full project.
MerchandiserDatabase.cs (Database Functions)
using SQLite;
namespace MobileApp.Database
{
public class MerchandiserDatabase
{
private static SQLiteConnection database = DependencyService.Get<IDatabaseConnection>().DbConnection();
private readonly static object collisionLock = new object();
public MerchandiserDatabase()
{
database.CreateTable<Models.Merchandiser>();
}
public static void SaveMerchandiser(Models.Merchandiser merchandiser)
{
lock (collisionLock)
{
if (merchandiser.Id != 0)
{
database.Update(merchandiser);
}
else
{
database.Insert(merchandiser);
}
}
}
}
}
MerchandiserEditPageViewModel.cs (ViewModel) UPDATED
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
namespace MobileApp.ViewModels
{
public class MerchandiserEditPageViewModel : BaseViewModel
{
public string PageTitle { get; } = "Edit Merchandiser Profile";
public Command SaveCommand { get; set; }
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged();
}
}
private string phone;
public string Phone
{
get { return phone; }
set
{
phone = value;
OnPropertyChanged();
}
}
private string email;
public string Email
{
get { return email; }
set
{
email = value;
OnPropertyChanged();
}
}
public MerchandiserEditPageViewModel(Models.Merchandiser selectedMerchandiser)
{
Name = selectedMerchandiser.Name;
Phone = selectedMerchandiser.Phone;
Email = selectedMerchandiser.Email;
SaveCommand = new Command( async ()=> {
selectedMerchandiser.Name = this.Name;
selectedMerchandiser.Phone = this.Phone;
selectedMerchandiser.Email = this.Email;
Database.MerchandiserDatabase.SaveMerchandiser(selectedMerchandiser);
await Application.Current.MainPage.Navigation.PopModalAsync();
});
}
}
}
MerchandiserEditPage.xaml (View)
<?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="MobileApp.Views.MerchandiserEditPage">
<ContentPage.Content>
<StackLayout>
<!--Page Heading-->
<StackLayout Spacing="0">
<Label Text="{Binding PageTitle}"
Style="{StaticResource PageTitle}"/>
<BoxView HeightRequest="1" Color="LightGray" />
</StackLayout>
<!-- Merchandiser Profile -->
<StackLayout Margin="10">
<Label Text="Name"/>
<Entry Text="{Binding Name}"/>
<Label Text="Phone"/>
<Entry Text="{Binding Phone}"/>
<Label Text="Email"/>
<Entry Text="{Binding Email}"/>
<StackLayout Orientation="Horizontal"
HorizontalOptions="Center">
<Button Text="Cancel"
Clicked="CancelButton_Clicked"/>
<Button Text="Save"
Command="{Binding SaveCommand}"/>
</StackLayout>
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
MerchandiserEditPage.xaml.cs (View - Code Behind)
public partial class MerchandiserEditPage : ContentPage
{
Models.Merchandiser SelectedMerchandiser { get; set; }
public MerchandiserEditPage (Models.Merchandiser selectedMerchandiser)
{
InitializeComponent ();
SelectedMerchandiser = selectedMerchandiser;
this.BindingContext = new ViewModels.MerchandiserEditPageViewModel(selectedMerchandiser);
}
async private void CancelButton_Clicked(object sender, EventArgs e)
{
await Navigation.PopModalAsync();
}
}
MerchandiserProfilePage.xaml (View - 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="MobileApp.Views.MerchandiserProfilePage"
NavigationPage.HasNavigationBar="False">
<ContentPage.Content>
<StackLayout>
<!--Page Heading-->
<StackLayout Spacing="0">
<Label Text="{Binding PageTitle}"
Style="{StaticResource PageTitle}"/>
<BoxView HeightRequest="1" Color="LightGray" />
</StackLayout>
<!-- Merchandiser Profile -->
<StackLayout Margin="10">
<Label Text="Name"/>
<Entry Text="{Binding Name}"
IsEnabled="False"/>
<Label Text="Phone"/>
<Entry Text="{Binding Phone}"
IsEnabled="False"/>
<Label Text="Email"/>
<Entry Text="{Binding Email}"
IsEnabled="False"/>
<StackLayout Orientation="Horizontal"
HorizontalOptions="Center">
<Button Text="Back"
Clicked="BackButton_Clicked"/>
<Button Text="Edit"
Clicked="EditButton_Clicked"/>
</StackLayout>
<Button Text="Delete"
Command="{Binding DeleteCommand}"/>
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
MerchandiserProfilePage.xaml.cs - (View - Code Behind)
public partial class MerchandiserProfilePage : ContentPage
{
private Models.Merchandiser SelectedMerchandister { get; set; }
public MerchandiserProfilePage (Models.Merchandiser selectedMerchandiser)
{
InitializeComponent ();
SelectedMerchandister = selectedMerchandiser;
this.BindingContext = new ViewModels.MerchandiserProfilePageViewModel(selectedMerchandiser);
}
async private void BackButton_Clicked(object sender, EventArgs e)
{
await Navigation.PopModalAsync();
}
async private void EditButton_Clicked(object sender, EventArgs e)
{
await Navigation.PushModalAsync(new Views.MerchandiserEditPage(SelectedMerchandister));
}
}
MerchandiserProfilePageViewModel.cs (ViewModel)
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
namespace MobileApp.ViewModels
{
public class MerchandiserProfilePageViewModel : BaseViewModel
{
public string PageTitle { get; } = "Merchandiser Profile";
public Command DeleteCommand { get; }
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged();
}
}
private string phone;
public string Phone
{
get { return phone; }
set
{
phone = value;
OnPropertyChanged();
}
}
private string email;
public string Email
{
get { return email; }
set
{
email = value;
OnPropertyChanged();
}
}
public MerchandiserProfilePageViewModel(Models.Merchandiser selectedMerchandiser)
{
Name = selectedMerchandiser.Name;
Phone = selectedMerchandiser.Phone;
Email = selectedMerchandiser.Email;
DeleteCommand = new Command( async()=> {
bool deleteConfirmed = await Application.Current.MainPage.DisplayAlert("Confirm Delete",$"Are you sure you want to delete {selectedMerchandiser.Name} as a Merchandiser?","Yes","No");
if (deleteConfirmed)
{
// TODO: Delete Merchandiser
await Application.Current.MainPage.Navigation.PopModalAsync();
}
});
}
}
}
you have a hardcoded set of data in your VM instead of loading it from the db
public MerchandisersPageViewModel()
{
//Merchandisers = new ObservableCollection<Models.Merchandiser>(Database.MerchandiserDatabase.GetMerchandisers());
Merchandisers = new ObservableCollection<Models.Merchandiser>()
{
new Models.Merchandiser { Id=1, Name="Barney Rubble", Phone="021 321 654", Email="barney#rubble.com"},
new Models.Merchandiser { Id=2, Name="Frank Grimes", Phone="022 456 789", Email="grimey#homersfriend.com"},
new Models.Merchandiser { Id=3, Name="Perry Platypus", Phone="023 789 456", Email="perry#agentp.com"},
};
}
Update:
in MerchandiserProfilePageViewModel, get rid of the properties for Name, Phone and EMail
then in MerchandiserProfilePage.xaml change the bindings
<Entry Text="{Binding SelectedMerchandiser.Name}" IsEnabled="False"/>

How to access/inherit a private field property from a different ContentPage in Xamarin Forms?

I have the following ContentPage ("LoginPage") which among others prompts the user for his username and password:
<?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="WeatherApp.LoginPage"
BackgroundImageSource="https://i.pinimg.com/236x/dc/d8/e5/dcd8e5e716b307d6ecfcbc73168fdff5--abstract-iphone-wallpaper-wallpaper-s.jpg"
NavigationPage.HasNavigationBar="False">
<StackLayout Padding="20,20,20,20"
Spacing="20">
<StackLayout>
<Label Text="Username" FontAttributes="Bold" FontFamily="Roboto" TextColor="White" FontSize="20"/>
<Entry x:Name="Username" Placeholder="Please enter username..." FontFamily="Roboto"/>
</StackLayout>
<StackLayout>
<Label Text="Password" FontAttributes="Bold" FontFamily="Roboto" TextColor="White" FontSize="20"/>
<Entry x:Name="Password" Placeholder="Please enter password..." IsPassword="True" FontFamily="Roboto"/>
</StackLayout>
<Button Text="Log in" HorizontalOptions="Center" VerticalOptions="Center" FontFamily="OpenSans" TextColor="DeepSkyBlue" BackgroundColor="#bef9cf" CornerRadius="25"
Clicked="Button_Clicked"/>
</StackLayout>
</ContentPage>
The code behind looks as follows:
using System;
using System.Collections.Generic;
using WeatherApp.Models;
using Xamarin.Forms;
namespace WeatherApp
{
public partial class LoginPage : ContentPage
{
public LoginPage()
{
InitializeComponent();
}
async void Button_Clicked(System.Object sender, System.EventArgs e)
{
User user = new User();
var username = Username.Text;
var password = Password.Text;
var _user = user.GetUser(username);
if (username == _user.Username && password == _user.Password)
{
await DisplayAlert("Log in successful", null, "OK");
await Navigation.PushAsync(new ProfilePage());
}
else
{
await DisplayAlert("Log in failed", $"No such combination with username: {username}", "Try again");
}
}
}
}
In another content page, I want to access this Entry property Username so I can use it in the "Greeting" property along with a time-based greeting (e.g. "Good evening, {username}!"). I tried the code below but again, I cannot access the properties from LoginPage:
using System;
using System.ComponentModel;
namespace WeatherApp.Models
{
public class ProfilePageViewModel : INotifyPropertyChanged
{
private string greeting;
public string Greeting
{
get
{
return greeting;
}
set
{
greeting = value;
OnPropertyChanged(nameof(Greeting));
}
}
public ProfilePageViewModel()
{
User user = new User();
var username = LoginPage.Username;
var _user = user.GetUser(username);
Greeting = TimeBasedGreeting() + ", {username}!";
}
I hence need to access it, but it is a protected property.
Is there a way I can inherit the LoginPage and hence inherit its protected properties?
the simplest thing to do is just pass the user object to the new page. Then ProfilePage will have a reference to the user object and can get the username from there
if (username == _user.Username && password == _user.Password)
{
await DisplayAlert("Log in successful", null, "OK");
await Navigation.PushAsync(new ProfilePage(_user));
}
alternately, you could create a User property on the App class which will allow any page in your app to access it

Multiselect checkbox in xamarin forms cross platform

Below is the code I'm using for checkbox in xamarin forms, but here I'm able to select only one item, I wanted to select multiple items from the checkbox. To the checkbox the data is binded from the database. Please help me
Checkforms.xaml.cs
public partial class Checkforms : ContentPage
{
private ObservableCollection<HelperModel> statusRecords;
string[] statusList;
public Checkforms()
{
InitializeComponent();
GetUserRoles();
}
public async void GetUserRoles()
{
HttpClient client = new HttpClient();
var response = await client.GetStringAsync("http://**********/api/Masters/getRoles");
var details = JsonConvert.DeserializeObject<List<HelperModel>>(response);
ListView1.ItemsSource = details;
}
private async void ListView1_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if (e.SelectedItem == null) return;
var statusData = e.SelectedItem as HelperModel;
((ListView)sender).SelectedItem = null;
HttpClient client = new HttpClient();
var response = await client.GetStringAsync("http://********/api/Masters/getRoles");
var details = JsonConvert.DeserializeObject<List<HelperModel>>(response);
ListView1.ItemsSource = details;
var item = details.Where(x => x.name == statusData.name).FirstOrDefault();
if (item != null)
item.IsSelected = !item.IsSelected;
}
}
Checkforms.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"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="Checkbox_listview.Checkforms"
xmlns:lv="clr-namespace:Xamarin.Forms.MultiSelectListView;assembly=Xamarin.Forms.MultiSelectListView" Padding="0,20,0,0">
<ContentPage.Content>
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<!-- Place new controls here -->
<ListView x:Name="ListView1" ItemSelected="ListView1_ItemSelected" lv:MultiSelect.Enable="true">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout HorizontalOptions="FillAndExpand"
Orientation="Horizontal" Padding="10 ">
<Label Text="{Binding name}" HorizontalOptions="StartAndExpand"/>
<Image Source="select.png" IsVisible="{Binding IsSelected}"
VerticalOptions="Center" HeightRequest="40"
WidthRequest="40"/>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
HelperModel.cs
public class HelperModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isSelected = false;
public string name { get; set; }
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
OnPropertyChanged("IsSelected");
}
}
//OnProperty changed method
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I'm trying to select multiple items from the checkbox after binding it from the database, from here only one item is selected at a time. please help how to select multiple items
Thanks in advance
You can try use CollectionView to replace the listview like following code. CollectionView have SelectionMode, you can set it to Multiple
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<!-- Place new controls here -->
<CollectionView x:Name="ListView1" ItemsSource="{Binding StatusRecords}" SelectionMode="Multiple"
SelectionChanged="ListView1_SelectionChanged">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout HorizontalOptions="FillAndExpand" Orientation="Horizontal" Padding="10 ">
<Label Text="{Binding name}" HorizontalOptions="StartAndExpand"/>
<Image Source="select.png" IsVisible="{Binding IsSelected}" VerticalOptions="Center" HeightRequest="40" WidthRequest="40"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
Here is running GIF.
=========Update===============
Do you want to Multiple pre-selection result?
If so, you should add the Property in your ViewModel. Note: No matter what is your model, please set the type of ObservableCollection to object
ObservableCollection<object> selectedHelperModels;
public ObservableCollection<object> SelectedHelperModels
{
get
{
return selectedHelperModels;
}
set
{
if (selectedHelperModels != value)
{
selectedHelperModels = value;
OnPropertyChanged("SelectedHelperModels");
}
}
}
Then If the IsSelected was selected to true. I will add it to the SelectedHelperModels.
public MyHelperViewModel()
{
StatusRecords = new ObservableCollection<HelperModel>();
StatusRecords.Add(new HelperModel() { IsSelected=false, name="test1" });
StatusRecords.Add(new HelperModel() { IsSelected = true, name = "test2" });
StatusRecords.Add(new HelperModel() { IsSelected = true, name = "test3" });
StatusRecords.Add(new HelperModel() { IsSelected = true, name = "test4" });
StatusRecords.Add(new HelperModel() { IsSelected = false, name = "test5" });
StatusRecords.Add(new HelperModel() { IsSelected = false, name = "test6" });
SelectedHelperModels = new ObservableCollection<object>();
foreach (var item in StatusRecords)
{
if (item.IsSelected)
{
SelectedHelperModels.Add(item);
}
}
}
In the foreground xaml. Add the SelectedItems="{Binding SelectedHelperModels}" in the CollectionView.
<CollectionView x:Name="ListView1" ItemsSource="{Binding StatusRecords}" SelectedItems="{Binding SelectedHelperModels}" SelectionMode="Multiple"
SelectionChanged="ListView1_SelectionChanged">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout HorizontalOptions="FillAndExpand" Orientation="Horizontal" Padding="10 ">
<Label Text="{Binding name}" HorizontalOptions="StartAndExpand"/>
<Image Source="{Binding IsSelected, Converter={StaticResource imageToBool}}" IsVisible="{Binding IsSelected} " VerticalOptions="Center" HeightRequest="40" WidthRequest="40"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
As you comment, you lack of the ListView1_SelectionChanged event. just add it in the layout background code.
public partial class MainPage : ContentPage
{
MyHelperViewModel myHelperViewModel;
public MainPage()
{
InitializeComponent();
myHelperViewModel= new MyHelperViewModel();
this.BindingContext = myHelperViewModel;
}
private void ListView1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
}
}
=========Update2============
Do you want to achieve the result like following GIF?
If so, I found the SelectionChanged event cannot achieve it easliy, and it cannot meet the MVVM requirement, So I add a TapGestureRecognizer for StackLayout in the CollectionView.
Here is code.
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<CollectionView x:Name="ListView1"
ItemsSource="{Binding StatusRecords}"
SelectedItems="{Binding SelectedHelperModels}"
SelectionMode="Multiple"
>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout HorizontalOptions="FillAndExpand" Orientation="Horizontal" Padding="10">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding BindingContext.ChangeCommand, Source={x:Reference Name=ListView1}}"
CommandParameter="{Binding .}"
/>
</StackLayout.GestureRecognizers>
<Label Text="{Binding name}" HorizontalOptions="StartAndExpand"/>
<Image Source="{Binding IsSelected, Converter={StaticResource imageToBool},Mode=TwoWay}" IsVisible="{Binding IsSelected, Mode=TwoWay}" VerticalOptions="Center" HeightRequest="40" WidthRequest="40"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
Here is ViewModel.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows.Input;
using Xamarin.Forms;
namespace SelectMutiPlyDemo
{
public class MyHelperViewModel: INotifyPropertyChanged
{
public ObservableCollection<HelperModel> StatusRecords { get; set; }
public ICommand ChangeCommand { protected set; get; }
ObservableCollection<object> selectedHelperModels;
public ObservableCollection<object> SelectedHelperModels
{
get
{
return selectedHelperModels;
}
set
{
if (selectedHelperModels != value)
{
selectedHelperModels = value;
OnPropertyChanged("SelectedHelperModels");
}
}
}
public MyHelperViewModel()
{
StatusRecords = new ObservableCollection<HelperModel>();
StatusRecords.Add(new HelperModel() { IsSelected=false, name="test1" });
StatusRecords.Add(new HelperModel() { IsSelected = true, name = "test2" });
StatusRecords.Add(new HelperModel() { IsSelected = true, name = "test3" });
StatusRecords.Add(new HelperModel() { IsSelected = true, name = "test4" });
StatusRecords.Add(new HelperModel() { IsSelected = false, name = "test5" });
StatusRecords.Add(new HelperModel() { IsSelected = false, name = "test6" });
SelectedHelperModels = new ObservableCollection<object>();
foreach (var item in StatusRecords)
{
if (item.IsSelected)
{
SelectedHelperModels.Add(item);
}
}
ChangeCommand=new Command<HelperModel>((key) =>
{
if (SelectedHelperModels.Contains<object>(key))
{
SelectedHelperModels.Remove(key);
}
else
{
SelectedHelperModels.Add(key);
}
key.IsSelected = !key.IsSelected;
});
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}

Categories

Resources