Xamarin iOS communication between Javascript and c# - javascript

We are implementing an app where we have communication between Javascript and c#. Our UIWebView has a button to invoke some native functionality. On a UIWebView i have an handler on ShouldStartLoad.
webView.ShouldStartLoad = myHandler;
bool myHandler (UIWebView webView, NSUrlRequest request, UIWebViewNavigationType navType)
{
}
This gets called everytime page loads. Indeed, i would like to only call it from an event from WebView such as on a button click.
From Javascript i have
window.location.href = "myapp://action?par1=abc&par2=def";
How to call a particular function from custom url?
Calling JavaScript from c#
I am trying to call back JavaScript from c# but it is not calling TestShow() function
wkWebView.EvaluateJavaScript(string.Format("TestShow()"), (r, e) =>
{
Console.WriteLine("In EvaluateJavaScript");
if (e != null) Console.WriteLine(e);
});
JavaScript side i have a Alert but it is not showing that alert
function TestShow()
{
alert("Hello! I am an alert box!!");
}

You can either continue using UIWebView and parse the NSUrlRequest to see if it is the call you're looking for. Then return true/false accordingly.
A better option would be to use WKWebView and create a custom message handler. Something like this:
1) Implement IWKScriptMessageHandler (tested on the default UIView created by Xamarin UIViewController)
public class UniversalView : UIView, IWKScriptMessageHandler
{
public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
{
var msg = message.Body.ToString();
System.Diagnostics.Debug.WriteLine(msg);
}
}
2) Create user controller with a handler for "myapp" (this = the IWKScriptMessageHandler)
var userController = new WKUserContentController();
userController.AddScriptMessageHandler(this, "myapp");
3) Create a config with the controller
var config = new WKWebViewConfiguration
{
UserContentController = userController
};
4) Create the WKWebView with the config
var webView = new WKWebView(new CGRect(10, 100, 500, 500), config);
5) Call "myapp" from your JS code
<html><head><meta charset = "utf-8"/></head><body>
<button onclick="callCsharp()">Click</button>"
<script type="text/javascript">
function callCsharp(){
window.webkit.messageHandlers.myapp.postMessage("action?par1=abc&par2=def");
}</script></body></html>";
EDIT: In regards to evaluating JS from C# you need to be sure the HTML page has finished loading or otherwise the call will result in an error. You can handle navigation events by implementing IWKNavigationDelegate
public class UniversalView : UIView, IWKScriptMessageHandler, IWKNavigationDelegate
{
[Export("webView:didFinishNavigation:")]
public void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
webView.EvaluateJavaScript("callCsharp()", (result, error) =>
{
if (error != null) Console.WriteLine(error);
});
}
Assign it to the WKWebView you created:
var webView = new WKWebView(new CGRect(10, 100, 500, 500), config)
{
WeakNavigationDelegate = this
};

Related

Android[Kotlin] - ERROR TypeError: ["Android"].showToast is not a function

I am loading the webpage in webview using WebChromeClient(). The webpage has a dropdown whenever the user select's item from that dropdown I need to show a toast. For this, I'm following official doc I've implemented the same as the doc says. still, I'm getting the error in the console. "showToast is not a function".
In Fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val webSettings = webView.settings
webSettings.javaScriptEnabled = true
webSettings.domStorageEnabled = true
webSettings.databaseEnabled = true
webView.addJavascriptInterface(WebAppInterface(requireContext()), "Android")
webView.webChromeClient = object : WebChromeClient() {
override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean {
Log.i(TAG, consoleMessage.message())
return true
}
override fun onProgressChanged(view: WebView?, newProgress: Int) {
super.onProgressChanged(view, newProgress)
if (newProgress == 100) {
if (webView != null) {
webView.settings.builtInZoomControls = true
webView.settings.displayZoomControls = false
webView.loadUrl("javascript:loadMobileDashboard($data);")
}
}
}
}
webView.loadUrl(url)
}
WebAppInterface:
class WebAppInterface(private val context: Context) {
private val TAG = WebAppInterface::class.java.simpleName
#JavascriptInterface
fun showToast(toast: String) {
Log.d(TAG, "showToast: $toast")
}
}
I tried several changes and searched about it on the internet didn't work single solution. Please let me know what mistake am making here. Thanks :)
Create an html page in your assets folder, let's say named dropDown.html.
Copy this code into that file
<input type="dropDown" value="Hello" onClick="showToastInWebView('strMsg')" />
<script type="text/javascript">
function showToastInWebView(toast) {
Android.showToast(toast);
}
</script>
Now load url like this
myWebView.loadUrl("file:///android_asset/dropDown.html");
Note: webView does not invoke the JS function you have added a bridge to, You need to use your own webpage(in this case which is dropDown.html) that does indeed invoke the function, either local(our case) or on the web.

Get return value from swift2 IOS function from javascript

I have an IOS App which uses Xwebview which loads pages from a web server. On all buttons (on the html pages) I call a function with javascript to the App function. The problem is I cant get a return value to the javascript.
Javascript:
function getHref(data) {
try { href = jsapi.loadHref ( data );
}
catch(err){ }
}
The jsapi is a class inside the view controller which handles all the javascript calls
IOS App side:
let jsapi = xjsapi
class xjsapi: NSobject
{
func loadHref(href:String)-> String {
some more code
return "some url"
}
}
I have also tried:
#objc func getPlatform(callback: XWVScriptObject) {
var myerror: NSError?
callback.call(arguments: ["{\"code\":1}"], error: &myerror)
}
JavaScript:
window.injectedObj.getPlatform(function(result){ /*your code */});

How to call a JS function by Bean JSF passing parameter as soon as the page loads?

I have to call a function in my JS by passing a vector as a parameter when the page loads.
I can do this using a p: commandButton, where here:
actionListener = "# {routeEnterBean.GetMap ()}"
I run the function in the Bean (getting values in the database) and then, here:
oncomplete = "initMap (xhr , status, args) "
Thus, the JS function is executed
Code Bean:
public void gerarMapa() {
RequestContext context = RequestContext.getCurrentInstance();
context.addCallbackParam("coord", new
org.primefaces.json.JSONArray(coordenadas));
}
Function JS
function initMap(xhr, status, args) {
var qtd_entregas = args.coord.length;
for (var i = 0; i < args.coord.length; i++) {
waypts.push({
location : args.coord[i].latitude + ', ' + args.coord[i].longitude,
stopover : true,
});
}
}
But I'm wanting to do this without having to click the button as soon as I load the page.
I know that it is possible to execute a function using this command:
RequestContext.getCurrentInstance().execute("testeJS();");
But I do not know how to pass a vector as a parameter
You can keep your existing code and do it using Primefaces p:remoteCommand by adding following line on your page
<p:remoteCommand autoRun="true" actionListener="#{routeEnterBean.gerarMapa()}" oncomplete="initMap(xhr, status, args);"/>
Attribute autoRun="true" will force p:remoteCommand to be executed on page load.

Call javascript code from android webview on LongClick

I just want to call the following code of javascript on longclick on webview to disable copying/selection content of web view
var touching = null;
$('selector').each(function() {
this.addEventListener("touchstart", function(e) {
e.preventDefault();
touching = window.setTimeout(longTouch, 500, true);
}, false);
this.addEventListener("touchend", function(e) {
e.preventDefault();
window.clearTimeout(touching);
}, false);
});
function longTouch(e) {
// do something!
}
For that you have to define a class in java for the webInterface to interact with it, say
WebInterface.class
public class WebAppInterface {
Context mContext;
WebAppInterface(Context c) {
mContext = c;
}
#JavascriptInterface
public void yourMethod(String parameter) {
//Do your stuff here
}
}
you have to initialize this interface with your webview as below, considering you named your interface as 'interface' in your code
browser.addJavascriptInterface(new WebAppInterface(this), "interface");
then comes the web part, all you have to do is just define a function there and call your method from android interface and your app shall interact from your webview inside application
Your Web Code
<script type="text/javascript">
function callYourMethod(parameter) {
interface.yourMethod(parameter); //this value goes to android code
}
</script>
For further query and understanding you can visit this link too javascript Interface Android

Monotouch / Mono for Android Javascript to C#

I am prototyping a HTML5 app using the Xamarin framework. I am trying to connect javascript to C# code and vice versa.
What is the best way to do this? So far I have tried creating Mono C# bindings to the Cordova framework, which is not an ideal solution and am looking for something more straightforward that takes advantage of a common .Net centric approach. So I have tried setting up a HttpListener to handle app http requests from javacript calls. This method works well with Monotouch, but doesn't with Mono for Android
Javascript call:
<script type="text/javascript">
function myFunction()
{
var request = new XMLHttpRequest();
request.open('GET','http://127.0.0.1:8001/callnative', false);
request.send();
if(request.status == 200){
alert("Callback!");
}
else{
alert("Error");
}
}
</script>
Here is the C# code in the Activity class:
[Activity (Label = "AnroidHTML5Prototype", MainLauncher = true)]
public class Activity1 : Activity
{
int count = 1;
public HttpListener listener;
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
SetupListener ();
// Set our view from the "main" layout resource
SetContentView (Resource.Layout.Main);
// Get our button from the layout resource,
// and attach an event to it
Button button = FindViewById<Button> (Resource.Id.myButton);
button.Click += delegate {
button.Text = string.Format ("{0} clicks!", count++);
};
LoadWebView ();
}
private void LoadWebView()
{
string lfn = "index.html";
string html = string.Empty;
// context could be ApplicationContext, Activity or
// any other object of type Context
using (var input = this.ApplicationContext.Assets.Open(lfn))
using (StreamReader sr = new System.IO.StreamReader(input))
{
html = sr.ReadToEnd();
}
WebView webView = FindViewById<WebView> (Resource.Id.webView1);
webView.SetWebChromeClient(new WebChromeClient());
webView.Settings.JavaScriptEnabled = true;
webView.LoadData (html, "text/html", null);
webView.Settings.AllowFileAccess = true;
webView.Settings.BlockNetworkLoads = false;
}
private void SetupListener() {
listener = new HttpListener();
listener.Prefixes.Add("http://*:8001/");
listener.Start();
listener.BeginGetContext(new AsyncCallback(HandleRequest), listener);
}
public void HandleRequest (IAsyncResult result)
{
RunOnUiThread(delegate{
//Get the listener context
HttpListenerContext context = listener.EndGetContext(result);
//Start listening for the next request
listener.BeginGetContext(new AsyncCallback(HandleRequest), listener);
string response = String.Empty;
if (context.Request.Url.ToString ().Contains ("callnative")) {
response = "native code reply";
}
byte[] responseBytes = System.Text.Encoding.UTF8.GetBytes(response);
context.Response.ContentType = "text/json";
context.Response.StatusCode = (int)HttpStatusCode.OK;
context.Response.ContentLength64 = responseBytes.Length;
context.Response.OutputStream.Write(responseBytes, 0, responseBytes.Length);
context.Response.OutputStream.Close();
// WebView webView = FindViewById<WebView> (Resource.Id.webView1);
// webView.LoadUrl("javascript:(function() { " +
// "alert('Callback!'); " +
// "})()");
});
}
}
Similar code works fine in Monotouch, but in Mono for Android, I get the following errors:
[Web Console] XMLHttpRequest cannot load http:// 127.0.0.1:8001/callnative. Cannot make any requests from null.:1
[Web Console] Uncaught Error: NETWORK_ERR: XMLHttpRequest Exception 101:1
Which I think has to do with Chrome Browser file permissions, so I set the AllowFileAccess setting on the WebView to true, but still get the same error.
Is there a better way to accomplish a bridge between javascript and C# in Mono, or a way to get the HTTPListener code to work with Android?
This article comes up in the Xamarin documentation search, but is a 404:
http://docs.xamarin.com/recipes/android/controls/webview/call_c#_from_javascript
Thanks
Derrick
I was able to connect JS and C# in Mono for Android by extending the WebViewClient and overriding the ShouldOverrideUrlLoading method:
Activity Class:
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using Android.Webkit;
using System.IO;
using System.Net;
using System.Text;
namespace AnroidHTML5Prototype
{
[Activity (Label = "AnroidHTML5Prototype", MainLauncher = true)]
public class Activity1 : Activity
{
protected override void OnCreate (Bundle bundle)
{
WebView webView = new WebView (this);
webView.Settings.JavaScriptEnabled = true;
webView.SetWebChromeClient(new WebChromeClient());
string html = "<html><head><title></title></head><body><h1 id='button'>Hello Android</h1><h1 id='message' style='color:red'></h1><script>document.getElementById('button').onclick=function(){window.location='/callnative';}; function callback(msg){document.getElementById('message').innerHTML = msg;}</script></body></html>";
webView.LoadData (html, "text/html", null);
webView.SetWebViewClient (new MyWebViewClient(webView));
base.OnCreate (bundle);
SetContentView (webView);
}
}
}
Extended WebViewClient class:
using System;
using Android.Webkit;
using Java.IO;
using Java.Net;
namespace AnroidHTML5Prototype
{
public class MyWebViewClient : WebViewClient
{
WebView webView;
public MyWebViewClient (WebView webView) : base()
{
this.webView = webView;
}
public override bool ShouldOverrideUrlLoading (WebView view, string url)
{
if (url == "/callnative")
{
webView.LoadUrl ("javascript:callback('Hello from Android Native');");
return true;
}
return base.ShouldOverrideUrlLoading(view, url);
}
}
}
In Android the localhost IP (the IP of the computer the emulation is running on) is not 127.0.0.1
it's 10.0.2.2 ref
that may or may not be the source of your error.
to anyone interested - I was able to run initial code sample without
RunOnUiThread(delegate{
Also in my html file I used jQuery AJAX call
<script type="text/javascript" src="../../js/jquery-1.9.1.js"></script>
...
<script type="text/javascript">
$(document).ready(function () {
$('#example-1').click(function () {
$.ajax({
async: true,
url: 'http://localhost:8801/test/url/test.htm'
});
$(this).load('ProgressDialog.htm');
});
});
</script>
You may also note that localhost is used instead of 127.0.0.1 so basically same code works on both Xamarin.iOS and Xamarin.Android .
We did crossplatform desktop HTML app that shares same codebase for Mac and Windows using Mono with WebKit.NET and Monobjc as WebKit webview controls. Completely free toolset btw, including InApps for Mac( yep Miguel, 999$ per platform is a way too much for most guys including myself ;) From work on that app I know that only stuff like
.EvaluateJavaScript(..)
needs to be invoked on main (aka UI) thread (and all the UI stuff of course), so not sure why you used RunOnUiThread in your question's code sample.
Hope that will help someone:)

Categories