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

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

Related

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

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

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"/>

ListView not filling up when I try to fill it from another page using MessagingCenter to comunicate both classes

I'm trying to fill up or populate a ListView that is instantiated in PageA when the user clicks a button in PageB. I'm doing it sending a message with the MessageCenter in PageB and calling the MessageCenter.Subscribe() method in PageA (the Page where I want to add the new ViewCell or row to the ListView)... but it's not working.
I don't think that the problem is coming from the send/subscribe usage because I have alredy debugged the application and the collection that i'm passing to the ListiView.ItemSource property is indeed growning in size. Here you can see the class definitions:
PageA class definition:
public partial class WorkoutRoutineTab : ContentPage
{
public List<Routine> Routines { get; set; } = new List<Routine>();
public WorkoutRoutineTab()
{
InitializeComponent();
routineListView.ItemsSource = Routines;
MessagingCenter.Subscribe<AddExercisePage, Routine>(this, "FillRoutines", (messageSender, arg) =>
{
Routines.Add(new Routine(arg.Name, arg.ExerciseList));
routineListView.ItemsSource = Routines;
});
}
private async void NewRoutineButton_Clicked(object sender, EventArgs e)
{
await DisplayAlert("ALERT", Routines.Count.ToString() , "ok");
await Navigation.PushAsync(new ExerciseBankTab() { Title = "" });
}
PageA .xaml file:
<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"
xmlns:local="clr-namespace:PumpFit"
xmlns:custom="clr-namespace:PumpFit.Entity"
mc:Ignorable="d"
x:Class="PumpFit.WorkoutRoutineTab"
Title="Workout"
BackgroundColor="#343434">
<StackLayout x:Name="routineStackLayout" Orientation="Vertical" HorizontalOptions="Center" VerticalOptions="Center" Margin="20,10">
<ListView x:Name="routineListView" x:FieldModifier="public" SeparatorColor="#2C2C2C">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid RowDefinitions="2*,*" ColumnDefinitions="*,*">
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Text="{Binding custom:Name}" TextColor="White" FontSize="Large" FontFamily="Ubuntu"/>
<Label Grid.Row="1" Grid.Column="0" Text="{Binding custom:TimesDone}" TextColor="#9F9F9F" FontSize="Body" FontFamily="Ubuntu"/>
<Label Grid.Row="1" Grid.Column="1" Text="{Binding custom:TimesDone}" TextColor="#9F9F9F" FontSize="Body" FontFamily="Ubuntu"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button x:Name="newRoutineButton" Text="New routine" FontSize="Body" FontFamily="Geo"
BackgroundColor="#2C2C2C" TextColor="#87BC72" Clicked="NewRoutineButton_Clicked" />
</StackLayout>
</ContentPage>
PageB class definition:
public partial class AddExercisePage : ContentPage
{
public Exercise newExercise;
public AddExercisePage(Exercise selectedExercise)
{
InitializeComponent();
newExercise = selectedExercise;
nameLabel.Text = newExercise.Name;
}
private async void CancelButton_Clicked(object sender, EventArgs e)
{
await Navigation.PopModalAsync();
}
private void AddButton_Clicked(object sender, EventArgs e)
{
if(setsEntry.Text != null && repsEntry.Text != null && restTimePicker.SelectedItem != null)
{
if (int.TryParse(setsEntry.Text, out int sets) && int.TryParse(repsEntry.Text, out int reps) && sets > 0 && reps > 0)
{
List<Exercise> newExerciseList = new List<Exercise>()
{
new Exercise(newExercise.Name, newExercise.MuscleGroup, newExercise.ExerciseDifficulty, newExercise.Equipment, newExercise.Description, sets, reps, restTimePicker.SelectedItem.ToString())
};
MessagingCenter.Send<AddExercisePage, Routine>(this, "FillRoutines", new Routine(routineNameEntry.Text, newExerciseList));
}
else
{
DisplayAlert("ERROR", "You must only enter positive numbers", "OK");
}
}
else
{
DisplayAlert("ERROR", "All fields must be set to add the exercise to the routine", "OK");
}
}
}
ANY HELP WILL BE APRECCIATE IT! :v
The issue is that you're using
public List<Routine> Routines { get; set; } = new List<Routine>();
Change it to :
public ObservableCollection<Routine> Routines { set; get; } = new ObservableCollection<Routine>();
Both List and ObservableCollection implement IList<T>, there isn't much of a difference there, ObservableCollection also implements INotifyCollectionChanged interface.So it will update the Ui automatically when the collection changes.

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>

Xamarin forms android json - Async call hangs/never returns

Hello I'm working on a android app in Xamarin forms thats getting json data from a service and its supposed to display that data in The ContactInfo class however when you click on the button that's supposed to get that data and then display it and then take you to the ContactInfo class it hangs and after a minute my galaxy s7 tells me that app isn't responding would you like to close it?
I have no idea what i'm doing wrong
here's my 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="ReadyMo.ContactInfo">
<ListView ItemsSource="{Binding ContactInfoList}" HasUnevenRows="true">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame Padding="0,0,0,8" BackgroundColor="#d2d5d7">
<Frame.Content>
<Frame Padding="15,15,15,15" OutlineColor="Gray" BackgroundColor="White">
<Frame.Content>
<StackLayout Padding="20,0,0,0" Orientation="Horizontal" HorizontalOptions="CenterAndExpand">
<Label x:Name="FirstName" Text="{Binding First_Name}" HorizontalOptions="Center">
</Label>
<Label x:Name ="LastName" Text="{Binding Last_Name}" HorizontalOptions="Center">
</Label>
<Label x:Name="County" Text="{Binding name}" HorizontalOptions="Center">
</Label>
<Label x:Name ="Adress" Text="{Binding Address1}" HorizontalOptions="Center">
</Label>
<Label x:Name ="City" Text="{Binding Address2}" HorizontalOptions="Center">
</Label>
<Label x:Name="Number" Text="{Binding BusinessPhone}" HorizontalOptions="Center">
</Label>
</StackLayout>
</Frame.Content>
</Frame>
</Frame.Content>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
Here's my codebehind:
using Newtonsoft.Json;
using ReadyMo.Data;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace ReadyMo
{
public partial class ContactInfo : ContentPage
{
private County item;
public static async Task<string> GetContactString(string contactid)
{
HttpClient client = new HttpClient();
var url = $"http://sema.dps.mo.gov/county/service.php?id={contactid}";
var response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var responsetext = await response.Content.ReadAsStringAsync();
return responsetext;
}
throw new Exception(response.ReasonPhrase);
}
public ContactInfo()
{
InitializeComponent();
}
List <ContactInfoModel> ContactInfoList;
public ContactInfo(County item)
{
InitializeComponent();
this.item = item;
var contactpagetext = ContactManager.GetContactString(item.id);
var contact = GetContactString("001").Result;
//need to add update database code
//Convert Jason string to object
ContactInfoList = JsonConvert.DeserializeObject<List<ContactInfoModel>>(contact);
this.BindingContext = ContactInfoList;
//Floodplain Administrators
}
}
}
any help would be amazing!
Thank in advance!
Your problem is in this line:
var contact = GetContactString("001").Result;
It's causing a deadlock that I explain in full on my blog.
To fix this, you have to first realize that the UI must display immediately. When the OS calls your code, you can't wait for an HTTP request to complete before responding; that's simply not allowed.
What you can do is start the HTTP request and display something else (like a spinner or "loading..." message) immediately. Then, when the HTTP request completes, update your display with the new information.
I have more details and sample code in my MSDN article on async MVVM data binding.
Try moving the async code out of the constructor. A common approach to this is to override OnAppearing() to perform the work. You would also probably want to display an ActivityIndicator in your UI until the data has been loaded:
public partial class ContactInfo : ContentPage
{
private County item;
public static async Task<string> GetContactString(string contactid)
{
HttpClient client = new HttpClient();
var url = $"http://sema.dps.mo.gov/county/service.php?id={contactid}";
var response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var responsetext = await response.Content.ReadAsStringAsync();
return responsetext;
}
throw new Exception(response.ReasonPhrase);
}
public ContactInfo()
{
InitializeComponent();
ContactInfoList = new ObservableCollection<ContactInfoModel>();
}
ObservableCollection<ContactInfoModel> ContactInfoList;
public ContactInfo(County item) : this()
{
this.item = item;
this.BindingContext = ContactInfoList;
}
protected override async void OnAppearing ()
{
if (item == null)
return;
var contact = await GetContactString(item.id);
var models = JsonConvert.DeserializeObject<List<ContactInfoModel>>(contact);
foreach (var model in models)
ContactInfoList.Add(model);
}
}
To fix the binding problem, recognize that you are assigning the list itself to your page's BindingContext. You can do one of two things:
Change ItemsSource="{Binding ContactInfoList}" in your ListView XAML to ItemsSource="{Binding .}".
Change this.BindingContext = ContactInfoList; in your constructor to this.BindingContext = this;.
Option #1 is preferred.

Categories

Resources