Search code examples
javascriptxamarin.iosxamarinhttplistener

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


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