Xamarin Forms picker SelectedIndexChange null exception - xamarin

I have created an application that asks for a username and returns the stats for this user. When a counter is more than 1 it focuses on a picker and asks to choose one of the options. After, the SelectedIndexChanged (Platformselect_SelectedIndexChanged) method is being called and gets the stats. The problem is that if the user enters a username, the first time it works. When the user goes back and re-enters the username it doesn't focus (it doesn't show the picker) and automatically calls the SelectedIndexChanged method with platformselect.SelectedIndex being -1 causing my program to crash. Could you please help me?
I have tried to set focus on the main thread, I have tested it in both iOS and Android and the same problem appears.
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
platformselect.HeightRequest = 0;
this.search.Clicked += Button_Clicked;
}
public async void searchStats(string usernameText)
{
search.IsEnabled = false;
activityIndicator.IsRunning = true;
var id = await GetDataLogic.GetID(usernameText);
publicid = id;
activityIndicator.IsRunning = false;
if (id.uid == "error")
{
await DisplayAlert("Error", "Could not find username: " + usernameText, "OK");
search.IsEnabled = true;
return;
}
else if (id.uid == "errornointernet")
{
await DisplayAlert("Error", "Could not connect to the Internet. Please check your connection and try again.", "OK");
search.IsEnabled = true;
return;
}
else
{
if(id.platforms.Count > 1)
{
platformselect.ItemsSource = publicid.platforms.ToList();
//we have to show alert to choose platform and then call get user stats.
**Device.BeginInvokeOnMainThread(() =>
{
if (platformselect.IsFocused)
platformselect.Unfocus();
platformselect.Focus();
});**
}
else
{
//we call it without asking because there is only one platform.
var userstats = await GetDataLogic.GetStats(id, 0);
await Navigation.PushAsync(new Stats(userstats, favorites));
}
}
}
private async void Platformselect_SelectedIndexChanged(object sender, EventArgs e)
{
//the second time it doesnt show the picker and it automatically enters -1... it returns null exception. Has to be investigated.
var userstats = await GetDataLogic.GetStats(publicid, platformselect.SelectedIndex);
await Navigation.PushAsync(new Stats(userstats, favorites));
}
}
I would like it to always focus on the picker and wait for the response for the user, not automatically call SelectedIndexChanged with -1 as the selectedindex

I suggest change your event handler this way
private async void Platformselect_SelectedIndexChanged(object sender, EventArgs e)
{
//Check for unselection event
if(platformselect.SelectedIndex == -1) return;
var userstats = await GetDataLogic.GetStats(publicid, platformselect.SelectedIndex);
await Navigation.PushAsync(new Stats(userstats, favorites));
}

Related

Freezing on permission request Xamarin.Forms

I want to get a user's position, and therefore I'm using James Montemagno's Geolocator Plugin for Xamarin. The author uses this code in the code-behind.cs file; I extracted it to a static class:
public static class GeoService
{
public static Position savedPosition;
public static async Task<Position> GetPosition()
{
var lastknown = await ExecuteLastKnownPosition();
if(lastknown == null)
{
var current = await ExecuteGPSPosition();
if(current != null)
{
return current;
}
else
{
return null;
}
}
return lastknown;
}
private static async Task<Position> ExecuteLastKnownPosition()
{
try
{
var hasPermission = await Utils.CheckPermissions(Permission.Location);
if (!hasPermission)
return null;
var locator = CrossGeolocator.Current;
locator.DesiredAccuracy = 50;
//Progress Ring einfügen
var position = await locator.GetLastKnownLocationAsync();
if (position == null)
{
//Benachrichtigung über null lastknownLocation
//Aufrufen der CurrentPosition Methode
return null;
}
savedPosition = position;
return position;
}
catch (Exception ex)
{
Debug.WriteLine(ex);
await Application.Current.MainPage.DisplayAlert("Uh oh", "Something went wrong, but don't worry we captured for analysis! Thanks.", "OK");
return null;
}
finally
{
//Freigabe der Buttons und Bools
}
}
private static async Task<Position> ExecuteGPSPosition()
{
try
{
var hasPermission = await Utils.CheckPermissions(Permission.Location);
if (!hasPermission)
return null;
var locator = CrossGeolocator.Current;
locator.DesiredAccuracy = 100;
//WarteText/Symbol
var position = await locator.GetPositionAsync(TimeSpan.FromSeconds(15));
if (position == null)
{
//Warnung, dass kein GPS vorhanden ist
return null;
}
savedPosition = position;
return position;
}
catch (Exception ex)
{
await Application.Current.MainPage.DisplayAlert("Uh oh", "Something went wrong, but don't worry we captured for analysis! Thanks.", "OK");
return null;
}
finally
{
//Zurücksetzen von Buttons und Lademodus beenden
}
}
public static async Task<Address> ExecuteTrackingAdress(Position currentPosition)
{
try
{
//if (savedPosition == null)
// return null;
var hasPermission = await Utils.CheckPermissions(Permission.Location);
if (!hasPermission)
return null;
string mapkey = "Ajbb9XXXXXXatUzUg1w9BSXXXXXVUAEuF4P-TSXJpnvl5OpXXXXXXXXX";
var locator = CrossGeolocator.Current;
var addresses = await locator.GetAddressesForPositionAsync(currentPosition, mapkey);
var address = addresses.FirstOrDefault();
if (address == null)
{
Debug.WriteLine("Keine Adresse vorhanden");
}
return address;
}
catch (Exception ex)
{
await Application.Current.MainPage.DisplayAlert("Uh oh", "Something went wrong, but don't worry we captured for analysis! Thanks.", "OK");
return null;
}
finally
{
}
}
}
Now I got a ContentPage, and when I go to that page (PushModalAsync) the constructor in the view model calls the GetPosition() method. There's a permission Task and whenever the Task is running, UWP offers me to prompt the location permission.
Unfortunately, from this point on the app is freezing. I can't choose Yes/No, and there's no way of interaction.
I tried to call the methods async, with Task.WhenAll() and so on, but it freezes every time.
This was the last code I wrote in my view model
private async void ExecuteGetPosition()
{
IsBusy = true;
await Task.Yield();
var positiontask = GeoService.GetPosition();
var addresstask = GeoService.ExecuteTrackingAdress(positiontask.Result);
await Task.WhenAll(positiontask, addresstask);
CurrentPosition = positiontask.Result;
CurrentAddress = addresstask.Result;
OnPropertyChanged("CurrentAddressView");
IsBusy = false;
}
I'm assuming that the XAML ContentPage isn't already loaded correctly, and so the prompting-window slips "behind" the MainWindow or something.
Please could you give me an idea for a workaround to handle this?
Edit:
Adding these lines to my App.xaml.cs OnStart()-Method brought the solution. Windows is now calling for permission OnStart, Android asks for permission on gps-request...crazy:
var permission = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.Location);
Are you running on iOS? If so, look at the simulator's console log to see if there is a privacy error. I am guessing it has to do with the privacy declarations required to be in the info.plist when requesting location permissions.
Check out all of the required privacy declarations listed here.
They include (check out the link for info on when to add each one):
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs access location when open.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs access location when in the background.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs access location when open and in the background.</string>

AlertDialog not showing at .show() - Xamarin Android

I have the following code:
private void CloseOrder(object sender, EventArgs e)
{
Android.Support.V7.App.AlertDialog.Builder alert = new Android.Support.V7.App.AlertDialog.Builder(this);
alert.SetTitle("Cerrar Pedido");
alert.SetMessage("Are you sure?");
alert.SetCancelable(true);
alert.SetPositiveButton("Confirm", delegate { this.Rta = true; });
alert.SetNegativeButton("Cancel", delegate { this.Rta = false; });
Dialog dialog = alert.Create();
dialog.Show();
if (this.Rta)
{
//Some code here
}
}
this.Rta is a property of my class.
The problem is that the alert doesn't show at dialog.show(), it shows once the method CloseOrder() ended, so this.Rta never gets the corresponding value assigned.
I've been searching a lot but I can't find a solution, if anyone can help me that'd be great!
dialog.Show() is asynchronous method, that means CloseOrder(object sender, EventArgs e) and dialog.Show() end up at the same time.
You can not get the 'Rta' assigned value at the CloseOrder function.
You will get the value when you click the confirm or cancel button of the dialog.
I suggest you to use message sender in the delegate{this.Rta = true}
For example:
mHandler handler = new mHandler();
Message message = new Message();
message.What = 1;
alert.SetPositiveButton("Confirm", delegate { this.Rta = true; handler.SendMessage(message); });
alert.SetNegativeButton("Cancel", delegate { this.Rta = false; handler.SendMessage(message); });
//....
class mHandler : Handler{
public override void HandleMessage(Message message) {
switch (message.What) {
case 1:
if (this.Rta)
{
//Some code here
}
break;
}
}
}

OnNavigatedTo in ViewModel isn't firing when page first loads

I'm trying to implement Azure Active Directory B2C in Xamarin.Forms. If I just copy their example, I can get it to work without a problem. But when I try to use Prism, I run into problems.
I took this code that was sitting in the codebehind of the XAML:
protected override async void OnAppearing ()
{
base.OnAppearing ();
App.PCApplication.PlatformParameters = platformParameters;
try {
var ar = await App.PCApplication.AcquireTokenSilentAsync(
AuthenticationInfo.Scopes, string.Empty, AuthenticationInfo.Authority,
AuthenticationInfo.SignUpSignInpolicy, false);
AuthenticationInfo.UserAuthentication = ar;
} catch {
}
}
async void OnSignUpSignIn(object sender, EventArgs e)
{
try {
var ar = await App.PCApplication.AcquireTokenAsync(
AuthenticationInfo.Scopes, string.Empty, UiOptions.SelectAccount,
string.Empty, null, AuthenticationInfo.Authority,
AuthenticationInfo.SignUpSignInpolicy);
AuthenticationInfo.UserAuthentication = ar;
} catch (Exception ex) {
if (ex != null) {
}
}
}
and moved it to the ViewModel's OnNavigatedTo:
public async void OnNavigatedTo (NavigationParameters parameters)
{
if (parameters.ContainsKey ("title"))
Title = (string)parameters ["title"];
listen2asmr.App.PCApplication.PlatformParameters = platformParameters;
try {
var ar = await listen2asmr.App.PCApplication.AcquireTokenSilentAsync(
AuthenticationInfo.Scopes, string.Empty, AuthenticationInfo.Authority,
AuthenticationInfo.SignUpSignInpolicy, false);
AuthenticationInfo.UserAuthentication = ar;
} catch {
}
}
This is in the Bootstrapper:
protected override Xamarin.Forms.Page CreateMainPage ()
{
return Container.Resolve<LoginPage> ();
}
protected override void RegisterTypes ()
{
Container.RegisterTypeForNavigation<LoginPage>();
}
OnNavigatedTo never seems to get called though. Is there some other method I should be using, or did I miss something else? The only other thing I could think of was to call the code in OnNavigatedTo from the ViewModel constructor, but the async/await does work with the constructor.
This has been fixed in the latest preview version of Prism for Xamarin.Forms. Try using these packages instead:
https://www.nuget.org/packages/Prism.Forms/6.1.0-pre4
https://www.nuget.org/packages/Prism.Unity.Forms/6.2.0-pre4
Also the bootstrapping process has changed. Read this for more information:
Prism.Forms 5.7.0 Preview - http://brianlagunas.com/first-look-at-the-prism-for-xamarin-forms-preview/
Prism.Forms 6.2.0 Preview - http://brianlagunas.com/prism-for-xamarin-forms-6-2-0-preview/
Prism.Forms 6.2.0 Preview 3 - http://brianlagunas.com/prism-for-xamarin-forms-6-2-0-preview-3/
Preview 4 Post Coming Soon
My advice is use your View events as triggers for your ViewModel.
For Instance:
View.xaml.cs
protected override async void OnAppearing () {
base.OnAppearing ();
viewModel.OnAppearing();
}
async void OnSignUpSignIn(object sender, EventArgs e) {
viewModel.OnSignUpSignIn(sender, e);
}
ViewModel.cs
protected override async void OnAppearing () {
App.PCApplication.PlatformParameters = platformParameters;
try {
var ar = await App.PCApplication.AcquireTokenSilentAsync(
AuthenticationInfo.Scopes, string.Empty,
AuthenticationInfo.Authority,
AuthenticationInfo.SignUpSignInpolicy, false);
AuthenticationInfo.UserAuthentication = ar;
} catch {
}
}
async void OnSignUpSignIn(object sender, EventArgs e) {
try {
var ar = await App.PCApplication.AcquireTokenAsync(
AuthenticationInfo.Scopes, string.Empty,
UiOptions.SelectAccount,
string.Empty, null, AuthenticationInfo.Authority,
AuthenticationInfo.SignUpSignInpolicy);
AuthenticationInfo.UserAuthentication = ar;
} catch (Exception ex) {
if (ex != null) {
}
}
}
Reasons:
View should only involve visuals, and the events your page receives. Logic should be forwarded to the ViewModel, unless it deals with representation of information (for instance, logic to use a toggle-box for 2 choices but a combo-box for 3+).
Nearly vice-versa, the ViewModel should keep track of "model state" (ex. the user still needs to enter their payment information) as opposed to "view state" (ex. the user has navigated to the payment page).

How can I await modal form dismissal using Xamarin.Forms?

Using Xamarin.Forms how can I use make an async method that waits for the form to dismiss? If I use
await Navigation.PushModalAsync(page);
it will return once the animation is finished not when the page is dismissed.
I want a to create modal Task SignInAsync method that return true if sign-in is successful.
You can do this by triggering an event in your login page and listen for that event before going on, but you want the full TAP support and I second you there. Here's a simple yet working 2 page app that does just this. You'll obviously want to use ContentPage custom subclass and have proper methods instead of my quick Commands, but you get the idea, and it saves me typing.
public static Page GetFormsApp ()
{
NavigationPage navpage = null;
return navpage = new NavigationPage (new ContentPage {
Content = new Button {
Text = "Show Login dialog",
Command = new Command (async o => {
Debug.WriteLine ("Showing sign in dialog");
var result = await SignInAsync (navpage);
Debug.WriteLine (result);
})
}
});
}
static Task<bool> SignInAsync (NavigationPage navpage)
{
Random rnd = new Random ();
var tcs = new TaskCompletionSource<bool> ();
navpage.Navigation.PushModalAsync (new ContentPage {
Content = new Button {
Text = "Try login",
Command = new Command ( o => {
var result = rnd.Next (2) == 1;
navpage.Navigation.PopModalAsync ();
tcs.SetResult (result);
})
}
});
return tcs.Task;
}
The minor drawback is that the Task<bool> returns before the end of the pop modal animation, but that's:
easy to fix
only an issue if you're awaiting that result to push a new modal Page. Otherwise, meh, just go on.
Override OnAppearing
Firstly, it's worth noting that simply overriding OnAppearing in the calling Page may suffice in many circumstances.
protected override void OnAppearing()
{
base.OnAppearing();
...
// Handle any change here from returning from a Pushed Page
}
(note that the pushed page's OnDisappearing override is called after the caller's OnAppearing - seems a bit backwards to me!)
AwaitableContentPage
Secondly...this is my take on #Chad Bonthuys answer:
public class AwaitableContentPage : ContentPage
{
// Use this to wait on the page to be finished with/closed/dismissed
public Task PageClosedTask { get { return tcs.Task; } }
private TaskCompletionSource<bool> tcs { get; set; }
public AwaitableContentPage()
{
tcs = new System.Threading.Tasks.TaskCompletionSource<bool>();
}
// Either override OnDisappearing
protected override void OnDisappearing()
{
base.OnDisappearing();
tcs.SetResult(true);
}
// Or provide your own PopAsync function so that when you decide to leave the page explicitly the TaskCompletion is triggered
public async Task PopAwaitableAsync()
{
await Navigation.PopAsync();
tcs.SetResult(true);
}
}
And then call it thus:
SettingsPage sp = new SettingsPage();
await Navigation.PushAsync(sp);
await sp.PageClosedTask; // Wait here until the SettingsPage is dismissed
Just thought I would contribute to this one, although it's been a while since it was asked and answered. I built upon the answer by #noelicus. I wanted a generic way to do this with multiple situations so the Task needs to be able to return not just bool but anything. Then, using generics:
public class AwaitableContentPage<T> : ContentPage
{
// Use this to wait on the page to be finished with/closed/dismissed
public Task<T> PageClosedTask => tcs.Task;
// Children classes should simply set this to the value being returned and pop async()
protected T PageResult { get; set; }
private TaskCompletionSource<T> tcs { get; set; }
public AwaitableContentPage()
{
tcs = new TaskCompletionSource<T>();
}
protected override void OnDisappearing()
{
base.OnDisappearing();
tcs.SetResult(PageResult);
}
}
Now, in the page you want to run as modal, you can do:
public partial class NewPerson : AwaitableContentPage<Person>
and when done, simply do:
base.PageResult = newPerson; // object you created previously
await base.Navigation.PopAsync();
Then, to make it super simple to use, use an extension method:
public static class ExtensionMethods
{
async public static Task<T> GetResultFromModalPage<T>(this INavigation nav, AwaitableContentPage<T> page)
{
await nav.PushAsync(page);
return await page.PageClosedTask;
}
That's all. Now, in your code, in any page where you want to use this, the syntax ends up simply like this:
Person newPerson = await Navigation.GetResultFromModalPage<string>(new NewPersonCreatePage());
if (newPerson != null)
UseNewPersonCreatedByOtherPage();
Hope this helps!
In my implementation I used:
await navigation.PopModalAsync();
Full Example:
private INavigation navigation;
public LoginPageModel(INavigation navigation, LoginPage loginPage)
{
this.navigation = navigation;
this.loginPage = loginPage;
}
public bool IsValid { get; set; }
protected async void ExecuteLoginCommand()
{
var loginResult = await AuthenticationHelper.Authenticate(Email, Password);
var isValid = false;
if (loginResult != null)
{
isValid = true;
}
//return isValid;
AuthenticationResult(isValid);
}
private async void AuthenticationResult(bool isValid)
{
if (isValid)
{
Debug.WriteLine("Logged in");
await navigation.PopModalAsync();
}
else
{
Debug.WriteLine("Failed" + email + password);
await loginPage.DisplayAlert("Authentication Failed", "Incorrect email and password combination","Ok", null);
}
}
The answer selected and given by #Stephane Delcroix above is awesome. But for anybody willing to push this further, by waiting for a page's completion and returning more structured data in a good MVVM fashion, you could do the following:
By calling an event from the page's OnDisapearing method, this event can then be subscribed by the navigation service which you create, and you can then use the "TaskCompletionSource" to wati until your page finishes its work and then complete the task.
For more details about accomplishing this, you can check this blog post.
Here is the base page's implementation, every page in this demo app inherit this page:
public class BasePage<T> : ContentPage
{
public event Action<T> PageDisapearing;
protected T _navigationResut;
public BasePage()
{
}
protected override void OnDisappearing()
{
PageDisapearing?.Invoke(_navigationResut);
if (PageDisapearing != null)
{
foreach (var #delegate in PageDisapearing.GetInvocationList())
{
PageDisapearing -= #delegate as Action<T>;
}
}
base.OnDisappearing();
}
}
Here is an overview of the navigation service you should use:
public async Task<T> NavigateToModal<T>(string modalName)
{
var source = new TaskCompletionSource<T>();
if (modalName == nameof(NewItemPage))
{
var page = new NewItemPage();
page.PageDisapearing += (result) =>
{
var res = (T)Convert.ChangeType(result, typeof(T));
source.SetResult(res);
};
await App.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page));
}
return await source.Task;
}
To call this page with the navigation service, you can use the following code:
var item = await new SimpleNavigationService().NavigateToModal<Item>(nameof(NewItemPage));
Items.Add(item);

Windows Phone 7 - wait for Webclient to complete

I'm developing an app and have run into a problem with asynchronous calls... Here's what i'm trying to do.
The app consumes a JSON API, and, when run, fills the ListBox within a panorama item with the necessary values (i.e. a single news article). When a user selects a ListBox item, the SelectionChanged event is fired - it picks up the articleID from the selected item, and passes it to an Update method to download the JSON response for the article, deserialize it with JSON.NET, and taking the user to the WebBrowser control which renders a html page from the response received.
The problem with this is that I have to wait for the response before I start the NavigationService, but I'm not sure how to do that properly. This way, the code runs "too fast" and I don't get my response in time to render the page.
The event code:
private void lstNews_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (lstNews.SelectedIndex == -1)
{
return;
}
ShowArticle _article = new ShowArticle();
ListBox lb = (ListBox)sender;
GetArticles item = (GetArticles)lb.SelectedItem;
string passId = ApiRepository.ApiEndpoints.GetArticleResponseByID(item.Id);
App.Current.JsonModel.JsonUri = passId;
App.Current.JsonModel.Update();
lstNews.SelectedIndex = -1;
NavigationService.Navigate(new Uri("/View.xaml?id=" + item.Id, UriKind.Relative));
}
OnNavigatedTo method in the View:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
long sentString = long.Parse(NavigationContext.QueryString["id"]);
string articleUri = ApiRepository.ApiEndpoints.GetArticleResponseByID(Convert.ToInt32(sentString));
//this throws an error, runs "too fast"
_article = App.Current.JsonModel.ArticleItems[0];
}
The update method:
public void Update()
{
ShowArticle article = new ShowArticle();
try
{
webClient.DownloadStringCompleted += (p, q) =>
{
if (q.Error == null)
{
var deserialized = JsonConvert.DeserializeObject<ShowArticle>(q.Result);
_articleItems.Clear();
_articleItems.Add(deserialized);
}
};
}
catch (Exception ex)
{
//ignore this
}
webClient.DownloadStringAsync(new Uri(jsonUri));
}
async callback pattern:
public void Update(Action callback, Action<Exception> error)
{
webClient.DownloadStringCompleted += (p, q) =>
{
if (q.Error == null)
{
// do something
callback();
}
else
{
error(q.Error);
}
};
webClient.DownloadStringAsync(new Uri(jsonUri));
}
call:
App.Current.JsonModel.Update(() =>
{
// executes after async completion
NavigationService.Navigate(new Uri("/View.xaml?id=" + item.Id, UriKind.Relative));
},
(error) =>
{
// error handling
});
// executes just after async call above

Resources