Question

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

Was it helpful?

Solution

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

OTHER TIPS

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

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top