MOBILE

Building Android Apps : Simple Bells and Whistles

10/14/2010 9:45:47 AM
With this tiny bit of HTML, CSS, and JavaScript, we have essentially turned an entire website into a single-page application. However, it still leaves quite a bit to be desired. Let’s slick things up a bit.

1. Progress Indicator

Since we are not allowing the browser to navigate from page to page, the user will not see any indication of progress while data is loading (Figure 1). We need to provide some feedback to users to let them know that something is, in fact, happening. Without this feedback, users may wonder if they actually clicked the link or missed it, and will often start clicking all over the place in frustration. This can lead to increased server load and application instability (i.e., crashing).

Figure 1. Without a progress indicator of some kind, your app will seem unresponsive and your users will get frustrated


Thanks to jQuery, providing a progress indicator only takes two lines of code. We’ll just append a loading div to the body when loadPage() starts and remove the loading div when hijackLinks() is done.

Example 1. Adding a simple progress indicator to the page
$(document).ready(function(){
loadPage();
});
function loadPage(url) {
$('body').append('<div id="progress">Loading...</div>');
if (url == undefined) {
$('#container').load('index.html #header ul', hijackLinks);
} else {
$('#container').load(url + ' #content', hijackLinks);
}
}
function hijackLinks() {
$('#container a').click(function(e){
e.preventDefault();
loadPage(e.target.href);
});
$('#progress').remove();
}

Simulating Real-World Network Performance

If you are testing this web application on a local network, the network speeds will be so fast you won’t ever see the progress indicator. If you are using Mac OS X, you can slow all incoming web traffic by typing a couple of ipfw commands at the terminal. For example, these commands will slow all web traffic to 4 kilobytes per second:

sudo ipfw pipe 1 config bw 4KByte/s
sudo ipfw add 100 pipe 1 tcp from any to me 80

You should use your computer’s hostname or external IP address in the URL (for example, mycomputer.local rather than localhost). When you’re done testing, delete the rule with sudo ipfw delete 100 (you can delete all custom rules with ipfw flush).

You can do similar things on Linux and Windows as well. For Linux, check out the following links:

If you are using Windows, see the following:

If you are using the Android emulator, you can configure it to limit its speed using the -netspeed command-line option. For example, invoking the emulator with the arguments -netspeed edge will simulate real-world EDGE network speeds (118.4 kilobits per second upstream, 236.8 kilobits per second downstream). Run emulator -help-netspeed at the command line to see a list of all supported speeds.


See Example 2 for the CSS you need to add to android.css to style the progress div.

Example 2. CSS added to android.css used to style the progress indicator
#progress {
-webkit-border-radius: 10px;
background-color: rgba(0,0,0,.7);
color: white;
font-size: 18px;
font-weight: bold;
height: 80px;
left: 60px;
line-height: 80px;
margin: 0 auto;
position: absolute;
text-align: center;
top: 120px;
width: 200px;
}

2. Setting the Page Title

Our site happens to have a single h2 at the beginning of each page that would make a nice page title (see Figure 2).  To be more mobile-friendly, we’ll pull that title out of the content and put it in the header (see Figure 3-3). Again, jQuery to the rescue: you can just add three lines to the hijackLinks() function to make it happen. Example 3-6 shows the hijackLinks function with these changes.

Example 3. Using the h2 from the target page as the toolbar title
function hijackLinks() {
$('#container a').click(function(e){
e.preventDefault();
loadPage(e.target.href);
});
var title = $('h2').html() || 'Hello!';
$('h1').html(title);
$('h2').remove();
$('#progress').remove();
}

Figure 2. Before moving the page heading to the toolbar...


Figure 3. ...and after moving the page heading to the toolbar



Note:

I added the title lines before the line that removes the progress indicator. I like to remove the progress indicator as the very last action because I think it makes the application feel more responsive.


The double pipe (||) in the first line of inserted code (shown in bold) is the JavaScript logical operator OR. Translated into English, that line reads, “Set the title variable to the HTML contents of the h2 element, or to the string ‘Hello!’ if there is no h2 element.” This is important because the first page load won’t contain an h2 because we are just grabbing the nav uls.


Note:

This point probably needs some clarification. When users first load the android.html URL, they are only going to see the overall site navigation elements, as opposed to any site content. They won’t see any site content until they tap a link on this initial navigation page.


3. Handling Long Titles

Suppose we had a page on our site with a title too long to fit in the header bar (Figure 4). We could just let the text break onto more than one line, but that would not be very attractive. Instead, we can update the #header h1 styles such that long text will be truncated with a trailing ellipsis (see Figure 5 and Example 4). This might be my favorite little-known CSS trick.

Example 4. Adding an ellipsis to text that is too long for its container
#header h1 {
color: #222;
font-size: 20px;
font-weight: bold;
margin: 0 auto;
padding: 10px 0;
text-align: center;
text-shadow: 0px 1px 1px #fff;
max-width: 160px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

Figure 4. Text wrapping in the toolbar is not very attractive...


Figure 5. ...but we can beautify it with a CSS ellipsis


Here’s the rundown: max-width: 160px instructs the browser not to allow the h1 element to grow wider than 160px. Then, overflow: hidden instructs the browser to chop off any content that extends outside the element borders. Next, white-space: nowrap prevents the browser from breaking the line into two. Without this line, the h1 would just get taller to accommodate the text at the defined width. Finally, text-overflow: ellipsis appends three dots to the end of any chopped-off text to indicate to the user that she is not seeing the entire string.

4. Automatic Scroll-to-Top

Let’s say you have a page that is longer than the viewable area on the phone. The user visits the page, scrolls down to the bottom, and clicks on a link to an even longer page. In this case, the new page will show up “prescrolled” instead of at the top as you’d expect.

Technically, this makes sense because we are not actually leaving the current (scrolled) page, but it’s certainly a confusing situation for the user. To rectify the situation, we can add a scrollTo() command to the loadPage() function (Example 5).

Whenever a user clicks a link, the page will first jump to the top. This has the added benefit of ensuring the loading graphic is visible if the user clicks a link at the bottom of a long page.

Example 5. It’s a good idea to scroll back to the top when a user navigates to a new page
function loadPage(url) {
$('body').append('<div id="progress">Loading...</div>');
scrollTo(0,0);
if (url == undefined) {
$('#container').load('index.html #header ul', hijackLinks);
} else {
$('#container').load(url + ' #content', hijackLinks);
}
}

5. Hijacking Local Links Only

Like most sites, ours has links to external pages (i.e., pages hosted on other domains). We shouldn’t hijack these external links, because it wouldn’t make sense to inject their HTML into our Android-specific layout. As shown in Example 3-9, we can add a conditional that checks the URL for the existence of our domain name. If it’s found, the link is hijacked and the content is loaded into the current page (i.e., Ajax is in effect). If not, the browser will navigate to the URL normally.


Warning:

You must change jonathanstark.com to the appropriate domain or hostname for your website, or the links to pages on your website will no longer be hijacked.


Example 6. You can allow external pages to load normally by checking the domain name of the URL
function hijackLinks() {
$('#container a').click(function(e){
var url = e.target.href;
if (url.match(/jonathanstark.com/)) {
e.preventDefault();
loadPage(url);
}
});
var title = $('h2').html() || 'Hello!';
$('h1').html(title);
$('h2').remove();
$('#progress').remove();
}


Tip:

The url.match function uses a language, regular expressions, that is often embedded within other programming languages such as JavaScript, PHP, and Perl. Although this regular expression is simple, more complex expressions can be a bit intimidating, but are well worth becoming familiar with. My favorite regex page is located at http://www.regular-expressions.info/javascriptexample.html.


6. Roll Your Own Back Button

The elephant in the room at this point is that the user has no way to navigate back to previous pages (remember that we’ve hijacked all the links, so the browser page history won’t work). Let’s address that by adding a Back button to the top left corner of the screen. First, we’ll update the JavaScript, and then we’ll do the CSS.

Adding a standard toolbar Back button to the app means keeping track of the user’s click history. To do this, we’ll have to:

  • store the URL of the previous page so we know where to go back to, and

  • store the title of the previous page so we know what label to put on the Back button

Adding this feature touches on most of the JavaScript we’ve written so far in this chapter, so I’ll go over the entire new version of android.js line by line (see Example 7). The result will look like Figure 6.

Example 7. Expanding the existing JavaScript example to include support for a Back button
 var hist = [];
var startUrl = 'index.html';
$(document).ready(function(){
loadPage(startUrl);
});
function loadPage(url) {
$('body').append('<div id="progress">Loading...</div>');
scrollTo(0,0);
if (url == startUrl) {
var element = ' #header ul';
} else {
var element = ' #content';
}
$('#container').load(url + element, function(){
var title = $('h2').html() || 'Hello!';
$('h1').html(title);
$('h2').remove();
$('.leftButton').remove();
hist.unshift({'url':url, 'title':title});
if (hist.length > 1) {
$('#header').append('<div class="leftButton">'+hist[1].title+'</div>');
$('#header .leftButton').click(function(){
var thisPage = hist.shift();
var previousPage = hist.shift();
loadPage(previousPage.url);
});
}
$('#container a').click(function(e){
var url = e.target.href;
if (url.match(/jonathanstark.com/)) {
e.preventDefault();
loadPage(url);
}
});
$('#progress').remove();
});
}


Figure 6. It wouldn’t be a mobile app without a glossy, left-arrow Back button



Tip:

Please visit http://www.hunlock.com/blogs/Mastering_Javascript_Arrays for a full listing of JavaScript array functions with descriptions and examples.


Now that we have our Back button, all that remains is to purty it up with some CSS (see Example 8). We’ll start off by styling the text with font-weight, text-align, line-height, color, and text-shadow. We’ll continue by placing the div precisely where we want it on the page with position, top, and left. Then, we’ll make sure that long text on the button label will truncate with an ellipsis using max-width, white-space, overflow, and text-overflow. Finally, we’ll apply a graphic with border-width and -webkit-border-image. Unlike the earlier border image example, this image has a different width for the left and right borders because the image is made asymmetrical by the arrowhead on the left side.


Note:

Don’t forget that you’ll need an image for this button. You’ll need to save it as back_button.png in the images folder underneath the folder that holds your HTML file.


Example 8. Add the following to android.css to beautify the Back button with a border image
#header div.leftButton {
font-weight: bold;
text-align: center;
line-height: 28px;
color: white;
text-shadow: 0px -1px 1px rgba(0,0,0,0.6);
position: absolute;
top: 7px;
left: 6px;
max-width: 50px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
border-width: 0 8px 0 14px;
-webkit-border-image: url(images/back_button.png) 0 8 0 14;
}

By default, Android displays an orange highlight to clickable objects that have been tapped (Figure 8). This may appear only briefly, but removing it is easy and makes the app look much better. Fortunately, Android supports a CSS property called -webkit-tap-highlight-color, which allows you to suppress this behavior. We can do this here by setting the tap highlight to a fully transparent color (see Example 9).

Figure 7. By default, Android displays an orange highlight to clickable objects that have been tapped


Example 9. Add the following to android.css to remove the default tap highlight effect
#header div.leftButton {
font-weight: bold;
text-align: center;
line-height: 28px;
color: white;
text-shadow: 0px -1px 1px rgba(0,0,0,0.6);
position: absolute;
top: 7px;
left: 6px;
max-width: 50px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
border-width: 0 8px 0 14px;
-webkit-border-image: url(images/back_button.png) 0 8 0 14;
-webkit-tap-highlight-color: rgba(0,0,0,0);
}

In the case of the Back button, there could be at least a second or two of delay before the content from the previous page appears. To avoid frustration, we can configure the button to look clicked the instant it’s tapped. In a desktop browser, this is a simple process: you just add a declaration to your CSS using the :active pseudoclass to specify an alternate style for the object that the user clicked. I don’t know if it’s a bug or a feature, but this approach does not work on Android; the :active style is ignored.

I toyed around with combinations of :active and :hover, which brought me some success with non-Ajax apps. However, with an Ajax app like the one we are using here, the :hover style is sticky (i.e., the button appears to remain “clicked” even after the finger is removed).

Fortunately, the fix is pretty simple—use jQuery to add the class clicked to the button when the user taps it. I’ve opted to apply a darker version of the button image to the button in the example (see Figure 8 and Example 10). You’ll need to make sure you have a button image called back_button_clicked.png in the images subfolder. 

Figure 8. It might be tough to tell in print, but the clicked Back button is a bit darker than the default state


Example 10. Add the following to android.css to make the Back button looked clicked when the user taps it
#header div.leftButton.clicked {
-webkit-border-image: url(images/back_button_clicked.png) 0 8 0 14;
}


Note:

Since we’re using an image for the clicked style, it would be smart to preload the image. Otherwise, the unclicked button graphic will disappear the first time it’s tapped while the clicked graphic downloads. I’ll cover image preloading in the next chapter.


With the CSS in place, we can now update the portion of the android.js that assigns the click handler to the Back button. First, we add a variable, e, to the anonymous function to capture the incoming click event. Then, we wrap the event target in a jQuery selector and call jQuery’s addClass() function to assign the clicked CSS class to the button:

$('#header .leftButton').click(function(e){
$(e.target).addClass('clicked');
var thisPage = hist.shift();
var previousPage = hist.shift();
loadPage(lastUrl.url);
});


Note:

A special note to any CSS gurus in the crowd: the CSS Sprite technique—popularized by A List Apart—is not an option in this case because it requires setting offsets for the image. The -webkit-border-image property does not support image offsets.


Other  
  •  Building Android Apps : Traffic Cop
  •  iPhone Application Development : Exploring Interface Builder - Connecting to Code
  •  iPhone Application Development : Customizing Interface Appearance
  •  iPhone Application Development : Creating User Interfaces
  •  iPhone Application Development : Understanding Interface Builder
  •  Android Security Tools
  •  Android Security : Binder Interfaces
  •  Android Security : Files and Preferences
  •  Android Security : ContentProviders
  •  Android Security : Services
  •  Android Security : Broadcasts
  •  Android Security : Activities
  •  Android Security : Creating New Manifest Permissions
  •  Android Permissions Review
  •  Android’s Security Model
  •  Android’s Securable IPC Mechanisms
  •  CSS for Mobile Browsers : CSS Techniques
  •  CSS for Mobile Browsers : Selectors
  •  CSS for Mobile Browsers : Where to Insert the CSS
  •  iPhone Programming : Creating a Table View
  •  
    Video
    Top 10
    Home Theatre Pc Software And Operating Systems (Part 4) - XBMC
    Home Theatre Pc Software And Operating Systems (Part 3) - Setting Up Windows Media Center
    Home Theatre Pc Software And Operating Systems (Part 2)
    Home Theatre Pc Software And Operating Systems (Part 1) - Windows Media Center
    Nokia's Extreme Megapixel Bid
    Storage, Screens And Sounds (Part 3)
    Storage, Screens And Sounds (Part 2)
    Storage, Screens And Sounds (Part 1)
    Microsoft ASP.NET 4 : Using the SqlProfileProvider (part 4) - The Profile API, Anonymous Profiles
    Microsoft ASP.NET 4 : Using the SqlProfileProvider (part 3) - Profiles and Custom Data Types
    Most View
    Managing and Administering SharePoint 2010 Infrastructure : Using Additional Administration Tools for SharePoint
    Binding Application Data to the UI objects in Silverlight
    iPhone Application Development : Getting the User’s Attention - Generating Alerts
    Understanding and Using Windows Server 2008 R2 UNIX Integration Components (part 2)
    iPhone Application Development : Creating and Managing Image Animations and Sliders (part 3) - Finishing the Interface
    Cisco Linksys X3000 - The Link to Connectivity
    HP LaserJet Pro CM1415fnw - Print from The Clouds
    Building Your First Windows Phone 7 Application (part 2) - Using Your First Windows Phone Silverlight Controls
    Determine Your Need for Server Core
    Mobile Application Security : Bluetooth Security - Overview of the Technology
    Using System Support Tools in Vista
    Windows 7 : Using Windows Live Calendar (part 3) - Scheduling Appointments and Meetings & Viewing Agendas and Creating To-Do Lists
    Advanced ASP.NET : The Entity Framework (part 3) - Handling Errors & Navigating Relationships
    Graham Barlow: the Apple view
    Ipad : Presentations with Keynote - Adding Transitions (part 2) - Object Transitions
    Windows Server 2003 : Troubleshooting Group Policy
    Microsoft XNA Game Studio 3.0 : Controlling Color (part 2)
    Building the WinPE Image
    Programming the Mobile Web : HTML 5 (part 3) - Offline Operation
    Windows Phone 7 Development : Using Culture Settings with ToString to Display Dates, Times, and Text