How to implement automatic login in Xamarin? - xamarin

I am implementing an application in Xamarin with a login functionality. I am using Xamarin SecureStorage to store my username and password in Keychain. I am using Prism to segue between my pages.
I have an AuthenticationService that handles all saving and storing keychain data with SecureStorage. In my App.Xaml, I have a PubSubEvent below that handles when the user is authenticated or not.
var result = await NavigationService.NavigateAsync("/MainPage?title=Welcome");
if (!result.Success)
{
Console.WriteLine("Error {0}", result.Exception);
logger.Report(result.Exception, new Dictionary<string, string> { { "navigationPath", "/MainPage" } });
//MainPage = result.CreateErrorPage();
System.Diagnostics.Debugger.Break();
}
In my LoginPageViewModel I have the following binding based on my login button.
private async Task OnLoginUserCommandExecutedAsync()
{
try
{
MyPrivateSession response = new MyPrivateSession();
//Username/Password text is valid
response = await _authenticationService.LoginAsync(UserLogin);
if (!response.IsValidUser)
{
await PageDialogService.DisplayAlertAsync("", "Login Unsuccessful - Invalid User Id or Password", "Ok");
}
}
catch (AuthenticationException authEx)
{
await PageDialogService.DisplayAlertAsync("Error", authEx.Message, "Ok");
}
catch (Exception e)
{
Debugger.Break();
Logger.Log(e.Message, Category.Exception, Priority.Medium);
await PageDialogService.DisplayAlertAsync("Fatal Error", e.Message, "Ok");
}
}
Also in my LoginPageViewModel, I have the following code in onNavigatedTo to get saved login from SecureStorage
public override async void OnNavigatedTo(INavigationParameters parameters)
{
// Determine if there is a preferred account to use in cases where the user is logged out and sent directly to the login
// in this case the AssumedLogin isn't defined.
if (_authenticationService.AssumedLogin is null)
{
await _authenticationService.AssumeUserPriorToLogin();
if (!(_authenticationService.AssumedLogin is null))
{
UserLogin.Username = _authenticationService.AssumedLogin.Username;
UserLogin.Password = _authenticationService.AssumedLogin.Properties["password"];
//Segues to VIN Utilities
await OnLoginUserCommandExecutedAsync();
}
}
}
In my AuthenticationService, I have this at the end of my logic after accessing username and password from SecureStorage
var user = GetCurrentUser();
_container.UseInstance(user);
//Publish User Authenticated Event
_eventAggregator.GetEvent<UserAuthenticatedEvent>().Publish(user);
My problem is when a user manually logs in with the login button, everything works perfectly. However, when I'm adding logic on the LoginPageViewModel's OnNavigatedTo to automatically authenticate user when the user have a saved username and password. It kicks me out of MainPage and back to login page.
One my doubts is that, when I do it automatic login through navigation page, the application is not finished loading yet. The application would log OnNavigatedTo from MainPage first before "Loaded My App" in the AppDelegate. (see below) How can I ensure the iOS initializers finish first before I start segueing to the main page?
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
Rg.Plugins.Popup.Popup.Init();
SlideOverKit.iOS.SlideOverKit.Init();
SfCheckBoxRenderer.Init();
Syncfusion.SfCarousel.XForms.iOS.SfCarouselRenderer.Init();
Console.WriteLine("Loading My App");
LoadApplication(new App(new IOSInitializer()));
Console.WriteLine("Loaded My App");
return base.FinishedLaunching(app, options);
}
public class IOSInitializer : IPlatformInitializer
{
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
private void OnUserAuthenticated(IUser user)
{
var result = await NavigationService.NavigateAsync("/MainPage");
if (!result.Success)
{
Console.WriteLine("Error {0}", result.Exception);
//MainPage = result.CreateErrorPage();
System.Diagnostics.Debugger.Break();
}
}

Related

Update listview from API call in certain interval- xamarin.forms

I am developing a chat application in xamarin.forms.The viewmodel binded to my chat listview page have an API call , which will fetch the chat data and bind to the listview.The API will call only once ie; when we open the page. What I am trying to do is call the API every 10 seconds and update the listview if there are new messages.But what happening is instead of updating the list, it duplicates the entire data.I think it is normal that if the API called again, it will rebind the entire data. How can I make this update the listview if any new message available? like a chat APP works.Any help or guidance is appreciated.
The API data will be assigned to incoming and outgoing cell according to a parameter.
My viewmodel;
public class ChatPageViewModel : INotifyPropertyChanged
{
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public INavigation Navigation { get; set; }
public string APropertyToSet { get; set; }
public ObservableCollection<NCMessage> Messages { get; set; } = new ObservableCollection<NCMessage>();
public ObservableCollection<ChatData> ChatListObj { get; set; }
public ChatPageViewModel(INavigation navigation)
{
// This is how I call the timer
Device.StartTimer(TimeSpan.FromSeconds(10), () =>
{
Device.BeginInvokeOnMainThread(async () =>
{
await loadChatList();
});
return true;
});
// <--------------- Load chat List API-------------------->
async Task loadChatList()
{
await Task.Run(async () =>
{
try
{
// API call is the dedicated class for makin API call
APICall callForNotificationList = new APICall("apicallUrl/CallChatList", null, null, "GET");
try
{
ChatListObj = callForNotificationList.APICallResult<ObservableCollection<ChatData>>();
if (ChatListObj[0].results.Count != null && ChatListObj[0].results.Count != 0)
{
if (ChatListObj[0].success)
{
foreach (var item in ChatListObj[0].results)
{
if (item.type == "user")
{
if (!string.IsNullOrEmpty(item.message))
{
var message = new NCMessage
{
Text = item.message.ToString(),
IsIncoming = "True"
};
Messages.Add(message);
}
}
}
}
else
{
//error message
}
}
else
{
//error message
}
}
catch (Exception e)
{
}
}
catch (Exception ex)
{
}
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
My chat XAML
<ListView
ItemTemplate="{StaticResource MessageTemplateSelector}"
ItemsSource="{Binding Messages,Mode=OneWay}"
Margin="0"
BackgroundColor="Transparent"
SelectionMode="None"
FlowDirection="RightToLeft"
HasUnevenRows="True" x:Name="ChatList"
VerticalOptions="FillAndExpand"
SeparatorColor="Transparent"
>
</ListView>
My XAML.cs
public partial class ChatPage : ContentPage
{
ChatPageViewModel vm;
public ChatPage()
{
InitializeComponent();
this.BindingContext = vm = new ChatPageViewModel(Navigation);
}
protected async override void OnAppearing()
{
base.OnAppearing();
await Task.Delay(2000);
await vm.loadChatList();
}
}
you are doing this for every message you retrieve from your API without checking to see if the message is already in the collection
Messages.Add(message);
you have (at least) three options
clear the entire Messages collection before calling the API
check if a message exists before adding it to the collection
modify the API call to only return new messages
The usual chat systems work using Socket connections, so that server can push just the new messages that it has received, as opposed to your current pull based system. Take a look at SignalR if you want to pursue that path, here's a good example - Real Time Chat App
As for your current code, there are a few possibilities:
As #Jason has mentioned, you could clear out the existing messages and add all of them again, you should use RangeObservableCollection so that it doesn't redraw the list every time you add a message
You could maintain a Id for all the chat messages and send the Id in your API call, ideally filtering should happen the server side, no point in extra load on the client.
new APICall("apicallUrl/CallChatList?from_id=<last_message_id>", null, null, "GET")
If there are no last_message_id, send all the data. This eliminates unnecessary data transfer, as you'll send only 1 new message than the 10k previous messages :)

Plugin.Geolocator exits method (deadlock?)

I'm building a Xamarin app and for the geolocation, I'm using the GeolocatorPlugin
The problem is that once the code wants to get the position, the code exists without warning.
My class fields:
private Position position;
private IGeolocator locator = CrossGeolocator.Current;
My page constructor:
public MainPage()
{
InitializeComponent();
locator.PositionChanged += Locator_PositionChanged;
locator.PositionError += Locator_PositionError;
}
OnAppearing event is calling the getLocationPermission:
private async Task GetLocationPermission()
{
var status = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.LocationWhenInUse);
if (status != PermissionStatus.Granted)
{
//Not granted, request permission
if (await CrossPermissions.Current.ShouldShowRequestPermissionRationaleAsync(Permission.LocationWhenInUse))
{
// This is not the actual permission request
await DisplayAlert("Need your permission", "We need to access your location", "Ok");
}
// This is the actual permission request
var results = await CrossPermissions.Current.RequestPermissionsAsync(Permission.LocationWhenInUse);
if (results.ContainsKey(Permission.LocationWhenInUse))
status = results[Permission.LocationWhenInUse];
}
//Already granted, go on
if (status == PermissionStatus.Granted)
{
//Granted, get the location
GetLocation();
await GetVenues();
await locator.StartListeningAsync(TimeSpan.FromMinutes(30), 500);
}
else
{
await DisplayAlert("Access to location denied", "We don't have access to your location.", "OK");
}
}
The permission is granted and gets to the GetLocation() method:
private async void GetLocation()
{
//var locator = CrossGeolocator.Current;
try
{
var myPosition = await locator.GetPositionAsync();
position = new Position(myPosition.Latitude, myPosition.Longitude);
}
catch (Exception ex)
{
throw;
}
if (position == null)
{
//Handle exception
}
}
Once the line is reached with locator.GetPositionAsync(), it stops. No exception is thrown, also the PositionError isn't raised.
I have no idea why, but in the beginning it worked once, never worked after that.
The location settings in de Android Emulator are as follow:
Based on my research, you did not acheved that Location Changes like this link
I wrote a demo about Location changes. This is running screenshot.
This is my demo
https://github.com/851265601/GeolocationDemo

how to prevent back button to login page after user logs in ( Xamarin Forms)

i have a log in page where user enters username and password and then the user is redirected to his account page, but when i press the back button it takes me back to the log in page. How do i prevent that from happening.
Code Snippet:
public Login ()
{
if (App.IsUserLoggedIn == false)
{
InitializeComponent();
}
else
{
Navigation.PushAsync(new NavigationPage(new LoginIndexPage()));
}
}
private async void LoginButton_Clicked(object sender, EventArgs e)
{
var user = new Xcore.Users.csUser
{
RefNo = referansnoEntry.Text,
Username = usernameEntry.Text,
Password = passwordEntry.Text
};
var isValid = IsCorrectLogin(user);
if (isValid)
{
App.IsUserLoggedIn = true;
await Navigation.PushAsync(new NavigationPage(new LoginIndexPage()));
}
else
{
showError.Text = "*Hatalı giriş";
passwordEntry.Text = string.Empty;
usernameEntry.Text = string.Empty;
referansnoEntry.Text = string.Empty;
}
}
private bool IsCorrectLogin(Xcore.Users.csUser user)
{
return user.RefNo == Xcore.Users.RegInfo.RefNo
&& user.Username == Xcore.Users.RegInfo.Username
&& user.Password == Xcore.Users.RegInfo.Password;
}
public LoginIndexPage()
{
if (App.IsUserLoggedIn == true)
{
InitializeComponent();
}
else
{
Navigation.PushAsync(new NavigationPage(new Login()));
}
}
protected override void OnAppearing()
{
base.OnAppearing();
showuserName.Text = Xcore.Users.RegInfo.Username;
}
When i login, its going on LoginIndexPage.
Similar to what App Pack mentioned, you should not have LoginPage in the navigation stack. Instead of trying to remove it, just set the next page to the root page.
Instead of pushing the next page onto the stack, set it :
Application.Current.MainPage = <your next page>
Also, I wouldn't navigate to a page in the constructor of a page. That's not really a good practice.
Usually in this situation I would remove the login page from the Navigation Stack.
I believe there is a method on Navigation such as Navigation.RemovePage(Page page) as you are navigating away from the login page. That way your back-stack and functionality stays in place but will not go back to the login page as it has been removed from the stack.
What you must remember however, is that if the login was the root page, there does need to still be a page in the stack when you call pop().
try use this code in your LoginIndexPage.
protected override bool OnBackButtonPressed()
{
return true; // true prevent navigation back and false to allow
}

Google Drive API implementation Xamarin Android

Our application should have the functionality to save Application files to Google Drive. Of course, using the local configured account.
From Android API i tried to figure out some clue. But android API with Xamarin implementation seems very tough for me.
I have installed Google Play Services- Drive from Xamarin Components but there are no examples listed from which we can refer the flow and functionality.
The basic steps (see the link below for full details):
Create GoogleApiClient with the Drive API and Scope
Try to connect (login) the GoogleApiClient
The first time you try to connect it will fail as the user has not selected a Google Account that should be used
Use StartResolutionForResult to handle this condition
When GoogleApiClient is connected
Request a Drive content (DriveContentsResult) to write the file contents to.
When the result is obtained, write data into the Drive content.
Set the metadata for the file
Create the Drive-based file with the Drive content
Note: This example assumes that you have Google Drive installed on your device/emulator and you have registered your app in Google's Developer API Console with the Google Drive API Enabled.
C# Example:
[Activity(Label = "DriveOpen", MainLauncher = true, Icon = "#mipmap/icon")]
public class MainActivity : Activity, GoogleApiClient.IConnectionCallbacks, IResultCallback, IDriveApiDriveContentsResult
{
const string TAG = "GDriveExample";
const int REQUEST_CODE_RESOLUTION = 3;
GoogleApiClient _googleApiClient;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.Main);
Button button = FindViewById<Button>(Resource.Id.myButton);
button.Click += delegate
{
if (_googleApiClient == null)
{
_googleApiClient = new GoogleApiClient.Builder(this)
.AddApi(DriveClass.API)
.AddScope(DriveClass.ScopeFile)
.AddConnectionCallbacks(this)
.AddOnConnectionFailedListener(onConnectionFailed)
.Build();
}
if (!_googleApiClient.IsConnected)
_googleApiClient.Connect();
};
}
protected void onConnectionFailed(ConnectionResult result)
{
Log.Info(TAG, "GoogleApiClient connection failed: " + result);
if (!result.HasResolution)
{
GoogleApiAvailability.Instance.GetErrorDialog(this, result.ErrorCode, 0).Show();
return;
}
try
{
result.StartResolutionForResult(this, REQUEST_CODE_RESOLUTION);
}
catch (IntentSender.SendIntentException e)
{
Log.Error(TAG, "Exception while starting resolution activity", e);
}
}
public void OnConnected(Bundle connectionHint)
{
Log.Info(TAG, "Client connected.");
DriveClass.DriveApi.NewDriveContents(_googleApiClient).SetResultCallback(this);
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_RESOLUTION)
{
switch (resultCode)
{
case Result.Ok:
_googleApiClient.Connect();
break;
case Result.Canceled:
Log.Error(TAG, "Unable to sign in, is app registered for Drive access in Google Dev Console?");
break;
case Result.FirstUser:
Log.Error(TAG, "Unable to sign in: RESULT_FIRST_USER");
break;
default:
Log.Error(TAG, "Should never be here: " + resultCode);
return;
}
}
}
void IResultCallback.OnResult(Java.Lang.Object result)
{
var contentResults = (result).JavaCast<IDriveApiDriveContentsResult>();
if (!contentResults.Status.IsSuccess) // handle the error
return;
Task.Run(() =>
{
var writer = new OutputStreamWriter(contentResults.DriveContents.OutputStream);
writer.Write("Stack Overflow");
writer.Close();
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.SetTitle("New Text File")
.SetMimeType("text/plain")
.Build();
DriveClass.DriveApi
.GetRootFolder(_googleApiClient)
.CreateFile(_googleApiClient, changeSet, contentResults.DriveContents);
});
}
public void OnConnectionSuspended(int cause)
{
throw new NotImplementedException();
}
public IDriveContents DriveContents
{
get
{
throw new NotImplementedException();
}
}
public Statuses Status
{
get
{
throw new NotImplementedException();
}
}
}
Ref: https://developers.google.com/drive/android/create-file

OData Connection in Xamarin Form

My code crashes and gives the following error on simulator. It attempts to run the try block in the GetDataFromOdataService() method and throws an error and also issue an alert. I am using Xamarin.Form
using Simple.OData.Client;
using System.Threading.Tasks;
private ODataClient mODataClient;
protected async override void OnAppearing ()
{
base.OnAppearing ();
await InitializeDataService ();
await GetDataFromOdataService();
}
public async Task <bool> InitializeDataService(){
try {
mODataClient = new ODataClient ("http://services.odata.org/Northwind/Northwind.svc/");
}
catch {
await DisplayAlert("Error", "Connection Error", "OK", "Cancel");
System.Diagnostics.Debug.WriteLine("ERROR!");
}
return true;
}
public async Task<bool> GetDataFromOdataService (){
try {
myCustomers= await mODataClient.For("Customers").Top(10).FindEntriesAsync();
}
catch {
await DisplayAlert("Error", "Connection Error", "OK", "Cancel");
System.Diagnostics.Debug.WriteLine("ERROR!");
}
return true;
}
Couple issues:-
In the constructor it was doing var list = new ListView() which constrained it locally than setting the class level scope variable. This was therefore adjusted to list = new ListView().
The other thing, was in the GetTheData function where the items source was being assigned as list.ItemsSource = myList; where it needed changing to list.ItemsSource = Customers;.
I've repackaged the zip file up and sent to you. Let me know if this works for you? You should now be able to see all your customers in the ListView.

Resources