Executing a page's JavaScript inside a WebView's JavascriptInterface in Kotlin - javascript

As an example, let's assume for a project, I have a WebView instance I'm using in my main activity loading a local HTML file:
class MainActivity : AppCompatActivity() {
lateinit var myWebView:WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
myWebView = findViewById(R.id.webview)
myWebView.getSettings().setAllowContentAccess(true);
myWebView.getSettings().setAllowFileAccess(true);
myWebView.settings.javaScriptEnabled = true;
myWebView.setWebChromeClient(WebChromeClient());
myWebView.loadUrl("file:///android_asset/websrc/mainPage.html")
myWebView.addJavascriptInterface(WebAppInterface(this), "Android")
}
}
class WebAppInterface(private val mContext: Context) {
#JavascriptInterface fun getStatus() {
MainActivity().myWebView.evaluateJavascript("setStatus('good to go!')", null)
//Or: MainActivity().myWebView.loadURL("javascript:setStatus('good to go!')")
}
}
I have a JavascriptInterface also, where from I would like to invoke Android.getStatus() in my HTML file:
<html>
<head>
...
</head>
<body>
<div id="status"></div>
</body>
<script>
document.addEventListener('DOMContentLoaded', function() {
Android.getStatus()
})
function setStatus(status) {
document.querySelector('#status').innerHTML = status
}
</script>
</html>
So far, I think understand why this wouldn't work. From what I've come to understand from answers like this or this, And especially in the documentation for WebView, I understand that the JavascriptInterface and the WebView are running on different threads, so something like setStatus() wouldn't be defined. So far, I've tried using runOnUiThread and Handler(). See an example for runOnUiThread:
#JavascriptInterface fun getStatus() {
MainActivity().runOnUiThread(
object : Runnable {
override fun run() {
MainActivity().myWebView.evaluateJavascript("setStatus('good to go!')", null)
//Or: MainActivity().myWebView.loadURL("javascript:setStatus('good to go!')")
}
}
)
}
This (and use of Handler), however, gives me: E/AndroidRuntime: FATAL EXCEPTION: JavaBridge and lateinit property myWebView has not been initialized. I really don't understand why. Should mention I'm new to Kotlin and Android.
Any help?

All I needed was to pass myWebView to the JavaScript interface along with this and use Runnable...
Creating the JS interface:
myWebView.addJavascriptInterface(WebAppInterface(this, myWebView), "Android")
And then inside a #JavascriptInterface function:
myWebView.post(Runnable {
myWebView.loadUrl("javascript:customFunction()")
})

Related

JSRuntime not playing sound when initializing Blazor web app

So I have this simple page:
#page "/"
#inject IJSRuntime _jsRuntime
<button #onclick="MakeSound">Make sound</button>
<button #onclick="ShowPopUp">Show pop-up</button>
#code
{
protected override void OnInitialized()
{
_jsRuntime.InvokeVoidAsync("makeSound");
_jsRuntime.InvokeVoidAsync("showPopUp");
}
void MakeSound()
{
_jsRuntime.InvokeVoidAsync("makeSound");
}
void ShowPopUp()
{
_jsRuntime.InvokeVoidAsync("showPopUp");
}
}
in which I have 2 buttons: one for playing a sound and one for showing an alert.
They work fine but when I try to run these actions at initialization, only the alert message shows up and there's no sound. Why?
By the way, this is the JS, though I have a feeling that the problem is not here:
function makeSound() {
var audio = new Audio("sounds/ding.wav");
audio.play();
}
function showPopUp() {
alert("Hello!");
}
Calling JavaScript function in OnInitialized do not work, because DOM is not rendered yet, OnAftrRender is the right event to call JavaScript functions. So you can try like this
protected override void OnAfterRender(bool firstRender)
{
if(firstRender)
{
_jsRuntime.InvokeVoidAsync("makeSound");
_jsRuntime.InvokeVoidAsync("showPopUp");
}
}

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.

Communicate between Javascript and WebView native code in Xamarin.Forms iOS

OK so this works on Android... I'm stuck on iOS.
I have a custom HTML WebView which has a JavaScript event:
const string html= #"<button type=""button"" onClick=CSharp.SendButton(' + buttonCode + ')"">Click here</button>";
On Android, I have a webview custom renderer:
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (Control != null)
{
Control.Settings.JavaScriptEnabled = true;
Control.AddJavascriptInterface(new JSBridge(Forms.Context), "CSharp");
}
base.OnElementPropertyChanged(sender, e);
}
And JSBridge is a bridge between Javascript and C#:
public class JSBridge : Java.Lang.Object
{
Context context;
public JSBridge(Context context)
{
this.context = context;
}
[Export]
[JavascriptInterface]
public void SendButton(string code)
{
// I can pass the buttonCode here
}
}
How can I do something similar on iOS?
Please note: I am aware of the HybridWebView examples on the Microsoft website: I dont want to use a seperate Javascript file, I want to fire the event from my HTML and catch it in my renderer.

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