Using XML and JSON with Android, Part 2: Deliver hybrid Android applications with JSON

Mix JavaScript, JSON, callback functions, Android-SDK Java code for flexible mobile apps

This two-part article series examines techniques for handling two of the most common data formats found on the Internet today, XML and JavaScript Object Notation (JSON), on the Android platform. Part 1 covered the basics of XML and JSON. Part 2 examines the manner in which Webkit-hosted JavaScript code exchanges data with Java™ code in an Android application. This article focuses on JSON, which is demonstrated as the most capable and flexible approach.

Frank Ableson, Entrepreneur, MSI Services, Inc.

W. Frank Ableson is an entrepreneur living in northern New Jersey with his wife Nikki and their children. His professional interests include mobile software and embedded design. He is the author of Unlocking Android (Manning Publications, 2010), and he is the mobile editor for Linux Magazine.



24 August 2010

Also available in Chinese Russian Japanese Vietnamese

To run the example code that accompanies this article you should install the Android SDK version 1.5 or later along with Eclipse. To learn more about setting up your environment please visit the Android Developers website. See Resources for links.

Introduction

It is hard to imagine a technology sector more popular than the mobile phone. Numerous platforms vie for the industry's top marks in terms of sales and mind-share. The devices are advanced engineering specimens, but what really fuels their popularity is the user experience enabled by the vast quantity of applications available for those platforms. In particular, the iPhone and Android platforms are the latest devices dueling for the hearts and wallets of business and consumer clients alike.

Frequently used acronyms

  • API: Application Programming Interface
  • CSS: Cascading Style Sheets
  • HTML: HyperText Markup Language
  • IDE: Integrated development environment
  • SDK: Software Developer Kit
  • UI: User Interface
  • XML: Extensible Markup Language

The majority of applications available to mobile users are written by mobile developers through the core SDKs provided by the respective platform vendors. The popularity of mobile devices coupled with the large number of talented web technology programmers who have earned their stripes across the Web over the past decade has led to a new model of application—the hybrid application, an application that uses both web browser interfaces and native mobile components. Hybrid applications exist for both iPhone and Android, though the focus in this article is on Android hybrid applications and the use of JavaScript and JSON.

The hybrid application is built around the WebKit engine found in Android's WebView control. This is a user interface widget, which exposes the WebKit's capabilities to an Android programmer. The control can be used to render remote web pages within an application, to provide a familiar user interface experience for developers and users alike, and to leverage the powerful and flexible JavaScript environment within a native Android application.

Hybrid applications often leverage the WebView widget to harness the power of WebKit primarily for user interface elements, however hybrid applications are more than simply displaying some HTML in the widget. Hybrid applications are versatile—the breadth of functionality contained in the Android SDK combined with the web technologies of HTML, CSS and JavaScript, makes hybrid applications limited only by your imagination. To put some flesh on the concept of a hybrid application, this article examines a sample application named AndroidJSON, which implements a number of interactions between an Activity, a WebView and the use of JSON to exchange data. This application demonstrates a number of interactions between the Activity and the HTML and JavaScript hosted by the WebView with the primary feature being a JavaScript calculator.

First, you'll look at embedding a WebKit engine directly into an Android application.


A JavaScript calculator embedded into Android

The majority of SDK-based Android applications contains one or more implementations of the Activity class. The Activity class is essentially a screen or page that contains the user interface elements experienced by the application user.

The Activity displays a collection of programmer-defined user interface elements such as buttons, labels, text entry boxes, radio buttons lists, and so on. All the expected items are present in the Android SDK. In addition to these user interface staples, there is a special widget, known simply as the WebView.

The JavaScript calculator demonstrates the symbiotic relationship between the Java environment of the Activity and the JavaScript environment of the WebView. The application reaches beyond simply asking the WebView to display HTML content—it actually wires-up the Java environment to provide functionality to the JavaScript environment and in doing so tightly integrates the two, enabling a unique user experience. Once the two environments are connected, data is exchanged in the form of JSON to deliver various features, all of which is explained thoroughly in this article. Let's begin by looking at how the JavaScript calculator leverages the WebView widget.

Before diving into the specifics of how the application is constructed, take a moment to review the various features of the application. Figure 1 shows the application screen

Figure 1. Demonstrating the JavaScript calculator in action
Screen capture that demonstrates the JavaScript calculator in action

In the sample native Android application, named AndroidJSON, the screen is defined using the Activity component. It contains traditional user interface elements in the top half of the screen such as a TextView (a static label), an EditText (a text box in which the user enters a formula), and three buttons (Simple, Complex, and Make Red). Activity also has a single instance of a WebView control, which displays the bottom half of the screen.

The WebView displays an HTML file (index.html) that is packaged with the Android application (though you might download the file from the Internet as well). This web page contains the heading, some sample text, the results of the calculation, and six buttons to perform various functions (Log Info, Log Error, Dynamic, How Many Calls, History, and Kill This App).

The files of interest in this project are AndroidJSON.java (the Android application code>, index.html (the web page), and main.xml (a UI layout file which you will look at later). See Download for links to these files.

First, examine the function of the three buttons in the Activity:

Simple
The Simple button causes the contents of the EditText to be evaluated as a mathematical expression. Note that the contents of the EditText, or the formula, are passed to the WebView control and evaluated in JavaScript.
Complex
The Complex button sends a JSON object to the WebView for evaluation. This is considered complex due to the fact that the object is subsequently interpreted in JavaScript code and manipulated mathematically. This button alternates between a function to add elements of an array of integers and a function to multiply the same array of integers.
Make Red
This third button is largely here for fun. When selected, this button applies a style to the embedded WebView's content, turning text elements contained within the <body> tag red.

Now examine the function within the index.html file, enabled at run time by the embedded WebView control.

Log Info
This button invokes a callback in the Android application to write an entry to the application log under the Info category.
Error Info
This button invokes a callback in the Android application to write an entry to the application log under the Error category.
Dynamic
This button invokes a callback in the Android application to retrieve a piece of text that represents valid JavaScript code. This code is brought back into the WebView and executed, demonstrating interaction between both sides of the application. Note that this approach is vulnerable to some security exploits due to a blind reliance upon the JavaScript eval function. However, your focus here is on the plumbing, not a complete production-ready application.
How many calls
Each time one of the callback functions is invoked, a counter is incremented. This button simply displays the counter.
History
Each time one of the JavaScript functions is invoked, a string representing the function name is added to a JavaScript array. When the history button is invoked, this array is converted to JSON and passed to the native portion of the Android application. The array is reconstituted as an object in the Java code, and enumerated with each element of the array written to the log.
Kill This App
This button is another one of the just for fun features of this application. This button invokes a callback which terminates the Android activity through a call to finish().

Like many applications under development, this Android application makes use of the Logging capabilities built within Android. Some of the screen captures shared in this article come from the Dalvik Debug Monitor Service (DDMS) view within Eclipse, where the LogCat window is visible. For more information on how to use the Android development tools, please refer to the links in Resources.

With this explanation of the function of the application out of the way, now look at how the user interface is constructed.


Setting up the user interface

Creating the user interface for this application involves all three of the previously introduced files. Begin with the layout file, main.xml, in Listing 1.

Listing 1. main.xml, the user interface layout file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView android:layout_width="fill_parent"  
android:layout_height="wrap_content" android:text="@string/title" />
    <EditText android:id="@+id/formula" android:layout_width="fill_parent" 
android:layout_height="wrap_content" android:text="" android:visible="False" />
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">
        <Button android:text="Simple" android:id="@+id/btnSimple" 
android:layout_width="wrap_content" android:layout_height="wrap_content">
</Button>
        <Button android:text="Complex" android:id="@+id/btnComplex"
 android:layout_width="wrap_content" android:layout_height="wrap_content">
</Button>
        <Button android:text="Make Red" android:id="@+id/btnRed" 
android:layout_width="wrap_content" android:layout_height="wrap_content">
</Button>    
    </LinearLayout>
    <WebView android:layout_width="fill_parent" android:layout_height="fill_parent"
 android:id="@+id/calculator" android:layout_weight="1" />
</LinearLayout>

In Listing 1, the layout contains the various user interface elements. Note that the presence of the android:id attribute enables the application to make reference to a specific widget within the layout. For example, the WebView contains an id of calculator; however, the TextView does not contain an id as its value is not changed throughout the lifetime of the application.

The onCreate() method found in AndroidJSON.java is responsible for inflating the layout as in Listing 2.

Listing 2. Setting up the user interface
public class AndroidJSON extends Activity {
    private final String tag = "AndroidJSON";
    private WebView browser = null;
    private int flipflop = 0;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);


        final EditText formula = (EditText) this.findViewById(R.id.formula);
        final Button btnSimple = (Button) this.findViewById(R.id.btnSimple);
        final Button btnComplex = (Button) this.findViewById(R.id.btnComplex);
        final Button btnRed = (Button) this.findViewById(R.id.btnRed);
    // remaining code removed for brevity - shown in next listings
}

You inflate the layout with a call to setContentView(). Note that you set up the user interface elements by calling the findViewById() method. The R.java file is automatically generated each time the main.xml file is saved. Layout elements containing the android:id attribute become values within the R.id class, as in Listing 3.

Listing 3. R.java
/* AUTO-GENERATED FILE.  DO NOT MODIFY.
 *
 * This class was automatically generated by the
 * aapt tool from the resource data it found.  It
 * should not be modified by hand.
 */

package com.msi.androidjson;

public final class R {
    public static final class attr {
    }
    public static final class drawable {
        public static final int icon=0x7f020000;
    }
    public static final class id {
        public static final int btnComplex=0x7f050002;
        public static final int btnRed=0x7f050003;
        public static final int btnSimple=0x7f050001;
        public static final int calculator=0x7f050004;
        public static final int formula=0x7f050000;
    }
    public static final class layout {
        public static final int main=0x7f030000;
    }
    public static final class string {
        public static final int app_name=0x7f040001;
        public static final int title=0x7f040000;
    }
}

You will revisit the Button setup code later in this article; for now focus on the setup of the WebView control or widget. While the Button and other user interface elements are rather straightforward, the WebView takes a noticeably greater effort. Don't worry though—it is not that difficult, particularly considering the tried and true cut and paste technique often applies! Look at the snippet in Listing 4, which is again taken from the onCreate() method found in AndroidJSON.java.

Listing 4. Setting up the WebView widget
        // connect to our browser so we can manipulate it
        browser = (WebView) findViewById(R.id.calculator);

        // set a webview client to override the default functionality
        browser.setWebViewClient(new wvClient());

        // get settings so we can config our WebView instance
        WebSettings settings = browser.getSettings();

        // JavaScript?  Of course!
        settings.setJavaScriptEnabled(true);

        // clear cache
        browser.clearCache(true);

        // this is necessary for "alert()" to work
        browser.setWebChromeClient(new WebChromeClient());

        // add our custom functionality to the javascript environment
        browser.addJavascriptInterface(new CalculatorHandler(), "calc");

        // uncomment this if you want to use the webview as an invisible calculator!
        //browser.setVisibility(View.INVISIBLE);

        // load a page to get things started
        browser.loadUrl("file:///android_asset/index.html");

        // allows the control to receive focus
        // on some versions of Android the webview doesn't handle input focus properly
        // this seems to make things work with Android 2.1, but not 2.2
       // browser.requestFocusFromTouch();

In Listing 4, note that you wired an Activity scoped variable named browser is to the WebView control. The WebView is a fairly complex class that can be highly customized. For instance, you need to set up a couple of classes to get the expected function associated with a web browser. This is one of those exercises where the programmer must put in a certain minimum amount of effort to get some useful function. However, the sky is the limit in just how far to take this customization. For the purposes of this application, the WebView control is somewhat minimally deployed.

The WebViewClient provides hooks for capturing various events such as page load start and end, form re-submission, keyboard intercepting, and many other events programmers love to trap and manipulate. Similarly, you need an instance of the WebChromeClient for permitting functions such as the very helpful alert() JavaScript function. You use the WebSettings class to enable JavaScript for the control.

To cause the WebView control to navigate to a page, you have a couple of different options. In this application, you employ the loadurl() method with a fully qualified path for the index.html file packaged as an asset of the project. For more information on setting up the WebView control, examine the documentation for the android.webkit package online (see Resources). The file named index.html is loaded into the Webview control directly from the resources shipped with the application. Note in Figure 2 the assets folder under the resources. This folder is the ideal place to store html files for use in a hybrid application. (View a text-only version of Figure 2.)

Figure 2. The project in Eclipse
The project in Eclipse

Arguably the most important and interesting aspects of working with the WebView is the next step: connecting the WebView's JavaScript environment to the Android Activity code.


Wiring up the JavaScript interface

The next step is to enable the Java code within the Activity to interact with the JavaScript code within the HTML file managed by the WebView. This is accomplished with a call to the addJavascriptInterface() method as in Listing 4.

The arguments to this function are an instance of a Java class and a namespace identifier. For example, for this application, you define a namespace of calc and implement the code in a class named CalculatorHandler as in Listing 5.

Listing 5. CalculatorHandler implementation
// Javascript handler
    final class CalculatorHandler
    {
        private int iterations = 0;
        // write to LogCat (Info)
        public void Info(String str) {
            iterations++;
            Log.i("Calc",str);
        }
        // write to LogCat (Error)
        public void Error(String str) {
            iterations++;
            Log.e("Calc",str);
        } 
        // sample to retrieve a custom - written function with the details provided 
        // by the Android native application code
        public String GetSomeFunction()
        {
            iterations++;
            return "var q = 6;function dynamicFunc(v) { return v + q; }";
        }
        // Kill the app        
        public void EndApp() {
            iterations++;
            finish();
        }
        public void setAnswer(String a)
        {
            iterations++;
            Log.i(tag,"Answer [" + a + "]");
        }
        public int getIterations()
        {
            return iterations;
        }
        public void SendHistory(String s)
        {
            Log.i("Calc","SendHistory" + s);
            try {
                JSONArray ja = new JSONArray(s);
                for (int i=0;i<ja.length();i++) {
                    Log.i("Calc","History entry #" + (i+1) + " is [" + ja.getString(i) 
+ "]");
                }
            } catch (Exception ee) {
                Log.e("Calc",ee.getMessage());
            }
        }
    }

Within the JavaScript environment, you access the CalculatorHandler's methods through the window.calc.methodname syntax. For example, the CalculatorHandler implements a method named Info(), which takes a string argument and writes it to the application log. To access this method from the JavaScript environment, use syntax such as this: window.calc.Info("write this string to the application log!");.

With this basic understanding of how the Java code is invoked from JavaScript code, examine the index.html file in Listing 6 to see how the various methods are invoked.

Listing 6. index.html rendered (and executed) in WebView control
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=0.25,
    user-scalable=yes" />
<title>Android to JavaScript with JSON</title>
</head>
<script language="JavaScript">
var cmdHistory = new Array();
function startup() {
    try {
        window.calc.Info("Starting up....");
        cmdHistory[cmdHistory.length] = "startup";
    } catch (ee) {

    }
}
function PerformSimpleCalculation(formula) {
    try {
        cmdHistory[cmdHistory.length] = "PerformSimpleCalculation";
        var answer = eval(String(formula));
        document.getElementById('data').value = answer;
        window.calc.setAnswer(answer);
    }    catch (ee)     {
        window.calc.Error(ee);
    }
}
function PerformComplexCalculation(andmethod) {
    try    {
        /*
         * argument to this function is a single object with 2 "members or properties"
         * operation: this is a string naming what we want the function to do.
         * array of arguments: this is an array of integers
         * 
         */
        //alert(andmethod.operation);
        //alert(andmethod.arguments.length);
        if (andmethod.operation == "addarray") {
            cmdHistory[cmdHistory.length] = "PerformCompleCalculation-addarray";
            var i;
            var result = 0;
            for (i=0;i<andmethod.arguments.length;i++) {
                result += andmethod.arguments[i];
            }
            document.getElementById('data').value = result;
            window.calc.setAnswer(result);
        }
        if (andmethod.operation == "multarray") {
            cmdHistory[cmdHistory.length] = "PerformCompleCalculation-multarray";
            var i;
            var result = 1;
            for (i=0;i<andmethod.arguments.length;i++) {
                result *= andmethod.arguments[i];
            }
            document.getElementById('data').value = result;
            window.calc.setAnswer(result);            
        }
    }    catch (ee)    {
        window.calc.Error(ee);
    }
}
function dynamicfunction()
{
    try {
        cmdHistory[cmdHistory.length] = "PerformCompleCalculation-dynamic";
        eval(String(window.calc.GetSomeFunction()));
        var result = dynamicFunc(parseInt(document.getElementById('data').value));
        document.getElementById('data').value = result;
    }catch (ee) {
        alert(ee);
    }
}
</script>
<body >
<center>
<h3>Running in Web View :)</h3>
this is some sample text here <br />
<input type="text" id="data" value="starting value"><br />
<button onclick="window.calc.Info(document.getElementById('data').value);">Log
 Info</button>&nbsp;&nbsp;
<button onclick="window.calc.Error(document.getElementById('data').value);">Log
 Error</button><br />
<button onclick="dynamicfunction();">Dynamic</button>
<button onclick="alert(String(window.calc.getIterations()));">How 
    Many Calls</button>
<button onclick="window.calc.SendHistory(JSON.stringify(cmdHistory));">
    History</button>
<button onclick="if (window.confirm('End App?')) window.calc.EndApp();">Kill This
 App</button><br />
</center>
</body>
</html>

Examine the button handlers towards the end of Listing 6. Essentially these are calling methods in the window.calc namespace, which are implemented in the CalculatorHandler class in AndroidJSON.java.

Listing 5 and Listing 6 work hand in hand to demonstrate the code interaction initiated in the JavaScript environment and implemented in the Java source file. But how about initiating some action from the Activity code that you want to take place within the WebView?

It is time to look deeper into the Java code.


Inserting JavaScript code

Start with the task of passing a mathematical formula to the JavaScript code for evaluation. One of the wonderful (and dangerous!) features of JavaScript is the eval() function. The eval() function permits the run-time evaluation of a string of code. In this example, you take a string from the EditText control and pass to the JavaScript environment for evaluation. Specifically, we invoke the PerformSimpleCalculation() function found in Listing 6.

Listing 7 includes the code from AndroidJSON.java which is responsible for handling the button selections.

Listing 7. Calling the PerformSimpleCalculation() JavaScript function from Java
  btnSimple.setOnClickListener(new OnClickListener()
  {
       public void onClick(View v) {
         Log.i(tag,"onClick Simple");
         // Perform action on click
         try
         {
            String formulaText =  formula.getText().toString();
            Log.i(tag,"Formula is [" + formulaText + "]" );
            browser.loadUrl("javascript:PerformSimpleCalculation(" + formulaText + ");");
         }
         catch (Exception e)
         {
               Log.e(tag,"Error ..." + e.getMessage());
         }
       }
  });

Despite the many lines of this method, the only one to focus on here is the browser.loadurl() line which passes a string of the format: javascript:<code to execute>.

This JavaScript code is injected into the WebView's current page and executed. In this way, the Java code can execute JavaScript code defined in the WebView.

In the Simple example, a string is passed. However, what about when you need to work with a more complex structure? Here is where JSON can be an aid. Listing 8 shows the invocation of the PerformComplexCalculation() function, which is in Listing 6.

Listing 8. Calling a more complex function by passing a JSON object
btnComplex.setOnClickListener(new OnClickListener()
{
     public void onClick(View v) {
         Log.i(tag,"onClick Complex");
         // Perform action on click
         try
         {
             String jsonText = "";
         
             if (flipflop == 0)
             {     
                 jsonText = "{ \"operation\" : \"addarray\",\"arguments\" :
 [1,2,3,4,5,6,7,8,9,10]}";
                 flipflop = 1;
             } else {
                 jsonText = "{ \"operation\" : \"multarray\",\"arguments\" :
 [1,2,3,4,5,6,7,8,9,10]}";
                 flipflop = 0;
             }
             Log.i(tag,"jsonText is [" + jsonText + "]" );
             browser.loadUrl("javascript:PerformComplexCalculation(" + jsonText + ");");
         }
         catch (Exception e)
         {
             Log.e(tag,"Error ..." + e.getMessage());
         }
         
     }
});

Examine the JavaScript function PerformComplexCalculation from Listing 6. Note that the argument passed in is not a string, but rather an object of your own creation.

  • operation - Name of a function or procedure to process
  • arguments - This is an array of integers

The object contains only two properties, but can be arbitrarily more complex to meet a greater need. In this example, the PerformComplexCalculation() JavaScript function supports two different operations: addarray and multarray. When these have done their work upon invocation, the result is passed back to the Java code by calling the function: window.calc.setAnswer. Here you see bi-directional data flows between the Java and JavaScript code.

In this example, you passed a JSON object, but one observation from experience is that when working with Java strings coming back from the Java code, it helps to convert them to JavaScript strings. This can be done by passing the value to the String function as in this example: eval(String(formula));.

The JavaScript eval() function uses JavaScript strings. Without the conversion, the eval function basically does nothing.

For a somewhat more complex example, you are encouraged to walk through the code sequence when the Dynamic button is selected in the WebView.

To wrap up the code examples, look at passing an array of strings from the JavaScript environment to the Java environment.


Exchanging JSON objects

The JavaScript code in the example application (index.html) records local function invocations into a page level array named cmdHistory. Each time a function is invoked, you add a new entry to this array. For example, when the dynamicfunction() is invoked, a new string is stored: cmdHistory[cmdHistory.length] = "PerformCompleCalculation-dynamic";.

Nothing is particularly special about this approach; it is simply an example of collecting usage data at the page level. Perhaps this data is useful stored within the database of the Android application. How does this data get back to the Java code?

To send your array of string objects, you call the JSON.stringify function, passing in the array as the argument. Optionally, the stringify function can allow the customization of how a particular property of a complex Object is formatted. For more information on how this is accomplished, you might consider the explanation found at json.org (see Resources).

Figure 3 shows what winds up in the Log after parsing the JSON array following a typical run of the application.

Figure 3. Parsing out the JSON array sent from JavaScript
Screen capture of parsing out the JSON array sent from JavaScript

This example is only storing string data, so arguably you can simply append it to a longer string and call a simple function in the CalculatorHandler, which in turn can parse it out. However, what if the application wanted to track other data such as the value of certain variables or even attempt to profile the code by recording the duration of a particular function invocation? Clearly the ability to record and exchange objects is of interest in more complex scenarios.


Summary

This article demonstrated techniques of transferring data between the Java code in the Android application to JavaScript code in the WebView as well as the more general topic of hybrid applications leveraging WebKit. Hybrid applications mix JavaScript, JSON, callback functions, Android-SDK Java code and the most important ingredient of all, your imagination, to deliver flexible and capable mobile applications.


Download

DescriptionNameSize
Article source codeandjson.zip58KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into XML on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML, Web development
ArticleID=512488
ArticleTitle=Using XML and JSON with Android, Part 2: Deliver hybrid Android applications with JSON
publish-date=08242010