This article explains how to develop a mobile Web application targeted at the Apple iPhone as well as Android-based smartphones. To develop mobile Web applications against such devices, you can't just use your normal desktop browsers, at least not completely. You will need either emulators or actual devices. For the iPhone, you need the iPhone emulator. This is part of the iPhone SDK. For this article, iPhone SDK 3.1 was used. Similarly, you will need the Android SDK. This includes the Android Virtual Device manager, which can be used to create Android emulators running various versions of Android. For this article, Android SDK 2.1 was used. Most of the code in this article is JavaScript, with some HTML and CSS thrown in. There is a server-side aspect to this application that was written in Java™ code. This is arbitrary, and just like with any other Web application, you can use whatever server-side technology you want. To run the application in this article, you will need Java 1.6. You will also need Jersey, the reference implementation of JAX-RS, and all of the associated Java Archive (JAR) files (see Resources for links).
Mobile devices have had Web browsers for years. However, developing for them has always been painful because Web developers have had to deal with cross-browser issues. A lot of long days and late nights can be attributed to getting HTML, JavaScript, and CSS to work identically on various versions of Internet Explorer, Mozilla Firefox, Safari, and so on. Well, the desktop browser world is completely tame compared to the mobile browser world. The number of different mobile browsers has, historically, been staggering. Every device maker had their own browser, and even devices from the same manufacturer had huge variations in operating systems, screen sizes, and so on. Some browsers only supported WAP, others supported subsets of HTML, some had full HTML, but no JavaScript.
Fortunately, things have really changed. As of January 2010, more than 80% of U.S. mobile Internet traffic is done either on an iPhone or an Android device. Not only do both of these operating systems use WebKit for HTML/CSS rendering, they both have JavaScript engines that have aggressively adopted the HTML 5 standards. You read that right. The browsers with the dominant position in the mobile space are also pushing open standards. You really could not ask for a better situation as a Web developer.
There are still variations between browsers, even between different versions of the iPhone and Android browsers. This is especially true for Android. On versions of Android prior to 2.0, the Android browser used the proprietary Google Gears technology. Gears can be credited with pushing many innovations that have now become part of the HTML 5 standard. However, that meant that for a while, some HTML 5 standards were not supported in the Android browser, but you could use Gears to get the same functions. All of the code in this article is based on the HTML 5 standards and will work on Android 2.0+ or iPhone 3.0+. Now that the stage is set for exploring these modern WebKit-based browsers, take a look at Ajax on these devices.
Just like on desktop Web applications, the key to creating a compelling user experience on mobile Web applications usually involves Ajax. Of course, user experience is not the only reason to use Ajax; it can also be faster and more efficient. These are even more significant reasons to use Ajax on a mobile Web application, where the network latency is so much greater, and the browser itself is much more constrained in terms of processor speed, memory, and cache sizes. Fortunately, Ajax turns out to be one of many things made easier by only having to worry about standards-based browsers. Before I get into that, let's take a quick look at the back-end server that the application you will create in this article will be calling.
Before you get started, you will need to download the necessary JAR files for Jersey, Xerces, Rome, and the Google App Engine SDK (see Resources for links) and install them in the following folder: WebKitBlog>war>WEB-INF>lib. You can download the rest of the source code for the application.
The mobile Web application in this article is a simple application for reading news on mobile Web development. For now, it will simply pull in an RSS feed from the official WebKit blog, but it could be easily modified to aggregate multiple RSS feeds. The application is a trivial Java Web application that you could deploy to any Java application server. All of the code is shown in Listing 1.
Listing 1. The
Feed class
@Path("/feed")
public class Feed {
String surfinSafari = "http://webkit.org/blog/feed/";
@GET @Produces("application/json")
public News getNews(@DefaultValue("0") @QueryParam("after") long after)
throws Exception{
URL feedUrl = new URL(surfinSafari);
SyndFeedInput input = new SyndFeedInput();
SyndFeed feed = input.build(new XmlReader(feedUrl));
List<Item> entries = new ArrayList<Item>(feed.getEntries().size());
for (Object obj : feed.getEntries()){
SyndEntry entry = (SyndEntry) obj;
if (entry.getPublishedDate().getTime() > after){
Item item = new Item(entry.getTitle(), entry.getLink(),
entry.getDescription().getValue(),
entry.getPublishedDate().getTime());
entries.add(item);
}
}
return new News(feed.getTitle(), entries);
}
}
|
This code uses Java's JAX-RS to create a RESTful service. The
@Path annotation denotes the endpoint of the
service, that is, the relative URL to this service will be /feed. The
@GET denotes that this service supports
HTTP GET. The
@Produces annotation declares that this service
will produce data in a JSON format. This is an easy way to get your data
serialized in JSON. The method getNews takes a
single parameter called after, that is, get the
entries after a certain date. This too uses the JAX-RS annotations to bind
the after parameter to the query string
parameter called after. If no value is given,
then it gets a default value of 0.
So far, all I've talked about in Listing 1 are the JAX-RS
annotations that set up the routing and data serialization for the service.
The actual body of the method relies heavily on the Rome package for
processing RSS. It simply downloads the latest RSS feed, and then
transforms it to just the data that you need for the application, modeled
by the Item and News
classes. The only tricky part is that the published date of the article is
turned into a long and used as an ID. This makes it an especially useful
ID, because you can use it to sort by, as you will see later. The
News class is shown in Listing
2.
Listing 2. The
News class
@XmlRootElement
public class News {
private String title;
private List<Item> entries;
// constructors, getters/setters omitted for brevity
}
|
Notice that the News class uses the JAXB
annotation @XmlRootElement. There is no XML in
this application, but JAX-RS leverages JAXB to do the automatic
serialization/deserialization. All this class has is a title property and
a list of Items. The Item class is shown in Listing 3.
Listing 3.
Item class
@XmlType
public class Item {
private String title;
private String link;
private String description;
private Long id;
// constructors, getters/setters omitted for brevity
}
|
This class shows you exactly what you will show in the Web application. Like
the News class, it uses a JAXB annotation so
that JAX-RS can serialize this into JSON for you. There is one last piece
to the server-side code and that is configuring your Web application so
that requests get routed to JAX-RS. For that you need to edit the web.xml
file
for the application, as shown in Listing 4.
Listing 4. The web.xml configuration file
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<servlet>
<servlet-name>WebKit Blog Servlet</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.
ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>org.developerworks.mobile</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>WebKit Blog Servlet</servlet-name>
<url-pattern>/resources/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<!-- Default page to serve -->
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<mime-mapping>
<extension>mf</extension>
<mime-type>text/cache-manifest</mime-type>
</mime-mapping>
</web-app>
|
This is mostly boilerplate web.xml code. As you can see from the servlet
declaration, the application is using Jersey, the reference implementation
of JAX-RS. The initialization parameter to the servlet tells Jersey to
scan the org.developerworks.mobile package for
classes that have been annotated to handle service requests. Also, any
requests that map to /resources/ will be mapped to the Jersey servlet. The
last thing to note here is the mime-mapping section at the end of the
file. This is the MIME type for manifest files, a key to offline Web
applications that I will discuss later. Now that you've seen the back-end
service that the Web application will use, it is time to look at the
front end that uses it.
User interface powered by Ajax
You might have noticed in Listing 4 that the application has a standard index.html file. This is the entry point to the application, and it is shown in Listing 5.
Listing 5. The index.html file
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html manifest="application.mf">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>WebKit News</title>
<meta name = "viewport" content = "width = device-width"/>
<script type="text/javascript" src="index.js"></script>
</head>
<body onload="loadEntries()">
<h1 id="title">Recent News About WebKit</h1><img
id="loader" src="loading.gif"/>
</body>
</html>
|
This is a pretty simple Web page, but there are a couple of very notable
things. First, notice that in the head of the document, I set
the viewport. This instructs the browser to zoom in on the content, so it
looks good on a device. In terms of UI code, there is just a title and a
loading image, that's it. Everything else is done by JavaScript. The
loadEntries function in the index.js file makes
an Ajax request to load the data. This function is shown in Listing 6.
Listing 6. The
loadEntries function
function loadEntries(){
if (!window.JSON){
var head = document.getElementsByTagName("head")[0];
var jsScript = document.createElement("script");
jsScript.setAttribute("src", "json2.js");
jsScript.setAttribute("type","text/javascript");
head.appendChild(jsScript);
}
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (this.readyState == 4 &&this.status == 200){
var theFeed = JSON.parse(this.responseText);
var i = 0;
if (theFeed.entries){
var len = theFeed.entries.length;
for (i=0;i<len;i++){
addEntry(theFeed.entries[len - 1 -i], true);
}
}
var body = document.getElementsByTagName("body")[0];
body.removeChild($("loader"));
}
};
var urlStr = "/resources/feed";
xhr.open("GET", urlStr);
xhr.send();
}
|
At the beginning of this function, I do a little bit of feature detection. Most of the browsers support a built-in function for parsing and serializing JSON. This is the same JSON library available from json.org. However, if it is not available, then all you need to do is include that file in your page, and you will have the same capabilities.
Otherwise, this is pretty simple Ajax code. You do not have to worry about
Internet Explorer, so you can just create an
XMLHttpRequest directly. You set its
onreadystatechange property to a function,
creating a closure in
this case. When the response comes back from the server
(readyState = 4) and there were no problems
with the processing (status = 200 OK), then you
parse the JSON response using the JSON.parse
function. For each entry from the feed, you call the
addEntry function. This is a function for
creating the UI elements for each entry. It is shown in Listing 7.
Listing 7. The
addEntry function
function addEntry(e, prepend){
var eDiv = document.createElement("div");
eDiv.setAttribute("class", "item");
var link = document.createElement("a");
link.setAttribute("href", e["link"]);
var title = document.createTextNode(e["title"]);
link.appendChild(title);
eDiv.appendChild(link);
var button = document.createElement("input");
button.setAttribute("type", "button");
button.setAttribute("value", "Show");
button.setAttribute("id", "button" + e["id"]);
button.setAttribute("onclick", "showDescription('" + e["id"] + "')");
eDiv.appendChild(button);
var dDiv = document.createElement("div");
dDiv.setAttribute("id", e["id"]);
dDiv.setAttribute("class", "description");
dDiv.setAttribute("style", "display:none");
dDiv.innerHTML = e["description"];
eDiv.appendChild(dDiv);
var body = document.getElementsByTagName("body")[0];
if (!prepend && body.childNodes.length > 1){
body.appendChild(eDiv);
} else {
body.insertBefore(eDiv, body.childNodes.item(2));
}
}
|
This function is all pretty standard DOM manipulation. The only tricky aspect is that it can either append or prepend the entry, or add the entry either at the top or the bottom. Going back to Listing 6, the last thing the function does is remove the loading graphic. This concludes the basic Ajax functions, and depending on your use case, may be good enough. However, the application requires a lot of data to be downloaded from the server and then to be parsed. You can do better, and for a mobile Web application, you really must use local caching.
Local storage is part of the HTML 5 specification and is widely supported. It provides a simple name - value pair storage mechanism, where both the name and values are strings. So saving the entries to local storage is straightforward, and is shown in Listing 8.
Listing 8. Saving to local storage
function saveLocal(entry){
if (!window.localStorage){
return;
}
localStorage.setItem(entry["id"], JSON.stringify(entry));
}
|
Once again, you do some browser capability detection, by first checking if
the window object has the localStorage
property, per the HTML 5 specification. If it does, then you just use the
id of the entry as the key to store. For the
value, you must use a
string, so you use the JSON.stringify function
to serialize the object as a string. Now you just need a function for
reading all of this data back from local storage. This is shown in Listing 9.
Listing 9. Loading from local storage
function loadLocal(){
if (!window.localStorage){
return [];
}
var i = 0;
var e = {};
var id = 0;
var entries = [];
for (i=0; i< localStorage.length; i++){
id = localStorage.key(i);
e = JSON.parse(localStorage.getItem(id));
entries[entries.length] = e;
}
entries.sort(function(a,b) {
return b.id - a.id;
});
return entries;
}
|
Again, this function starts by checking if local storage is available or
not. Next, it iterates over all of the data in local storage. For each
value in storage, you once again use the
JSON.parse function to parse the string back
into an object. Next, you sort the entries, because the order that they come
out of from local storage is not guaranteed. This does a descending sort, putting the newest entries first. Finally, now that you have functions for
storing and loading from local storage, you just need to integrate it back
into the loadEntries function as shown in Listing 10.
Listing 10. Add caching to
loadEntries
function loadEntries(){
// check for JSON object
var localEntries = loadLocal();
var newest = 0;
if (localEntries.length > 0){
newest = localEntries[0].id;
}
var i = 0;
for (i=0;i<localEntries.length;i++){
addEntry(localEntries[i]);
}
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (this.readyState == 4 && this.status == 200){
var theFeed = JSON.parse(this.responseText);
var i = 0;
if (theFeed.entries){
var len = theFeed.entries.length;
for (i=0;i<len;i++){
addEntry(theFeed.entries[len - 1 -i], true);
saveLocal(theFeed.entries[len - 1 -i]);
}
}
var body = document.getElementsByTagName("body")[0];
body.removeChild($("loader"));
}
};
var urlStr = "/resources/feed?after=" + newest;
xhr.open("GET", urlStr);
xhr.send();
}
|
The major difference here is that you first load the local data. You
determine the newest entry that you have, then when you call the
server, use this parameter so that the server will only send entries that
are not already available from local storage. Finally, on the callback,
when you get the entries, you need to call
saveLocal to save them to the local cache. With
new entries stored locally, you are now in a good position to enable full
offline access to your Web application.
Enabling an application to work offline is often viewed as the holy grail of mobile Web application development. Although it can be tricky, many of the new features in HTML 5 have removed a lot of the hurdles. The first thing you need to do is identify what assets are needed offline, and just list them in the application's manifest, as shown in Listing 11.
Listing 11. Application manifest
CACHE MANIFEST index.html index.js json2.js loading.gif |
For an application as simple as this one, the manifest is also quite simple. It
is all of the static files: HTML, JavaScript, and images (also CSS, if you
needed it). It is important that this file is served with a MIME type of
text/cache-manifest—just as you set it in the web.xml file in Listing 4. Finally, if you look again at Listing 5, you
will see that the root html element has an attribute of manifest that
points to the manifest file in Listing 11. The last thing that you need to
do is make one last modification to the loadEntries function as shown in
Listing 12.
Listing 12. Check for offline
function loadEntries(){
// load local
// ...
// check if online
if (navigator.onLine){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (this.readyState == 4 && this.status == 200){
var theFeed = JSON.parse(this.responseText);
var i = 0;
if (theFeed.entries){
var len = theFeed.entries.length;
for (i=0;i<len;i++){
addEntry(theFeed.entries[len - 1 -i], true);
saveLocal(theFeed.entries[len - 1 -i]);
}
}
var body = document.getElementsByTagName("body")[0];
body.removeChild($("loader"));
}
};
var urlStr = "/resources/feed?after=" + newest;
xhr.open("GET", urlStr);
xhr.send();
}
}
|
To see if you are online or offline, check the onLine property of the
navigator object (window.navigator). If you are not online, then there is
no point in calling the server to ask for new entries, just display all of
the local ones. Now you have a complete mobile Web application that is
offline-capable.
Mobile Web applications are currently a hot topic, for a good reason. They provide a way to reach users on many different kinds of devices. They take advantage of the highest level of standardization between browsers that Web developers have ever enjoyed. In this article, you have seen all of the essential techniques needed for creating a mobile Web application that relies on Ajax. And, by relying on Ajax, you are able to take advantage of cutting edge features in mobile browsers. Just look at the sample code in this article. You could have easily generated all of the HTML on the server when the page is requested. However, by putting all of the UI on the client and pulling data from the server using Ajax, you could easily cache data on the client and even enable the application to work offline.
| Description | Name | Size | Download method |
|---|---|---|---|
| Article source code | WebKitBlog.zip | 32KB | HTTP |
Information about download methods
Learn
-
"New
elements in HTML 5" (developerWorks, August
2007): HTML 5 is not just about JavaScript. Read about some of the new
markup in the developerWorks article.
-
"Android and iPhone browser wars, Part 1: WebKit to the rescue"
(developerWorks, December 2009): Do you like the mobile Web
application approach using HTML 5, but still want your
application in the iPhone App Store and Android Market? See how you can
get the best of both worlds in Part 1 of this two-part article series.
-
Dive Into Html 5: Check out this
free book for a great look at HTML 5 detection techniques as well as the
many features of HTML 5.
- If you are developing Web applications
for the iPhone, you will definitely want to keep the Safari
Reference Library handy.
- The
W3C HTML 5 Specification is the
definitive source on HTML 5.
- The developerWorks Web Development zone
specializes in articles covering various Web-based solutions.
-
My
developerWorks: Personalize your developerWorks experience.
- To listen to interesting interviews and
discussions for software developers, check out developerWorks podcasts.
-
developerWorks technical events and webcasts: Stay current with
developerWorks technical events and webcasts.
Get products and technologies
-
Download the Android
SDK, access the API reference, and get the latest news on Android.
- Get the latest iPhone SDK.
- Because Android is
open source, you can get the source code for it from the Android Open Source Project.
- Get the Java SDK.
JDK 1.6.0_17 was used in this article.
- Download Jersey Version 1.1.5.
- Get the Project Rome JAR
files.
- Get the Xerces JAR files.
- Download the Google App
Engine SDK.
- Innovate your
next open source development project with IBM trial
software.
Discuss
- Participate in developerWorks
blogs and get involved in the developerWorks community.

Michael Galpin is an architect at eBay and is a frequent contributor to developerWorks. He has spoken at various technical conferences, including JavaOne, EclipseCon and AjaxWorld. To get a preview of what he is working on, follow @michaelg on Twitter.




