WebView(Javascript) -> Native Android API

Image credit: MingMing

WebView(Javascript) -> Native Android API

For a few weeks towards the end of last year I was developing an Android application. During that time I discovered a few new (to me) things about the Android platform. One in particular that caught my attention was having Javascript code running locally on the device call a native Android function.

Before getting into the technical details, let me first talk about a situation where this piece of functionality would be useful. Lets say (for arguments sake), that you have to write an Android application that requires advanced and very polished charts. What are you options? You can try to find a native Android implementation that meets the requirement, but I must admit, I have been there and done that, and couldn’t find any particularly good libraries. Yes there are some out there, but they didn’t have the polish I was looking for (please comment if you know of good ones).

If there aren’t any good Android libraries available what can you do? From personal experience I know there are a lot of good Javascript charting libraries out there. How can this help when developing an Android application not a web application? You can host the charting library on a server somewhere and reference them from an Android WebView? From my experience, this solution is not optimal because of slow performance. Even thought it was too slow, it still looked much better than any of the native libraries available.

Is there way to get the Javascript code to run faster perhaps by taking advantage of the beefy hardware most Android devices run on? Turns out it is quite easy to run the Javascript libraries directly on the device. After moving the Javascript code from the server to the device the performance was greatly improved. The charts rendered fast and were very responsive to the touch.

Running Javascript on the device instead of the Server is fast, but it creates a different sort of problem, namely, now you have a view (Javascript Chart) running inside another view (WebView), how does the Javascript library get its data? The obvious answer is to have the Javascript code call some (REST) service via HTTP. For arguments sake, lets say, this would not work due to the fact that the data is only available via a proprietary Java wrapped protocol. Is there a way for the Javascript code to make a Java call? There is and that is what the rest of this post is going to be about.

For simplicity, I am going to abstract away the charts and data and replace them with a simple requirement, namely, have a WebView render the underlyingAndroid SDK version.

Note: For the purposes of this post, and because I like it, I am going to use Scala as the programming language. As always, you can find all the code on Github.

The goal of this demo is to show how you can call Java from Javascript running in an Android WebView, thus we need to create a WebView, populate it with a simple html file and then enable Javascript.

MainActivity.scala - setting up basic view

  // Step 1: Create WebView
  val webView: WebView = new WebView(this)

  // Step 2: Load page from assets
  webView loadUrl ("file:///android_asset/index.html")

  // Step 3: Enable Javascript
  webView.getSettings setJavaScriptEnabled(true)

So far, so easy. Next, we need to create a simple Scala function in our MainActivity to expose for Javascript to call. We said we wanted our view to expose the underlying Android sdk version, so lets create a function called sdkVersion()

MainActivity.scala - function to expose SDK version

  object jsFun {
      def sdkVersion() = android.os.Build.VERSION.SDK
  }

Next we need to make this function available to Javascript by adding it to the DOM.

MainActivity.scala - Adding a function to the DOM

  // Add the above function to the DOM as "Android"
  // The function can now be invoked from Javascript with the following: Android.sdkVersion()
  webView addJavascriptInterface(jsFun, "Android")

Optional step: When developing Javascript applications it is sometimes helpfull to be able to debug something to the browser console. Believe it or not, it is quite easy to implement the browser’s console implementation with an Android one, and instead of logging the message to the browser console, it will send them to the Android Logcat system.

MainActivity.scala - Implement Javascript console.log

   // provide the WebView with a console.log implementation
   webView setWebChromeClient new WebChromeClient {
      override def onConsoleMessage(consoleMessage: ConsoleMessage): Boolean = {
        val msg = new StringBuilder(consoleMessage
          .messageLevel.name).append('\t')
          .append(consoleMessage.message).append('\t')
          .append(consoleMessage.sourceId).append(" (")
          .append(consoleMessage.lineNumber).append(")\n")
        if (consoleMessage.messageLevel == ConsoleMessage.MessageLevel.ERROR)
          Log.e("JavascriptExmple", msg.toString())
        else
          Log.d("JavascriptExmple", msg.toString())
        true
      }
    }

Finally we need to create our html/javascript view.

Lets create a file called index.html and place it under src/main/assets and add at least the following code to it:

index.html - default page that calls our Android.sdkVersion() function via Javascript

<div>Click <a href="#">here</a> to invoke an Android function to
   find out the Android SDK version used to build this App.
</div>
<div class="sdk">SDK:</div>
</body>
<script>
    console.log("This message should appear as a debug message in Logcat.");
    $("a").click(function () {
        $('.sdk').append(Android.sdkVersion());
    });
</script>

As you can see here, we are invoking the Android (Scala) method called sdkVersion() and appending the results of the call to a div using JQuery.

Thats all there is to it, now you know how to invoke an Android function from Javascript running in an Android WebView.

comments powered by Disqus