Touch Id with Xamarin Forms Application - c#

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();
}

Related

Xamarin Android ForceDarkHelper What is it?

Periodically, the application begins to update itself. There is a constant call in the logs:
[ForceDarkHelper] updateByCheckExcludeList: pkg: com.companyname.manimobile activity: crc64d14753dcc52b83b4.MainActivity#a894c70
[ForceDarkHelper] updateByCheckExcludeList: pkg: com.companyname.manimobile activity: crc64d14753dcc52b83b4.MainActivity#a894c70
[ForceDarkHelper] updateByCheckExcludeList: pkg: com.companyname.manimobile activity: crc64d14753dcc52b83b4.MainActivity#a894c70
[ForceDarkHelper] updateByCheckExcludeList: pkg: com.companyname.manimobile activity: crc64d14753dcc52b83b4.MainActivity#a894c70
When this happens, if, for example, you open the menu , it closes itself, if something is filled in, it is cleared, the page is updated. There are no timers in the code. I'm testing the app on Xiaomi Redmi. I repeat sometimes it happens sometimes it doesn't. What is it?
I do not know what the problem is, but occasionally, it happens that the application throws the fingerprint to the page. It is intermittent. Sometimes everything works fine. That is, I go through the fingerprint, the next page opens, everything is normal and a second after 5 I am again thrown to the page where you need to enter the fingerprint.
Code for the authorization page:
public authentification()
{
try
{
InitializeComponent();
bool auth = CrossSettings.Current.GetValueOrDefault("authorized", false);
if (auth == false) { CheckAuth(); }
else
{
Application.Current.MainPage = new MasterLk();
}
}
catch { }
}
async void CheckAuth()
{
try
{
var avail = await CrossFingerprint.Current.IsAvailableAsync();
if (!avail)
{
CrossSettings.Current.GetValueOrDefault("authorized", true);
Application.Current.MainPage = new MasterLk();
}
else
{
var request = new AuthenticationRequestConfiguration("NeedAuth", "-");
var result = await CrossFingerprint.Current.AuthenticateAsync(request);
if (result.Authenticated)
{
CrossSettings.Current.GetValueOrDefault("authorized", true);
Application.Current.MainPage = new MasterLk();
}
else
{
CheckAuth();
}
}
}
catch { }
}
On the page where it throws it there is a ListView with a binding:
public class OrdersViewModel : BaseViewModel
{
private Table oldLoan;
private bool isRefreshing;
private readonly string clientId;
public bool IsRefreshing
{
get
{
return isRefreshing;
}
set
{
isRefreshing = value;
OnPropertyChanged("IsRefreshing");
}
}
public ICommand RefreshCommand { get; set; }
public ObservableCollection<Table> Loans { get; set; }
public void ShowOrHideLoan(Table loan)
{
if (oldLoan == loan)
{
loan.IsExpanded = !loan.IsExpanded;
Reload(loan);
}
else
{
if (oldLoan != null)
{
oldLoan.IsExpanded = false;
Reload(oldLoan);
}
loan.IsExpanded = true;
Reload(loan);
}
oldLoan = loan;
}
private void Reload(Table loan)
{
var index = Loans.IndexOf(loan);
Loans.Remove(loan);
Loans.Insert(index, loan);
}
public async Task LoadDataAsync()
{
IsRefreshing = true;
Loans.Clear();
try
{
var loans = await ConnectAPI.GetOrdersAsync(clientId);
await Task.Delay(1000);
foreach (var item in loans)
{
Loans.Add(item);
}
}
catch (Exception exc)
{
Console.WriteLine(exc.Message);
}
finally
{
oldLoan = null;
IsRefreshing = false;
}
}
public OrdersViewModel(string clientId)
{
IsRefreshing = false;
this.clientId = clientId;
Loans = new ObservableCollection<Table>();
RefreshCommand = new Command(async () =>
{
await LoadDataAsync();
});
Task.Run(async () => await LoadDataAsync());
}
}
That is, whenever the [ForceDarkHelper] updateByCheckExcludeList: pkg: com.companyname.manimobile activity: crc64d14753dcc52b83b4 event appears.MainActivity#a894c70
Throws it to the print page...
and if you stay on this page, it is updated after a while.
MIUI 12 has made an intelligent dark theme. The system itself repaints the applications if they do not support the dark theme. Apparently this service is ForceDarkHelper. And ExcludeList is in the settings a list of applications that cannot be repainted

Pattern to authenticate the user in SceneDelegate.ContinueUserActivity?

I'm using Deeplinking on Xamarin.iOS.
I can't find a pattern to handle the link if the user has disconnected from my app.
In this case I need to redirect to the login page while waiting for the login to complete before leaving SceneDelegate.ContinueUserActivity, right?
What I've tried so far is to set a Nito.AsyncEx.AsyncAutoResetEvent in my login ViewController, then wait for it with the help of AsyncHelpers. But the login UI is frozen.
[Export("scene:continueUserActivity:")]
public void ContinueUserActivity(UIScene scene, NSUserActivity userActivity)
{
....
if (notAuthenticated)
{
// The user is not authenticated
// Redirect to the Login Page
LoginViewController lvc = new LoginViewController(true);
if (Window.RootViewController == null)
{
var navController = new UINavigationController(lvc);
Window.RootViewController = navController;
}
else
{
UINavigationController rootViewController = ((UINavigationController)Window.RootViewController);
rootViewController.PopToRootViewController(true);
rootViewController.PushViewController(lvc, true);
}
Window.MakeKeyAndVisible();
AsyncHelpers.RunSync(async() => await lvc.LoginFinishedOrCancelled.WaitAsync());
...
}
}
As far as I know there is no way to make ContinueUserActivity async aware.
You could invoke the line AsyncHelpers.RunSync(async() => await lvc.LoginFinishedOrCancelled.WaitAsync()); in your login page instead of in SceneDelegate .
public bool isNeedLoad {get;set;}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
if(isNeedLoad)
{
// ...AsyncHelpers.RunSync(async() => await lvc.LoginFinishedOrCancelled.WaitAsync());
isNeedLoad = false ;
}
}
LoginViewController lvc = new LoginViewController(true){isNeedLoad = true};
if (Window.RootViewController == null)
{
var navController = new UINavigationController(lvc);
Window.RootViewController = navController;
Window.MakeKeyAndVisible();
}
else
{
UINavigationController rootViewController = ((UINavigationController)Window.RootViewController);
rootViewController.PopToRootViewController(true);
rootViewController.PushViewController(lvc, true);
}

Login Screen before Master Detail View in Xamarin

I am trying to write a xamarin app that will display a login page before a master detail page but I am running into issues.
Right now I have my app.xaml calling an appbootstrapper as follows:
public App()
{
this.InitializeComponent();
RxApp.SuspensionHost.CreateNewAppState = () => new AppBootstrapper();
RxApp.SuspensionHost.SetupDefaultSuspendResume();
this.MainPage = RxApp.SuspensionHost
.GetAppState<AppBootstrapper>()
.CreateMainPage();
}
/// <summary>Gets the Router associated with this Screen.</summary>
public RoutingState Router { get; } = new RoutingState();
With the app bootstrapper as follows:
public class AppBootstrapper : ReactiveObject, IScreen
{
public AppBootstrapper(IMutableDependencyResolver dependencyResolver = null)
{
SetupLogging();
this.RegisterParts(dependencyResolver ?? Locator.CurrentMutable);
this.Router.Navigate.Execute(new LoginPageViewModel(this));
}
/// <summary>Gets the Router associated with this Screen.</summary>
public RoutingState Router { get; } = new RoutingState();
public Page CreateMainPage()
{
return new RoutedViewHost();
}
private static void SetupLogging()
{
var logger = new Logger { Level = LogLevel.Debug };
Locator.CurrentMutable.RegisterConstant(logger, typeof(ILogger));
}
private void RegisterParts(IMutableDependencyResolver dependencyResolver)
{
dependencyResolver.RegisterConstant(this, typeof(IScreen));
dependencyResolver.Register(() => new LoginPage(), typeof(IViewFor<LoginPageViewModel>));
dependencyResolver.RegisterConstant(new LoginService(), typeof(ILoginService));
}
}
This gets me to my login screen no problem, and I can perform my login operation. Then, once login is successful, I try to navigate to the master detail page, but this is where I run into issues.
public LoginPageViewModel(IScreen screen)
{
this.loginService = Locator.Current.GetService<ILoginService>();
this.HostScreen = screen ?? Locator.Current.GetService<IScreen>();
this.PrepareObservables();
}
........................................................
private void PrepareObservables()
{
...
this.LoginCommand = ReactiveCommand.CreateFromTask(
async execute =>
{
var loginSuccessful = await this.loginService.Login(this.Username, this.Password);
if (loginSuccessful)
{
this.HostScreen.Router.NavigateBack.Execute().Subscribe();
}
}, canExecuteLogin);
...
You can see that my login command is trying to perform a navigate and reset to go to the Main Page (which is my master detail page). This is not working and is resulting in an unhandled exception stating:
An object implementing IHandleObservableErrors has errored, thereby breaking its observable pipeline. To prevent this, ...>
Does anyone know what to do here? I need a good pattern for handling the use case of Login -> Master Detail Page in Xamarin Forms using ReactiveUI. Thanks.
this.LoginCommand = ReactiveCommand.CreateFromTask(
async execute =>
{
var loginSuccessful = await this.loginService.Login(this.Username, this.Password);
if (loginSuccessful)
{
this.HostScreen.Router.NavigateBack.Execute().Subscribe();
}
}, canExecuteLogin);
The above code is navigating back on successful login. I think you mean to use Router.NavigateAndReset.Execute(new MainPageViewModel()).Subscribe();

How to Implement an iOS Overlay in Shared Code Using the Media Plugin for Xamarin.Forms?

I am trying to implement the overlay option for the media plugin in Xamarin, found here: https://github.com/jamesmontemagno/MediaPlugin
I understand how to use the feature in the iOS code, but am having trouble implementing it in the shared code.
Would really appreciate it if anyone knows what is wrong with my code, or if there is a working example of using the overlay feature in shared code.
I have setup an interface to handle the overlay for the iOS code, and have passed the function via a dependency service. It seems that the code block inside of func is skipping over when I set breakpoints and step into the function. The camera works but the overlay does not appear when using the camera.
IPhotoOverlay:
namespace Camera
{
public interface IPhotoOverlay
{
object GetImageOverlay();
}
}
AppDelegate:
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
Xamarin.Forms.DependencyService.Register<IPhotoOverlay, PhotoOverlay_iOS>();
return base.FinishedLaunching(app, options);
}
PhotoOverlay_iOS:
[assembly: Xamarin.Forms.Dependency (typeof (PhotoOverlay_iOS))]
namespace Camera.iOS
{
public class PhotoOverlay_iOS: IPhotoOverlay
{
public PhotoOverlay_iOS ()
{
}
public object GetImageOverlay()
{
Func<object> func = () =>
{
var imageView = new UIImageView(UIImage.FromBundle("face-template.png"));
imageView.ContentMode = UIViewContentMode.ScaleAspectFit;
var screen = UIScreen.MainScreen.Bounds;
imageView.Frame = screen;
return imageView;
};
//Func<object> func = CreateOverlay;
return func;
}
}
}
Shared Code:
var photo = await Plugin.Media.CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions()
{
OverlayViewProvider = DependencyService.Get<IPhotoOverlay>().GetImageOverlay,
DefaultCamera = Plugin.Media.Abstractions.CameraDevice.Front
});
Solution:
You can refer the following code .
in Forms
async void OpenCameraAsync()
{
Func<object> func = () =>
{
var obj = DependencyService.Get<IPhotoOverlay>().GetImageOverlay();
return obj;
};
var photo = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions()
{
OverlayViewProvider =func,
DefaultCamera = Plugin.Media.Abstractions.CameraDevice.Front,
});
}
in iOS project
public object GetImageOverlay()
{
var imageView = new UIImageView(UIImage.FromBundle("yourimagename.png"));
imageView.ContentMode = UIViewContentMode.ScaleAspectFit;
var screen = UIScreen.MainScreen.Bounds;
imageView.Frame = screen;
return imageView;
}

What causes this AndroidRuntimeException?

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;
}
}
}

Categories