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

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.

Related

Unable to read CefSharp Chromium console message

I have a WinForm application that using cefsharp web browser. From what I learnt about cefsharp, the events MouseDown, Click cannot trigger in WinForm. So I decided to use javascript to send a message to the console, and trigger ChromiumWebBrowser.ConsoleMessage event instead. But that did not work well. Did I mess up the javascript code or execute the code wrongly?
Here are the codes, please noted that UI.cs is a Form object. A panel object pn_Browser already created in the design view. What I tried to do is, click any element and send "success" to the console. Then the Background show the received message if the message in console.log is "success".
Background.cs
public ChromiumWebBrowser browser;
public Background()
{
browser = new ChromiumWebBrowser("https://www.google.com");
browser.ConsoleMessage += browser_ConsoleMessage;
browser.FrameLoadEnd += browser_FrameLoadEnd;
}
public void browser_ConsoleMessage(object sender, ConsoleMessageEventArgs e)
{
if (e.Message == "success")
{
System.Windows.Forms.MessageBox.Show("Received message.");
}
}
public void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e)
{
Chromium.ExecuteScriptAsync("document.addEventListener(\"click\", function() {alert('Detect a click.');});");
}
UI.cs
public UI()
{
InitializeComponent();
Background background = new Background();
pn_Browser.Controls.Add(background.browser);
background.browser.JavascriptMessageReceived += browser_JavascriptMessageReceived;
background.browser.ExecuteScriptAsyncWhenPageLoaded(
"function test() {console.log(\"success\")}" +
"document.addEventListener(\"click\", test);"
);
}
delegate void JavascriptReceivedCallback(object sender, JavascriptMessageReceivedEventArgs e);
private void browser_JavascriptMessageReceived(object sender, JavascriptMessageReceivedEventArgs e)
{
if (InvokeRequired)
{
JavascriptReceivedCallback callback = new JavascriptReceivedCallback(Browser_JavascriptMessageReceived);
Invoke(callback, new object[] { sender, e };
}
else
{
MessageBox.Show(e.Message.ToString());
}
}

How to remove the header in Android WebView? (While Loading)

I want to remove the header of the website in android 'WebView'. With the Code that I have, it works. But the problem is that the 'WebView' is removing the header after the page loaded completely. I want to remove it, while it is loading.
Thats the code snippet:
webView.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url)
{
webView.loadUrl("javascript:(function() { " +
"var head = document.getElementsByTagName('header')[0];"
+ "head.parentNode.removeChild(head);" +
"})()");
webView.loadUrl("javascript:(function() { " +
"var head = document.getElementsByTagName('footer')[0];"
+ "head.parentNode.removeChild(head);" +
"})()");
}
});
ANDROID WebView-Remove header or any tag from Webpage in Android
Let's understand how's it possible, in my case I had loaded url in webview and look like below screenshot,
Now from the above view if we want to remove the header and menu
portion of the Webpage. We need to use JSOUP lib with the help of
which you can remove the unnecessary portion of webpage.
STEPS
Add jsoup lib in your app level build gradle
compile 'org.jsoup:jsoup:1.10.3'
Now In activity we will use Async task as we parse / remove some html tags that leads to network operations which cannot be done directly on UI Thread. Async Task allows you to perform background operations and publish results on the UI thread. It has three methods commonly used.
onPreExecute():-Is used to display progress bar
doInBackground():-This step is used to perform background computation that can take a long time. You cannot update any UI in this method.
onPostExecute():-This is invoked after completion of background task and it will update the UI.
I have defined WebView in xml like following way,
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/webview"/>
And in MainActivity.java, I had wrote JSOUP code for remove the unnecessary portion of webpage
public class MainActivity extends AppCompatActivity {
WebView webview;
String url="http://pixelay.com/";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webview= (WebView) findViewById(R.id.webview);
new MyAsynTask().execute();
}
private class MyAsynTask extends AsyncTask<Void, Void, Document> {
#Override
protected Document doInBackground(Void... voids) {
Document document = null;
try {
document= Jsoup.connect(url).get();
document.getElementsByClass("main-navigation").remove();
document.getElementsByClass("custom-header-image").remove();
} catch (IOException e) {
e.printStackTrace();
}
return document;
}
#Override
protected void onPostExecute(Document document) {
super.onPostExecute(document);
webview.loadDataWithBaseURL(url,document.toString(),"text/html","utf-8","");
webview.getSettings().setCacheMode( WebSettings.LOAD_CACHE_ELSE_NETWORK );
webview.setWebViewClient(new WebViewClient(){
#Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
view.loadUrl(url);
return super.shouldOverrideUrlLoading(view, request);
}
});
}
}
}
Now, it's time to see the result,
Easiest way is to inject Javascript in onLoadResource() method. Put it inside try-catch block since WebView will not know about the element before it has been loaded:
webView.setWebChromeClient(new WebChromeClient() {
...
#Override
public void onLoadResource(WebView view, String url) {
try {
webView.loadUrl("javascript:(window.onload = function() { " +
"(elem1 = document.getElementById('id1')); elem.parentNode.removeChild(elem1); " +
"(elem2 = document.getElementById('id2')); elem2.parentNode.removeChild(elem2); " +
"})()");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class YourwebView extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
view.loadUrl(request.getUrl());
return true;
}
#Override
public void onPageFinished(WebView view, String url) {
view.loadUrl("javascript:var footer = document.getElementById(\"footer\"); footer.parentNode.removeChild(footer); var header = document.getElementById(\"header-full\"); header.parentNode.removeChild(header);");
}
}
And also make sure you have enabled javscript
YourwebView myWebClient = new YourwebView();
webview.setWebViewClient(myWebClient);
WebSettings webSettings = webview.getSettings();
webSettings.setJavaScriptEnabled(true);
Use onPageStarted method instead of onPageFinished.
From the Android Docs here
EDIT: Reading the comments on other answers, I see you want the header to show again. Simply implement both onPageStarted and onPageEnded, hiding the header in one and showing it again in the other.

Android Webview doesn't load HTML sometimes

I am trying to load local HTML content in my webview. But sometimes it failed to load the content instead shows blank screen. It happens ones in every 5 times of loading.
NOTE My HTML content, I am trying to load is Official 2048 Source code.
Below is my Activity source code
public class GameActivity extends AppCompatActivity {
private WebView mWebView;
#SuppressWarnings("ConstantConditions")
#SuppressLint({ "SetJavaScriptEnabled", "NewApi", "ShowToast"})
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
setContentView(R.layout.activity_game);
Toolbar toolbar = (Toolbar) findViewById(R.id.game_toolbar);
if (toolbar != null) {
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
// Load webview with game
mWebView = (WebView) findViewById(R.id.mainWebView);
WebSettings settings = mWebView.getSettings();
String packageName = getPackageName();
settings.setJavaScriptEnabled(true);
settings.setDomStorageEnabled(true);
settings.setDatabaseEnabled(true);
settings.setRenderPriority(WebSettings.RenderPriority.HIGH);
if (Build.VERSION.SDK_INT >= 19) {
// chromium, enable hardware acceleration
mWebView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
} else {
// older android version, disable hardware acceleration
mWebView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
// Since the database path is automatically handled by Chromium Webkit,
// we should not mention the db path for greater than KitKat version
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
settings.setDatabasePath("/data/data/" + packageName + "/databases");
}
mWebView.addJavascriptInterface(new WebInterface2048(this), "Android");
// If there is a previous instance restore it in the webview
if (savedInstanceState != null) {
mWebView.restoreState(savedInstanceState);
} else {
mWebView.loadUrl("file:///android_asset/2048/index.html");
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
break;
}
return super.onOptionsItemSelected(item);
}
public class WebInterface2048 {
Context mContext;
public WebInterface2048(Context context) {
mContext = context;
}
#JavascriptInterface
public void showToast(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
}
}
}
Till now I have tried the below things to solve the problem:
Put Hardware Acceleration on the of Manifest file.
Enabled and Disabled Hardware Acceleration inside Activity.
Reloaded the same URL inside shouldOverrideUrlLoading
Tried to load the URL inside onStart() instead of onCreate()
But nothing seems to be working for me.
My Logs:
D/OpenGLRenderer: endAllActiveAnimators on 0xb7d7e248 (RippleDrawable) with handle 0xb76b0cf0
I/AppCompatViewInflater: app:theme is now deprecated. Please move to using android:theme instead.
D/cr_Ime: [InputMethodManagerWrapper.java:30] Constructor
W/cr_AwContents: onDetachedFromWindow called when already detached. Ignoring
D/cr_Ime: [InputMethodManagerWrapper.java:59] isActive: false
I/cr_Ime: ImeThread is not enabled.
W/cr_BindingManager: Cannot call determinedVisibility() - never saw a connection for the pid: 18631
D/cr_Ime: [InputMethodManagerWrapper.java:59] isActive: true
D/cr_Ime: [InputMethodManagerWrapper.java:68] hideSoftInputFromWindow
D/OpenGLRenderer: endAllActiveAnimators on 0xb7a893f8 (RippleDrawable) with handle 0xb7ec8810
I/AppCompatViewInflater: app:theme is now deprecated. Please move to using android:theme instead.
D/cr_Ime: [InputMethodManagerWrapper.java:30] Constructor
W/cr_AwContents: onDetachedFromWindow called when already detached. Ignoring
D/cr_Ime: [InputMethodManagerWrapper.java:59] isActive: false
I/cr_Ime: ImeThread is not enabled.
W/cr_BindingManager: Cannot call determinedVisibility() - never saw a connection for the pid: 18631
D/cr_Ime: [InputMethodManagerWrapper.java:59] isActive: true
D/cr_Ime: [InputMethodManagerWrapper.java:68] hideSoftInputFromWindow
Additional Info: My Device is Moto G3 (Android 6.0.1)
Never understood why this is happening. But as a workaround, I came out of this problem using below code:
mWebView.postDelayed(new Runnable() {
#Override
public void run() {
mWebView.loadUrl("file:///android_asset/2048/index.html");
}
}, 500);
Instead of loading the URL in main thread, if I am running in a different thread, it worked for me.
The above work around can be helpful for those, who are facing problem, where their webview shows blank when loads first time and on reloading, it loads the content.
May be its not a prob=per solution, but it worked. If anybody is finding a better solution, please do post.
I solved the issue by manual adding WebView into the layout within onCreate method:
LinearLayout ll = findViewById(R.id.content);
WebView help = new WebView(this);
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
help.setLayoutParams(lp);
ll.addView(help);
Then load html as usual:
try {
String base64 = Base64.encodeToString(sampleHtml.getBytes("UTF-8"), Base64.DEFAULT);
help.loadData(base64, "text/html; charset=utf-8", "base64");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (Exception ex){
ex.printStackTrace();
}
Try this isolate from the rest of the code. Put it in oncreate by itself to test. saveinstancestate is not always null so it doesn't load.
mWebView.loadUrl("file:///android_asset/2048/index.html");

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:)

JavaScript doesn't work in Android WebView

I want to load a url by webView.
The url is http://wapp.baidu.com/f?kw=%BB%F0%BC%FD ,this page can work correctly on system default browser, but in my webView, some JavaScript not working.
JavaScript is enabled, and I dont know what's wrong with it.
I would appreciate any help.
private void initUI() {
webView = (WebView) findViewById(R.id.web_view);
webView.getSettings().setJavaScriptEnabled(true);
final NavigationBar navigationBar = (NavigationBar) findViewById(R.id.navigationbar);
navigationBar.refreshUI();
navigationBar.rightButton.setImageResource(R.drawable.refresh);
navigationBar.rightButton.setVisibility(View.VISIBLE);
navigationBar.rightButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
refresh();
}
});
navigationBar.leftButton.setImageResource(R.drawable.back);
navigationBar.leftButton.setOnClickListener(new OnClickListener() {
public void onClick(View arg0) {
webView.goBack();
}
});
webView.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
public void onPageFinished(WebView view, String url) {
navigationBar.leftButton.setVisibility(webView.canGoBack() ? View.VISIBLE
: View.INVISIBLE);
super.onPageFinished(view, url);
}
});
refresh();
}
update: the problem is solved
just add following code:
webView.getSettings().setDomStorageEnabled(true);
If you're using proguard, it stops javascript running. For instance, in my proguard-project.txt file I have the following:
if your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
I think there is some issue with javascript file caching/refreshing. I changed the filename of js & it starts working fine.

Categories