Verifying a password that was hashed with php, in C# Maui [closed] - c#

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 3 days ago.
Improve this question
I am struggling with trying to verify a password hashed by php, in MAUI. I am trying to use the bcrypt-next library.
The error insists that the salt of the password is incorrectly formated everytime i try to verify it.
Being more specific, the error is 'Invalid salt version'
The password i am verifying was hashed by the php password_hash with the bcrypt option, and is "2y$10$1xQDIdTFdscXIuKPl1oxY.9QXZIoeeubQpB.0OdhT0YRYgp9fm666" using bcrypt.net-next version 4.0.3
I tried updating the bcrypt library, and i dont seem to be able to update it more than that. My code is as follows:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using thebridgeproject.Models;
namespace thebridgeproject.ViewModels
{
public class LoginViewModel : INotifyPropertyChanged
{
private Command _loginCommand;
public Command LoginCommand => _loginCommand ?? (_loginCommand = new Command(async () => await Login()));
private string _username;
public string Username
{
get { return _username; }
set
{
_username = value;
OnPropertyChanged(nameof(Username));
}
}
private string _password;
public string Password
{
get { return _password; }
set
{
_password = value;
OnPropertyChanged(nameof(Password));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private async Task Login()
{
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("Authorization", "RaV9NUn");
var response = await httpClient.GetAsync("http:///bp/utentes");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
var users = JsonConvert.DeserializeObject<users.Root>(content);
if (users.success)
{
var user = users.result.FirstOrDefault(x => x.Nome == Username);
if (user != null && VerifyPassword(user.Password, Password))
{
// Login successful
// ...
await Application.Current.MainPage.DisplayAlert("Success", "You have successfully logged in", "OK");
}
else
{
// Login failed
// ...
await Application.Current.MainPage.DisplayAlert("Error", "Invalid username or password", "OK");
}
}
else
{
// API request failed
// ...
await Application.Current.MainPage.DisplayAlert("Error", "An error occurred while processing your request", "OK");
}
}
else
{
// API request failed
// ...
await Application.Current.MainPage.DisplayAlert("Error", "An error occurred while processing your request2", "OK");
}
}
}
private bool VerifyPassword(string enteredPassword, string hashedPassword)
{
return BCrypt.Net.BCrypt.Verify(enteredPassword, hashedPassword);
}
}
}
with this design:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="thebridgeproject.Views.login"
xmlns:ViewModels="clr-namespace:thebridgeproject.ViewModels"
Shell.NavBarIsVisible="False"
Title="LoginPage" >
<ContentPage.BindingContext>
<ViewModels:LoginViewModel />
</ContentPage.BindingContext>
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center">
<Image Source="loginicon.png" HeightRequest="150" WidthRequest="150" />
<VerticalStackLayout Spacing="5">
<Label Text="Welcome!" FontSize="28" TextColor="#3B7A5E" HorizontalTextAlignment="Center" />
<Label Text="Login to your account" FontSize="18" TextColor="Gray" HorizontalTextAlignment="Center" />
</VerticalStackLayout>
<StackLayout Orientation="Horizontal">
<Frame ZIndex="1" HasShadow="True" BorderColor="White" HeightRequest="56" WidthRequest="56" CornerRadius="28">
<Image Source="user.png" HeightRequest="20" WidthRequest="20" />
</Frame>
<Frame HeightRequest="45" Margin="-20,0,0,0" Padding="0" HasShadow="True" BorderColor="White" HorizontalOptions="FillAndExpand">
<Entry Text="{Binding Username}" Margin="20,0,0,0" VerticalOptions="Center" Placeholder="Username"/>
</Frame>
</StackLayout>
<StackLayout Orientation="Horizontal">
<Frame ZIndex="1" HasShadow="True" BorderColor="White" HeightRequest="56" WidthRequest="56" CornerRadius="28">
<Image Source="lock.png" HeightRequest="20" WidthRequest="20" />
</Frame>
<Frame HeightRequest="45" Margin="-20,0,0,0" Padding="0" HasShadow="True" BorderColor="White" HorizontalOptions="FillAndExpand">
<Entry Text="{Binding Password}" Margin="20,0,0,0" VerticalOptions="Center" Placeholder="Password" IsPassword="True" />
</Frame>
</StackLayout>
<Button Text="Sign in" WidthRequest="100" CornerRadius="20" HorizontalOptions="Center" BackgroundColor="#3B7A5E" Command="{Binding LoginCommand}" />
<StackLayout Orientation="Horizontal" Spacing="5" HorizontalOptions="Center">
<Label Text="Dont have an account?" TextColor="Gray" />
<Label Text="Sign up here" TextColor="#50b3f2" />
</StackLayout>
</VerticalStackLayout>
</ContentPage>
and this model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace thebridgeproject.Models
{
class users
{
public class Result
{
public int NumUtente { get; set; }
public string Nome { get; set; }
public string Password { get; set; }
public string Morada { get; set; }
public string Cidade { get; set; }
public string DataNascimento { get; set; }
public string NumTlf { get; set; }
}
public class Root
{
public bool success { get; set; }
public string message { get; set; }
public List<Result> result { get; set; }
}
}
}
the api sends a object root with a boolean of success and a message. if it was a success we get the list of users, which then we use to verify if the user and password match the content the api gave the application. i already checked and it seems like the password that is being verified is indeed the hashed one that i posted in this question. i really cant figure out how to fix the issue and would love any possible help.
the password is: 2y$10$1xQDIdTFdscXIuKPl1oxY.9QXZIoeeubQpB.0OdhT0YRYgp9fm666
the bcrypt version is: bcrypt.net-next version 4.0.3
and the error message is: Invalid salt version

Related

.Net MAUI ListView IsRefreshing Binding not working

I am trying to create a ListView that supports the pull to refresh feature of ListView.
My XAML is:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:Local="clr-namespace:FireLearn.ViewModels"
x:Class="FireLearn.MainPage"
Title="Categories">
<ContentPage.BindingContext>
<Local:CategoryViewModel/>
</ContentPage.BindingContext>
<ListView
ItemsSource="{Binding Categories}"
ItemSelected="ListView_ItemSelected"
HasUnevenRows="True"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding ListRefreshing}"
RefreshCommand="{Binding RefreshCommand}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<HorizontalStackLayout
Padding="8">
<Image Source="cafs_bubbles.png"
HeightRequest="64"/>
<VerticalStackLayout
Padding="8">
<Label Text="{Binding FormattedName}"
SemanticProperties.HeadingLevel="Level1"
FontSize="Title"
HorizontalOptions="Start"/>
<Label Text="{Binding ItemCount}"
FontSize="Subtitle"/>
<Label Text="{Binding Description}"
HorizontalOptions="Center"
LineBreakMode="WordWrap"
FontSize="Caption"
VerticalOptions="CenterAndExpand"/>
</VerticalStackLayout>
</HorizontalStackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
and the code behind is:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using FireLearn.Models;
namespace FireLearn.ViewModels
{
class CategoryViewModel : ObservableObject
{
public ObservableCollection<CategoryModel> categories = new ObservableCollection<CategoryModel>();
public ObservableCollection<CategoryModel> Categories
{
get => categories;
set => SetProperty(ref categories, value);
}
public bool ListRefreshing = false;
public ICommand RefreshCommand;
public CategoryViewModel()
{
loadFromSource();
RefreshCommand = new Command(async () =>
{
if (!ListRefreshing)
{
ListRefreshing = true;
try
{
await loadFromSource();
}
finally
{
ListRefreshing = false;
}
}
});
}
public async Task loadFromSource()
{
HttpClient httpClient = new()
{
Timeout = new TimeSpan(0,0,10)
};
Uri uri = new Uri("https://somewodpresssite/wp-json/wp/v2/categories");
HttpResponseMessage msg = await httpClient.GetAsync(uri);
if (msg.IsSuccessStatusCode)
{
var result = CategoryModel.FromJson(await msg.Content.ReadAsStringAsync());
Categories = new ObservableCollection<CategoryModel>(result);
}
Debug.WriteLine("List Refreshed");
}
}
}
The binding of the list works great and items show as expected (based on loadFromSource() being called in the constructor but the ICommand doesnt get called when I pull to refresh and I get the following error around the IsRefreshing binding:
[0:] Microsoft.Maui.Controls.Xaml.Diagnostics.BindingDiagnostics:
Warning: 'ListRefreshing' property not found on
'FireLearn.ViewModels.CategoryViewModel', target property:
'Microsoft.Maui.Controls.ListView.IsRefreshing'
I have tried on Android and iOs with the same result and am at a loss as to what I've missed here.
You can only bind to public properties
public bool ListRefreshing { get; set; }

Xamarin, Label Text in xaml doesn't change on property changed WITH MVVM Helpers

In my Xaml the values only update when, i go into the xaml and do this for example:
{Binding use.currentlevel}->{Binding use.currentleve}->{Binding use.currentlevel}
but not when the use variable is updated upon launch and aqustion of data from the database, i cant figure out why.
P.S. i set the bindingcontext in xaml file.
AboutPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="INWORK.Views.AboutPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:control="clr-namespace:ProgressRingControl.Forms.Plugin;assembly=ProgressRing.Forms.Plugin"
xmlns:vm="clr-namespace:INWORK.ViewModels"
Title="{Binding Title}"
BackgroundImage="MainBackground.png">
<ContentPage.BindingContext>
<vm:AboutViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<Color x:Key="Accent">#96d1ff</Color>
<Color x:Key="Muscular">#E76F51</Color>
<Color x:Key="Cardio">#429EA6</Color>
</ResourceDictionary>
</ContentPage.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="3*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1.15*" />
</Grid.RowDefinitions>
<Ellipse
Grid.Row="1"
Fill="Gray"
HeightRequest="160"
HorizontalOptions="Center"
Stroke="#FFFF9900"
VerticalOptions="Center"
WidthRequest="160" />
<control:ProgressRing
Grid.Row="1"
HeightRequest="100"
Progress="{Binding use.muscularprogress}"
RingProgressColor="{StaticResource Muscular}"
RingThickness="20"
Scale="1"
WidthRequest="100"
class="pro" />
<control:ProgressRing
Grid.Row="1"
HeightRequest="100"
Progress="{Binding use.cardioprogress}"
RingProgressColor="{StaticResource Cardio}"
RingThickness="20"
Scale="0.85"
class="pro" />
<StackLayout Grid.Row="1" VerticalOptions="Center">
<StackLayout Orientation="Horizontal" HorizontalOptions="Center">
<Label
x:Name="Level"
FontAttributes="Bold"
FontSize="20"
HorizontalOptions="CenterAndExpand"
Text="Level "
TextColor="Black" />
<Label
FontAttributes="Bold"
FontSize="20"
HorizontalOptions="CenterAndExpand"
Text="{Binding use.currentlevel}"
TextColor="Black" />
<Button Command="{Binding GoInfoCommand}"></Button>
</StackLayout>
<Label
x:Name="Totalprocent"
FontAttributes="Bold"
FontSize="20"
HorizontalOptions="CenterAndExpand"
Text="0%"
TextColor="Black" />
</StackLayout>
<Grid Grid.Row="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<StackLayout Grid.Column="0">
<Label
Padding="2"
FontAttributes="Bold"
FontSize="20"
HorizontalOptions="Center"
Text="Muscular"
TextColor="{StaticResource Muscular}" />
<StackLayout HorizontalOptions="Center" Orientation="Horizontal">
<Label
FontAttributes="Bold"
FontSize="20"
Text="{Binding use.muscularprogress}"
TextColor="Black" />
<Label
FontAttributes="Bold"
FontSize="20"
Text="%"
TextColor="Black" />
</StackLayout>
</StackLayout>
<StackLayout Grid.Column="2">
<Label
x:Name="easier"
FontAttributes="Bold"
FontSize="20"
HorizontalOptions="Center"
Text="Cardio"
TextColor="{StaticResource Cardio}" />
<StackLayout HorizontalOptions="Center" Orientation="Horizontal">
<Label
FontAttributes="Bold"
FontSize="20"
Text="{Binding use.cardioprogress}"
TextColor="Black" />
<Label
FontAttributes="Bold"
FontSize="20"
Text="%"
TextColor="Black" />
</StackLayout>
</StackLayout>
</Grid>
</Grid>
</ContentPage>
LevelProgress.cs Model
using SQLite;
using System;
using System.Collections.Generic;
using System.Text;
namespace INWORK.Models
{
public class LevelProgress
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public int currentlevel { get; set; }
public bool pushups;
public bool squats;
public bool pullups;
public bool splitsquats;
public bool stepups;
public bool tricepdips;
public bool legraises;
//Cardio section
public bool running;
public bool intervals;
public double muscularprogress { get; set; }
public double cardioprogress { get; set; }
}
}
Service for accsessing local database
using INWORK.Models;
using SQLite;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Essentials;
namespace INWORK.Services
{
internal class DataStorage
{
private static SQLiteAsyncConnection db;
private static async Task Init()
{
if (db != null)
return;
var databasePath = Path.Combine(FileSystem.AppDataDirectory, "test2.db");
db = new SQLiteAsyncConnection(databasePath);
await db.CreateTableAsync<LevelProgress>();
await db.CreateTableAsync<Overview>();
}
public static async Task FirstCreation()
{
await Init();
LevelProgress LevelProgress = new LevelProgress()
{
currentlevel = 1,
cardioprogress = 0,
muscularprogress = 0,
pushups = false,
squats = false,
pullups = false,
splitsquats = false,
stepups = false,
tricepdips = false,
legraises = false
};
await db.InsertAsync(LevelProgress);
}
public static async Task EditProgress(LevelProgress usehere)
{
await Init();
await db.UpdateAsync(new LevelProgress()
{
Id = 1,
currentlevel = usehere.currentlevel,
muscularprogress = usehere.muscularprogress,
pushups = usehere.pushups,
squats = usehere.squats,
pullups = usehere.pullups,
splitsquats = usehere.splitsquats,
stepups = usehere.stepups,
tricepdips = usehere.tricepdips,
legraises = usehere.legraises,
cardioprogress = usehere.cardioprogress,
running = usehere.running,
intervals = usehere.intervals
});
}
public static async Task FinishWorkout()
{
}
public static async Task<LevelProgress> GetProgress()
{
await Init();
var levelProgress = await db.Table<LevelProgress>().FirstOrDefaultAsync();
//var levelProgress = await db.Table<LevelProgress>().ToListAsync();
return levelProgress;
}
public static async Task AddWorkout(string _Workout_type, int _Result, DateTime _Date)
{
await Init();
Overview Overview = new Overview()
{
Workout_type = _Workout_type,
Result = _Result,
Date = _Date
};
await db.InsertAsync(Overview);
}
public static async Task<IEnumerable<Overview>> GetOverview(string type)
{
await Init();
IEnumerable<Overview> overview;
if (type == "Running" || type == "Intervals")
{
overview = await db.Table<Overview>().Where(v => v.Workout_type == "Running" || v.Workout_type == "Intervals").ToListAsync();
}
else
{
overview = await db.Table<Overview>().Where(v => v.Workout_type != "Running" || v.Workout_type != "Intervals").ToListAsync();
}
return overview;
}
}
}
AboutViewModel
using INWORK.Models;
using INWORK.Services;
using MvvmHelpers;
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace INWORK.ViewModels
{
public class AboutViewModel : ViewModelBase
{
public ICommand GoInfoCommand { get; set; }
public AboutViewModel()
{
Title = "About";
OpenWebCommand = new Command(async () => await Browser.OpenAsync("https://aka.ms/xamarin-quickstart"));
//Command = "{Binding OpenWebCommand}
Task.Run(async () => await Loadup());
//use.currentlevel = use.currentlevel;
}
private LevelProgress pp;
private LevelProgress _use;
public LevelProgress use
{
get => _use;
set
{
_use = value;
OnPropertyChanged();
}
}
public async Task Loadup()
{
_use = new LevelProgress();
var temps = await DataStorage.GetProgress();
use = temps;
//await ProgressTracker.AddWorkout("Ŗunning",2, DateTime.Today);
if (use.currentlevel == 0)
{
await DataStorage.FirstCreation();
Loadup();
}
}
public ICommand OpenWebCommand { get; }
}
}
Yes,if we want to update the UI after we change the field(muscularprogress,cardioprogress) in object use, we need to make class LevelProgress implement interface INotifyPropertyChanged.
Since you have has base class ViewModelBase, we can do like this:
public class LevelProgress: ViewModelBase
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public int currentlevel { get; set; }
public bool pushups;
public bool squats;
public bool pullups;
public bool splitsquats;
public bool stepups;
public bool tricepdips;
public bool legraises;
//Cardio section
public bool running;
public bool intervals;
//public double muscularprogress { get; set; }
private double _muscularprogress;
public double muscularprogress
{
get => _muscularprogress;
set { SetProperty(ref _muscularprogress, value); }
}
//public double cardioprogress { get; set; }
private double _cardioprogress;
public double cardioprogress
{
get => _cardioprogress;
set { SetProperty(ref _cardioprogress, value); }
}
}
Note:
As a test , I created a fake object with special value for it and assign it's value for use at the beginning,after that we change it's value, and the UI could refresh automatically.
private void test(object obj)
{
use.muscularprogress = 98.8;
use.cardioprogress = 12.9;
}

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

Xamarin ActivityIndicator Not Working

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>

Categories

Resources