I am displaying 2 second fading animation in view class, after 2-secs, I want to go to viewmodel class to run which load login page or signup page logic
right now its skipping 2 sec animation and going directly to viewmodel. idk what the issue is here
if i delete line BindingContext = new SplashscreenViewModel(); than animation shows up but it doesnt go to viewmodel class
SplashscreenPage.xaml.cs - view class
public SplashscreenPage ()
{
InitializeComponent ();
Animation();
BindingContext = new SplashscreenViewModel();
}
async void Animation()
{
ssImage.Opacity = 0;
await Task.WhenAll(
ssImage.FadeTo(1, 2000),
ssImage.ScaleTo(1.1, 2000)
);
}//end of method
Splashscreen - viewmodel class
public SplashscreenViewModel()
{
WhichPageToLoad();
}
async void WhichPageToLoad()
{
var getToken = await SecureStorage.GetAsync("Save_Pin_1");
if(getToken == null)
{
var route = $"{ nameof(SignupPage)}";
await Shell.Current.GoToAsync(route);
}
else
{
var route = $"{ nameof(LoginPage)}";
await Shell.Current.GoToAsync(route);
}
}
there is really no point in having a VM class or assigning the BindingContext since you are not doing any databinding. You can just call the navigation code after the animation completes. You cannot make async calls from the constructor, so moving the animation and navigation login into OnAppearing will allow you to make an async call
public SplashscreenPage ()
{
InitializeComponent ();
}
async override void OnAppearing()
{
ssImage.Opacity = 0;
await Task.WhenAll(
ssImage.FadeTo(1, 2000),
ssImage.ScaleTo(1.1, 2000)
);
var getToken = await SecureStorage.GetAsync("Save_Pin_1");
if(getToken == null)
{
var route = $"{ nameof(SignupPage)}";
await Shell.Current.GoToAsync(route);
}
else
{
var route = $"{ nameof(LoginPage)}";
await Shell.Current.GoToAsync(route);
}
}
Related
In my Xamarin.Forms Application, I am using azure mobile service for Offline-data sync.
in my App's OnStart method I am calling an async task and according to the result of the task I need to load differrent pages. Unfortunately now no page is loading. When I remove the call to async task the page is loading.
OnStart Method
protected async override void OnStart()
{
login = new List<Login>();
List<Login> items = await App.dataManager.GetLoginAsync();
if (items.Count <= 0)
{
MainPage = (new Registration());
}
else
{
MainPage = (new LoginPage());
}
}
Async Task Method
public async Task<List<Login>> GetLoginAsync()
{
try
{
IEnumerable<Login> items = await logins
.ToEnumerableAsync();
return new List<Login>(items);
}
catch (MobileServiceInvalidOperationException msioe)
{
Debug.WriteLine(#"Invalid sync operation: {0}", msioe.Message);
}
catch (Exception e)
{
Debug.WriteLine(#"Sync error: {0}", e.Message);
}
return null;
}
Since The MainPage property should be set in the App constructor, and we should not block UI thread by waiting an async method in constructor, you can achieve your navigation logic like this:
In App's constructor set MainPage like this:
MainPage = new StarterPage();
In StarterPage's OnAppearing method:
protected override async void OnAppearing()
{
base.OnAppearing();
login = new List<Login>();
//show ActivityIndicator
List<Login> items = await App.dataManager.GetLoginAsync();
//hide ActivityIndicator
if (items.Count <= 0)
{
await Navigation.PushAsync(Registration);
}
else
{
await Navigation.PushAsync(LoginPage);
}
}
Async operations typically blocks the UI thread. You may use Device.BeginInvokeOnMainThread (() => {
MainPage = (new Registration());
});
I think that the OnStart method doesn't really work well with async void.
What you can do:
protected override void OnStart()
{
NavigateToEntryAsync()
.ContinueWith(
t => Log.Error("Unhandled exception while navigation to entry page", t.Exception.InnerException),
TaskContinuationOptions.OnlyOnFaulted);
}
protected async Task NavigateToEntryAsync()
{
login = new List<Login>();
List<Login> items = await App.dataManager.GetLoginAsync();
if (items.Count <= 0)
{
Device.BeginInvokeOnMainThread(() => MainPage = (new Registration()));
}
else
{
Device.BeginInvokeOnMainThread(() => MainPage = (new LoginPage()));
}
}
I need some help to understand why this exception is thrown. The exception is:
Android.Util.AndroidRuntimeException: Only the original thread that created a view hierarchy can touch its views.
Link to hastebin with full exception
I use ZXing.Net.Mobile.Forms for barcode scanning and Rg.Plugins.Popup for showing a popup. I believe one of them causes the exception.
The exception seems to be thrown at random. The app works fine 99 % of the time.
ScannerPage.xaml
<zxing:ZXingScannerView x:Name="ScannerView"
Result="{Binding ScanResult, Mode=OneWayToSource}"
ScanResultCommand="{Binding ScanResultCommand}"
IsScanning="{Binding IsScanning}"
IsAnalyzing="{Binding IsAnalyzing}" />
<zxing:ZXingDefaultOverlay x:Name="ScannerOverlay"
BottomText="Scanning will happen automatically"
ShowFlashButton="False"/>
ScannerPageViewModel.cs (stripped of irrelevant parts)
[PropertyChanged.AddINotifyPropertyChangedInterface]
internal class ScannerPageViewModel : INavigatedAware
{
public ScannerPageViewModel(IScannerService scannerService, IUserDialogs dialogs, IPopupNavigation popups, IScreenService screen)
{
ScanResultCommand = new Command(ProcessBarcode);
}
public ICommand ScanResultCommand { get; }
/// <summary>
/// Show info dialog box with ticket info.
/// </summary>
private async Task ShowInfoScanResult(string message)
{
var popup = new PopupViews.InfoScanResult(Popups, message);
popup.Disappearing += (se, ev) => IsAnalyzing = true;
await Popups.PushAsync(popup);
}
private void ProcessBarcode()
{
Device.BeginInvokeOnMainThread(async () =>
{
if (ScanResult != null && !string.IsNullOrEmpty(ScanResult.Text))
{
// Disable the scanner after one barcode is found.
IsAnalyzing = false;
var source = new CancellationTokenSource();
// Show loading animation if scanning takes >1 second.
var t = Task.Run(async () =>
{
await Task.Delay(1000, source.Token);
Device.BeginInvokeOnMainThread(ShowLoading);
});
// Call the web service to process the barcode.
var scanResponse = await ScannerService.ScanBarcode(ScanResult.Text, ScanningSession, SelectedScanAction);
if (scanResponse.IsSuccessful)
{
var scanResult = scanResponse.Data;
if (scanResult.Success)
{
var json = scanResult.BarcodeInfo;
var message = ParseJsonBarcodeInfo(json);
if (SelectedScanAction == ScanAction.Information)
await ShowInfoScanResult(message);
else
await ShowOkScanResult(message);
}
else
{
await ShowErrorScanResult(scanResult.FaultDescription);
}
}
else
{
ShowScanRequestError(scanResponse.ErrorMessage);
}
source.Cancel(); // Cancel loading animation timer.
HideLoading();
Screen.SetFullscreen();
source.Dispose();
}
});
}
I have created the Dependency service for Android for me it's working perfect check a below code.
PCL Project
public interface IBarcodeScanner
{
Task<string> ScanAsync();
}
And then in Android project
[assembly: Dependency(typeof(BarcodeScanner))]
namespace CodeMashScanner.Droid.Helpers
{
public class BarcodeScanner : IBarcodeScanner
{
public async Task<string> ScanAsync()
{
var scanner = new ZXing.Mobile.MobileBarcodeScanner(Forms.Context;
var scanResults = await scanner.Scan();
return scanResults.Text;
}
}
}
We are trying to use Touch Id with iOS using our Xamarin Forms application.
In our Xamarin Forms Application, in the App.Xaml.cs constructor we are using an interface to reference the iOS native touch id implementation:
bool _authenticatedWithTouchID = DependencyService.Get<ITouchID>().AuthenticateUserIDWithTouchID();
if (_authenticatedWithTouchID)
{
MainPage = new NavigationPage(new MainPage());
}
else
{
MainPage = new NavigationPage(new LoginPage());
}
This is the interface signature within Forms Application:
public interface ITouchID
{
bool AuthenticateUserIDWithTouchID();
}
This is the implementation of the interface within the iOS project:
[assembly: Dependency(typeof(TouchID))]
namespace GetIn.iOS
{
public class TouchID : ITouchID
{
public bool AuthenticateUserIDWithTouchID()
{
bool outcome = false;
var context = new LAContext();
if (context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out NSError AuthError))
{
var replyHandler = new LAContextReplyHandler((success, error) => {
Device.BeginInvokeOnMainThread(() => {
if (success)
{
outcome = true;
}
else
{
outcome = false;
}
});
});
context.EvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, "Logging with touch ID", replyHandler);
};
return outcome;
}
}
}
We get a response from the outcome variable (which is true if user is authenticated) but that is not being passed back to the forms application.
We have also tried using async tasks with no luck.
Is there a recommended way we do this? We are trying to keep this as simple as possible.
You need to change your code to handle asynchronous behavior.
public class TouchID : ITouchID
{
public Task<bool> AuthenticateUserIDWithTouchID()
{
var taskSource = new TaskCompletionSource<bool>();
var context = new LAContext();
if (context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out NSError AuthError))
{
var replyHandler = new LAContextReplyHandler((success, error) => {
taskSource.SetResult(success);
});
context.EvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, "Logging with touch ID", replyHandler);
};
return taskSource.Task;
}
}
Remember add the using on top
using System.Threading.Tasks;
And change your interface declaration
public interface ITouchID
{
Task<bool> AuthenticateUserIDWithTouchID();
}
And finally your Xamarin.Forms code...
var touchId = DependencyService.Get<ITouchID>();
var _authenticatedWithTouchID = await touchId.AuthenticateUserIDWithTouchID();
if (_authenticatedWithTouchID)
{
MainPage = new NavigationPage(new MainPage());
}
else
{
MainPage = new NavigationPage(new LoginPage());
}
Managed to get this working by using the async changes above (although you can do this without using the async method), and then doing the following:
By moving and adapting the following code from the app.xaml.cs to our MainPage.xaml.cs (our tabbed page) constructor.
var touchId = DependencyService.Get<ITouchID>();
var _authenticatedWithTouchID = await
touchId.AuthenticateUserIDWithTouchID();
if (_authenticatedWithTouchID)
{
//Do Nothing as on this page
}
else
{
//Go back to login page
Navigation.InsertPageBefore(new LoginPage(), this);
await Navigation.PopAsync();
}
I have a task in my viewmodel that looks like this:
ICommand getWeather;
public ICommand GetWeatherCommand =>
getWeather ??
(getWeather = new Command(async () => await ExecuteGetWeatherCommand()));
public async Task ExecuteGetWeatherCommand()
{
if (IsBusy)
return;
IsBusy = true;
try
{
WeatherRoot weatherRoot = null;
var units = IsImperial ? Units.Imperial : Units.Metric;
if (UseGPS)
{
//Get weather by GPS
var local = await CrossGeolocator.Current.GetPositionAsync(10000);
weatherRoot = await WeatherService.GetWeather(local.Latitude, local.Longitude, units);
}
else
{
//Get weather by city
weatherRoot = await WeatherService.GetWeather(Location.Trim(), units);
}
//Get forecast based on cityId
Forecast = await WeatherService.GetForecast(weatherRoot.CityId, units);
var unit = IsImperial ? "F" : "C";
Temp = $"Temp: {weatherRoot?.MainWeather?.Temperature ?? 0}°{unit}";
Condition = $"{weatherRoot.Name}: {weatherRoot?.Weather?[0]?.Description ?? string.Empty}";
}
catch (Exception ex)
{
Temp = "Unable to get Weather";
//Xamarin.Insights.Report(ex);
}
finally
{
IsBusy = false;
}
}
How can I reach that Task and make it execute the function properly?
My goal is for it to execute right when the user enters the contentpage (StartPage). Right now I use this code below but the Command does not execute.
public StartPage ()
{
InitializeComponent ();
loadCommand ();
}
async Task loadCommand ()
{
var thep = new WeatherViewModel ();
await thep.ExecuteGetWeatherCommand ();
}
I bind the command into my listview:
RefreshCommand="{Binding GetWeatherCommand}"
With my current code the Task does not execute. What am I missing?
Firstly, your naming convention is odd, theTask is not a Task, so you probably should not call it one. Secondly, because you are calling loadCommand in your constructor and not awaiting it, its possible for the constructor to complete before the function is completed. In general, you want to avoid async in your constructor.
Stephen Cleary has a great article on async in constructors here: http://blog.stephencleary.com/2013/01/async-oop-2-constructors.html
What may be appropriate is attaching a handler to the Appearing event of your page, and do the async work there. Without more context it is a little hard to say if this is the best approach for your use case.
For instance:
public StartPage ()
{
InitializeComponent ();
Appearing += async (sender, args) => await loadCommand();
}
async Task loadCommand ()
{
var viewModel = new StartPageViewModel();
await viewModel.ExecuteCommand();
}
I'm working with the Philips Hue, and I need to get some information from the hue bridge before I can populate my application. The requests are made via HTTP/JSON. I have no issue when I run all my code async, but when I try to break out my code so that I have a separate method to update the UI upon loading I'm getting a System.NullReferenceException on myLights. I'm assuming that's because my startUpProcedure() isn't finished yet, hence myLights has not been set. I can't seem to figure out how to wait for the startUpProcedure() to finish before I run setupUI().
To further back that up, if I just run startUpProcedure() without setupUI() I get no issues. And then if I run setupUI() from say a button click it runs just fine.
Clearly I'm missing something here. I just can't seem to find the answer.
So to succinctly put the question: How do I wait for these Async calls to finish so that I can use variables that are depended on their return values?
public sealed partial class MainPage : Page
{
public string appKey;
public string myIP;
public IEnumerable<Light> myLights;
public ILocalHueClient myClient;
public MainPage()
{
this.InitializeComponent();
startUpPocedure();
setupUI();
}
public async Task startUpPocedure()
{
await startUp();
await getLights();
}
public async Task startUp()
{
if (await findBridgeIP())
{
Debug.WriteLine("Bridge Found...");
//Do Actions
}
else
{
//Error!!
Debug.WriteLine("No hue found");
}
Windows.Storage.ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
Windows.Storage.StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
try {
appKey = localSettings.Values["appKey"].ToString();
Debug.WriteLine("appKey loaded: " + appKey);
//Load up
myClient = new LocalHueClient(myIP);
myClient.Initialize(appKey);
}
catch {
Debug.WriteLine("Need to register app");
}
}
async Task getLights()
{
myLights = await myClient.GetLightsAsync();
Debug.WriteLine("Light Count " + myLights.Count());
IEnumerable<string> myLightNames;
List<string> myTempList = new List<string>();
foreach (Light l in myLights)
{
myTempList.Add(l.Name);
Debug.WriteLine(l.Name);
}
myLightNames = myTempList;
comboBox_LightSelect.ItemsSource = myLightNames;
}
private void setupUI()
{
//Populate the Combo Box
IEnumerable<string> myLightNames;
List<string> myTempList = new List<string>();
foreach (Light l in myLights)
{
myTempList.Add(l.Name);
Debug.WriteLine(l.Name);
}
myLightNames = myTempList;
comboBox_LightSelect.ItemsSource = myLightNames;
}
Your startUpPocedure method in MainPage constructor returns Task almost immediately, and because you're not awaiting it, code execution goes to the next line right after that and setupUI gets called. So when your code starts to enumerate myLights collection, it's still null because startUpPocedure and therefore getLights are still running.
Problem is, you can't await asynchronous methods in a constructor, so in your case solution will be to move both startUpPocedure and setupUI to single async method, await them inside this method and call it from constructor similar to this:
public MainPage()
{
this.InitializeComponent();
Startup();
}
private async void Startup()
{
await startUpPocedure();
setupUI();
}
You should leave only InitializeComponent in the constructor, and move all other logic to Loaded event handler,
https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.frameworkelement.loaded
Then you can mark that handler as async, and use await in it to await on async methods.