Xamarin Android two way communication between Javascript and C# - xamarin

I am developing an app using Xamarin Android which has a WebView displaying a web page. I want to implement a two way communication between Javascript from WebView to c#. I could call C# from Javascript using this link. However i couldn't find a way to send data back from C# to Javascript.
Is there a way to send data back and forth in this approach. I thought writing a callback in Javascript would work but how to fire it from C# code.
Now, My problem is how to call WebView from a javascript interface class. I have a Javascript interface class as mentioned https://developer.xamarin.com/recipes/android/controls/webview/call_csharp_from_javascript/
namespace ScannerAndroid
{
public class JSInterface: Java.Lang.Object
{
Context context;
WebView webView;
public JSInterface (Context context, WebView webView1)
{
this.context = context;
this.webView = webView1;
}
[Export]
[JavascriptInterface]
public void ShowToast()
{
Toast.MakeText (context, "Hello from C#", ToastLength.Short).Show ();
this.webView.LoadUrl ("javascript:callback('Hello from Android Native');");
}
}
}
The code throws an exception at LoadUrl line. java.lang.Throwable: A WebView method was called on thread 'Thread-891'. All WebView methods must be called on the same thread. (Expected Looper Looper (main, tid 1) {42ce58a0} called on null, FYI main Looper is Looper (main, tid 1) {42ce58a0})
Now i am struggling how to refer the WebView from this Java script interface class

Yes. That is possible. If you are targeting KitKat or higher you can use:
webView.EvaluateJavascript("enable();", null);
Where in this case enable(); is a JS function.
If you are targeting lower API levels you can use LoadUrl();:
webView.LoadUrl("javascript:enable();");
EDIT:
The error you get where it complains on LoadUrl is because it for some reason happens on a non-UI thread.
Since you have already passed on the Context into your JavascriptInterface class, then you can simply wrap the contents of ShowToast in:
context.RunOnUiThread(() => {
// stuff here
});
Just change signature from Context to Activity and it should help you marshal you back on UI thread.

Related

How to implement an Alert box in xamarin forms android

I implemented a Dependency Service to display alert box in my xamarin forms app.My app crashes when I call the alert box in android.
Here is My code
Android.App.AlertDialog.Builder _dialog = new AlertDialog.Builder(Android.App.Application.Context);
AlertDialog _alertDialog = _dialog.Create();
_alertDialog.SetTitle("Unauthorized");
_alertDialog.SetMessage("Please login again to continue using the App);
_alertDialog.SetButton("OK", (c, ev) => { CloseApp(); });
_alertDialog.Show();
It throws an exception:-Unable to add window -- token null is not for an application in android.
How to fix this Please help me
Unable to add window -- token null is not valid; is your activity running?
You are using a Application context and you need to use an Activity based one.
So you need the current Activity's context within your Forms' dependancy class which you can obtain that via multiple methods; A static var on the MainActivity, using the "CurrentActivityPlugin", etc...
As a quick fix, add a static Context variable to your MainActivity class and set it in the OnResume override.
public static Context context;
protected override void OnResume()
{
context = this;
base.OnResume();
}
Then change your context to that static one:
Android.App.AlertDialog.Builder _dialog = new Android.App.AlertDialog.Builder(MainActivity.context);

Xamarin.Forms (UWP) - How Can I Get a WebView's DOM as an HTML String?

In a Xamarin.Forms (UWP) project, I have a WebView control whose Source is created with an HTML string, like this:
var webview = new Xamarin.Forms.WebView
{
Source = new HtmlWebViewSource
{
Html = "<html>....</html>"
}
};
The HTML contains JavaScript that dynamically generates HTML inside the <body>. This renders perfectly on the screen. That means the WebView understands the DOM that is being created with the JavaScript. Great.
But now I need to parse through some of the generated HTML, but all I can seem to access is the original HTML string that I passed in as the Source, and not the final generated DOM.
Is there a way to convert the DOM generated by the JavaScript and understood by the WebView into a string so that I can parse (using a library like HTML Agility Pack or AngleSharp) and pull out some segments of the HTML? This can be in Xamarin.Forms or UWP (the platform I'm targeting).
NOTE: In full disclosure (in case it helps, and to avoid accusations of this being an XY problem), I am ultimately trying to solve the problem of printing a WebView with multiple pages on UWP - research on this has been met with very sparse information. I have a solution that works for HTML that is not dynamically generated with JavaScript - basically I'm pulling out parts of the HTML that represent printable pages, and I'm adding those as separate pages for print and print preview. But as mentioned earlier, I can't seem to parse through dynamically generated content.
My first thought was to use the Eval method built into Xamarin.Forms, but then I found out this is method does not return anything so it is suitable only for app-to-webview communication.
So far the easiest way to implement this is using a custom version of the WebView control:
public class ExtendedWebView : WebView
{
public delegate Task<string> GetHtmlRequestedHandler();
public event GetHtmlRequestedHandler GetHtmlRequested;
public async Task<string> GetHtmlAsync()
{
var handler = GetHtmlRequested;
if (handler != null)
{
return await handler.Invoke();
}
return null;
}
}
Now in UWP platform project create a custom renderer:
[assembly: ExportRenderer(typeof(ExtendedWebView), typeof(ExtendedWebViewRenderer))]
namespace App.UWP
{
public class ExtendedWebViewRenderer : WebViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
var ew = (e.OldElement as ExtendedWebView);
ew.GetHtmlRequested -= Ew_GetHtmlRequested;
}
if (e.NewElement != null)
{
var ew = (e.NewElement as ExtendedWebView);
ew.GetHtmlRequested += Ew_GetHtmlRequested;
}
}
private async Task<string> Ew_GetHtmlRequested()
{
return await Control.InvokeScriptAsync("eval", new string[] { "document.documentElement.outerHTML;" });
}
}
}
The trick is that we are calling the JavaScript eval function that will return the HTML itself from the web view.
You just need to replace the WebView in XAML with our ExtendedWebView and call its GetHtmlAsync method whenever needed.
The only thing I dislike about this solution is that the event has Task<string> return type, which is weird. Actually already having a return type on event is unusual. A better solution would be to put a property in custom EventArgs that the native control would set with result of the operation, but because the InvokeScriptAsync method is asynchronous (and the non-asynchronous InvokeScript method is obsolete and should no longer be used) we would have to implement a custom Task that would complete when the property is set. Such approach is utilized in UWP with some events, they are using a "deferral" which says the caller that the event will finish only after some asynchronous operation finishes. I will try to look for some authoritative answer on how calling a native asynchronous operation should be implemented in case of custom views :-) .

Why use Device.BeginInvokeOnMainThread() in a Xamarin application?

My code looks like this:
public void Init() {
if (AS.pti == PTI.UserInput)
{
AS.runCardTimer = false;
}
else
{
AS.runCardTimer = true;
Device.BeginInvokeOnMainThread(() => showCards().ContinueWith((arg) => { }));
}
}
The Init method is called from the constructor. Can someone please explain to me why the developer might have added the Device.BeginInvokeOnMainThread() instead of just calling the method showCards?
Also what does the ContinueWith((arg)) do and why would that be included?
The class where this Init() method is might be created on a background thread. I'm assuming showCards() are updating some kind of UI. UI can only be updated on the UI/Main thread. Device.BeginInvokeOnMainThread() ensures that the code inside the lambda is executed on the main thread.
ContinueWith() is a method which can be found on Task. If showCards() returns a task, ContinueWith() makes sure the task will complete before exiting the lambda.
UI actions must be performed on UI thread (different name for main thread). If you try to perform UI changes from non main thread, your application will crash. I think developer wanted to make sure it will work as intended.
The simple answer is: Background thread cannot modify UI elements because most UI operations in iOS and Android are not thread-safe; therefore, you need to invoke UI thread to execute the code that modifies UI such MyLabel.Text="New Text".
The detailed answer can be found in Xamarin document:
For iOS:
IOSPlatformServices.BeginInvokeOnMainThread() Method simply calls NSRunLoop.Main.BeginInvokeOnMainThread
public void BeginInvokeOnMainThread(Action action)
{
NSRunLoop.Main.BeginInvokeOnMainThread(action.Invoke);
}
https://developer.xamarin.com/api/member/Foundation.NSObject.BeginInvokeOnMainThread/p/ObjCRuntime.Selector/Foundation.NSObject/
You use this method from a thread to invoke the code in the specified object that is exposed with the specified selector in the UI thread. This is required for most operations that affect UIKit or AppKit as neither one of those APIs is thread safe.
The code is executed when the main thread goes back to its main loop for processing events.
For Android:
Many People think on Xamarin.Android BeginInvokeOnMainThread() method use Activity.runOnUiThread(), BUT this is NOT the case, and there is a difference between using runOnUiThread() and Handler.Post():
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);//<-- post message delays action until UI thread is scheduled to handle messages
} else {
action.run();//<--action is executed immediately if current running thread is UI thread.
}
}
The actual implementation of Xamarin.Android BeginInvokeOnMainThread() method can be found in AndroidPlatformServices.cs class
public void BeginInvokeOnMainThread(Action action)
{
if (s_handler == null || s_handler.Looper != Looper.MainLooper)
{
s_handler = new Handler(Looper.MainLooper);
}
s_handler.Post(action);
}
https://developer.android.com/reference/android/os/Handler.html#post(java.lang.Runnable)
As you can see, you action code is not executed immediately by Handler.Post(action). It is added to the Looper's message queue, and is handled when the UI thread's scheduled to handle its message.

How to log in to Facebook in Xamarin.Forms

I want to make a Xamarin.Forms project, targeting iOS, Android and Windows Phone.
My app needs to authenticate users using Facebook.
Should I implement login for each platform independently, or use a manual flow?
https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/v2.0
I prefer to have a single implementation of the login flow, and use it on all platforms.
How can I get a single implementaion of the Facebook login flow?
UPDATE (10/24/17): While this way of doing things was okay a few years ago, I now strongly advocate for using native UI for doing authentication, as opposed to the webview method shown here. Auth0 is a great way to accomplish native UI login for your apps, using a wide variety of identity providers:
https://auth0.com/docs/quickstart/native/xamarin
EDIT: I finally put a sample for this on Gihub
I posted an answer on the Xamarin Forums. I'll repeat it here.
Let's start with the core of the app, the Xamarin.Forms PCL project. Your App class will look something like this:
namespace OAuth2Demo.XForms
{
public class App
{
static NavigationPage _NavPage;
public static Page GetMainPage ()
{
var profilePage = new ProfilePage();
_NavPage = new NavigationPage(profilePage);
return _NavPage;
}
public static bool IsLoggedIn {
get { return !string.IsNullOrWhiteSpace(_Token); }
}
static string _Token;
public static string Token {
get { return _Token; }
}
public static void SaveToken(string token)
{
_Token = token;
}
public static Action SuccessfulLoginAction
{
get {
return new Action (() => {
_NavPage.Navigation.PopModalAsync();
});
}
}
}
}
The first thing to notice is the GetMainPage() method. This tells the app which screen it should load first upon launching.
We also have a simple property and method for storing the Token that is returned from the auth service, as well as a simple IsLoggedIn property.
There's an Action property as well; something I stuck in here in order to have a way for the platform implementations to perform a Xamarin.Forms navigation action. More on this later.
You'll also notice some red in your IDE because we haven't created the ProfilePage class yet. So, let's do that.
Create a very simple ProfilePage class in the Xamarin.Forms PCL project. We're not even going to do anything fancy with it because that will depend on your particular need. For the sake of simplicity in this sample, it will contain a single label:
namespace OAuth2Demo.XForms
{
public class ProfilePage : BaseContentPage
{
public ProfilePage ()
{
Content = new Label () {
Text = "Profile Page",
VerticalOptions = LayoutOptions.CenterAndExpand,
HorizontalOptions = LayoutOptions.CenterAndExpand,
};
}
}
}
Again, you'll probably have some red in your IDE because we seem to be missing the BaseContentPage class. The sole purpose of the BaseContentPage class is to ensure that none of the app's screens can be displayed until the user has logged in. (In this simplified demo, we're just persisting the user info to memory, so you'll need to re-login every time the app is run. In a real-world app, you'd be storing the authenticated user info to the device's keychain, which would eliminate the need to login at each app start.)
Create a BaseContentPage class in the Xamarin.Forms PCL project:
namespace OAuth2Demo.XForms
{
public class BaseContentPage : ContentPage
{
protected override void OnAppearing ()
{
base.OnAppearing ();
if (!App.IsLoggedIn) {
Navigation.PushModalAsync(new LoginPage());
}
}
}
}
There's a few interesting things going on here:
We're overriding the OnAppearing() method, which is similar to the ViewWillAppear method in an iOS UIViewController. You can execute any code here that you'd like to have run immediately before the screen appears.
The only thing we're doing in this method is checking to see if the user is logged in. If they're not, then we perform a modal push to a class called LoginPage. If you're unfamiliar with the concept of a modal, it's simply a view that takes the user out of the normal application flow in order to perform some special task; in our case, to perform a login.
So, let's create the LoginPage class in the Xamarin.Forms PCL project:
namespace OAuth2Demo.XForms
{
public class LoginPage : ContentPage
{
}
}
Wait...why doesn't this class have a body???
Since we're using the Xamatin.Auth component (which does the job of building and presenting a web view that works with the provided OAuth2 info), we actually don't want any kind of implementation in our LoginPage class. I know that seems weird, but bear with me.
The LoginPageRenderer for iOS
Up until this point, we've been working solely in the Xamarin.Forms PCL project. But now we need to provide the platform-specific implementation of our LoginPage in the iOS project. This is where the concept of a Renderer comes in.
In Xamarin.Forms, when you want to provide platform-specific screens and controls (i.e. screens that do not derive their content from the abstract pages in the Xamarin.Forms PCL project), you do so with Renderers.
Create a LoginPageRenderer class in your iOS platform project:
[assembly: ExportRenderer (typeof (LoginPage), typeof (LoginPageRenderer))]
namespace OAuth2Demo.XForms.iOS
{
public class LoginPageRenderer : PageRenderer
{
public override void ViewDidAppear (bool animated)
{
base.ViewDidAppear (animated);
var auth = new OAuth2Authenticator (
clientId: "", // your OAuth2 client id
scope: "", // the scopes for the particular API you're accessing, delimited by "+" symbols
authorizeUrl: new Uri (""), // the auth URL for the service
redirectUrl: new Uri ("")); // the redirect URL for the service
auth.Completed += (sender, eventArgs) => {
// We presented the UI, so it's up to us to dimiss it on iOS.
App.SuccessfulLoginAction.Invoke();
if (eventArgs.IsAuthenticated) {
// Use eventArgs.Account to do wonderful things
App.SaveToken(eventArgs.Account.Properties["access_token"]);
} else {
// The user cancelled
}
};
PresentViewController (auth.GetUI (), true, null);
}
}
}
}
There are important things to note:
The [assembly: ExportRenderer (typeof (LoginPage), typeof (LoginPageRenderer))] line at the top (and importantly before the namespace declaration) is using the Xamarin.Forms DependencyService. It's not the most beautiful thing in the world because it's not IoC/DI, but whatever...it works. This is the mechanism that "maps" our LoginPageRenderer to the LoginPage.
This is the class in which we're actually using the Xamarin.Auth component. That's where the OAuth2Authenticator reference comes from.
Once the login is successful, we fire off a Xamarin.Forms navigation via App.SuccessfulLoginAction.Invoke();. This gets us back to the ProfilePage.
Since we're on iOS, we're doing all of our logic sinde of the ViewDidAppear() method.
The LoginPageRenderer for Android
Create a LoginPageRenderer class in your Android platform project. (Note that class name you're creating is identical to the one in the iOS project, but here in the Android project the PageRenderer inherits from Android classes instead of iOS classes.)
[assembly: ExportRenderer (typeof (LoginPage), typeof (LoginPageRenderer))]
namespace OAuth2Demo.XForms.Android
{
public class LoginPageRenderer : PageRenderer
{
protected override void OnModelChanged (VisualElement oldModel, VisualElement newModel)
{
base.OnModelChanged (oldModel, newModel);
// this is a ViewGroup - so should be able to load an AXML file and FindView<>
var activity = this.Context as Activity;
var auth = new OAuth2Authenticator (
clientId: "", // your OAuth2 client id
scope: "", // the scopes for the particular API you're accessing, delimited by "+" symbols
authorizeUrl: new Uri (""), // the auth URL for the service
redirectUrl: new Uri ("")); // the redirect URL for the service
auth.Completed += (sender, eventArgs) => {
if (eventArgs.IsAuthenticated) {
App.SuccessfulLoginAction.Invoke();
// Use eventArgs.Account to do wonderful things
App.SaveToken(eventArgs.Account.Properties["access_token"]);
} else {
// The user cancelled
}
};
activity.StartActivity (auth.GetUI(activity));
}
}
}
Again, let's take a look at some interesting things:
The [assembly: ExportRenderer (typeof (LoginPage), typeof (LoginPageRenderer))] line at the top (and importantly before the namespace declaration) is using the Xamarin.Forms DependencyService. No difference here from the iOS version of LoginPageRenderer.
Again, this is where we're actually using the Xamarin.Auth component. That's where the OAuth2Authenticator reference comes from.
Just as with the iOS version, once the login is successful, we fire off a Xamarin.Forms navigation via App.SuccessfulLoginAction.Invoke();. This gets us back to the ProfilePage.
Unlike the iOS version, we're doing all of the logic inside of the OnModelChanged() method instead of the ViewDidAppear().
Here it is on iOS:
...and Android:
UPDATE:
I've also provided a detailed sample at my blog: http://www.joesauve.com/using-xamarin-auth-with-xamarin-forms/
You could consume either Xamarin.Social or Xamarin.Auth for that. It allows using the same api whatever the platform is.
As of now, those libs aren't PCL yet, but you still can consume them from a Shared Assets Project, or abstract the API you need in an interface and inject in with DependencyService or any other DI container.
I've created a sample project to show how to create a Facebook login using native Facebook component, not through a webview like the solutions suggested here.
You can check it out in this address:
https://github.com/IdoTene/XamarinFormsNativeFacebook
IOS 8: For those who are using #NovaJoe code and are stuck on view, add the code bellow to workaround:
bool hasShown;
public override void ViewDidAppear(bool animated)
{
if (!hasShown)
{
hasShown = true;
// the rest of #novaJoe code
}
}
Here's a good Xamarin.Forms authentication sample. The documentation in the code is nice. It uses a webview to render the login screen, but you can select what login type you want. It also saves a users token so he doesn't have to keep re-logging in.
https://github.com/rlingineni/Xamarin.Forms_Authentication
Another addition to #NovaJoe's code, on iOS8 with Facebook, you'd need to modify the Renderer class as below to close the View after successful authentication.
auth.Completed += (sender, eventArgs) => {
// We presented the UI, so it's up to us to dimiss it on iOS.
/*Importand to add this line */
DismissViewController (true, null);
/* */
if (eventArgs.IsAuthenticated) {
App.Instance.SuccessfulLoginAction.Invoke ();
// Use eventArgs.Account to do wonderful things
App.Instance.SaveToken (eventArgs.Account.Properties ["access_token"]);
} else {
// The user cancelled
}
};
The correct implementation for the Androids PageRenderer is:
using System;
using Android.App;
using Android.Content;
using OAuth2Demo.XForms.Android;
using Xamarin.Auth;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using XamarinAuth;
[assembly: ExportRenderer(typeof(LoginPage), typeof(LoginPageRenderer))]
namespace OAuth2Demo.XForms.Android
{
public class LoginPageRenderer : PageRenderer
{
public LoginPageRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
// this is a ViewGroup - so should be able to load an AXML file and FindView<>
var activity = this.Context as Activity;
var auth = new OAuth2Authenticator(
clientId: "<Constants.clientId>", // your OAuth2 client id
scope: "<Constants.scope>", // the scopes for the particular API you're accessing, delimited by "+" symbols
authorizeUrl: new Uri("<Constants.authorizeUrl>"), // the auth URL for the service
redirectUrl: new Uri("<Constants.redirectUrl>")); // the redirect URL for the service
auth.Completed += (sender, eventArgs) =>
{
if (eventArgs.IsAuthenticated)
{
App.SuccessfulLoginAction.Invoke();
// Use eventArgs.Account to do wonderful things
App.SaveToken(eventArgs.Account.Properties["access_token"]);
}
else
{
// The user cancelled
}
};
activity.StartActivity(auth.GetUI(activity));
}
}
}

Unit testing a custom control in a windows store project

I want to unit test the custom controls I create for a windows store project. Just simple things like "there is a button when X is true".
However, I can't seem to even instantiate the controls in a testing context. Whenever I try to invoke the constructor, I get an exception related to not being run in the UI context. I've also been unable to create coded UI test projects that target windows store projects.
How do I programmatically instantiate a control to test? How do I create a WinRT UI synchronization context?
How do I programmatically send "user" command events to a control?
How do I programmatically instantiate/teardown the entire application?
I've found a hacky way to make non-interactive parts work: with the function Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync.
Obvious, right? However, this still leaves open the question of how to emulate user actions.
/// Runs an action on the UI thread, and blocks on the result
private static void Ui(Action action) {
Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
CoreDispatcherPriority.Normal,
() => action()
).AsTask().Wait();
}
/// Evaluates a function on the UI thread, and blocks on the result
private static T Ui<T>(Func<T> action) {
var result = default(T);
Ui(() => { result = action(); });
return result;
}
[TestMethod]
public void SliderTest() {
// constructing a Slider control is only allowed on the UI thread, so wrap it in UI
var slider = Ui(() => new Slider());
var expected = 0;
// accessing control properties is only allowed on the UI thread, so same deal
Assert.AreEqual(expected, Ui(() => slider.Value));
}

Resources