Building mobile applications for WebSphere Commerce using the hybrid application programming model

Learn how to build mobile applications for WebSphere® Commerce using the hybrid application programming model. This article describes the hybrid model, how it compares to the other mobile application programming models, and how to use it to build iPhone® and Android® applications for WebSphere Commerce in two case studies.

Share:

Overview

With the proliferation of Internet-capable mobile devices, mobile applications have become integral parts of many cross-channel retail solutions. In particular, the increasing popularity of iPhone OS and Android devices has driven demand for native applications with capabilities beyond what web applications running in mobile browsers can offer. However, building a fully native application from scratch is a significant commitment of skills and resources, and a majority of the code written for one platform will not be reusable on another. This is where the hybrid application programming model comes in - with the hybrid model. You will reuse many of your existing web application assets to build mobile applications.

Note that this article is targeted at architects and developers interested in building mobile applications for WebSphere Commerce. It assumes a basic understanding of WebSphere Commerce and mobile application development. A companion white paper, Leveraging mobile commerce in your multi-channel strategy, discusses the market opportunity in mobile commerce, and sketches some approaches to take advantage of those opportunities within WebSphere Commerce.

Mobile application programming models

We can categorize the approaches to providing a mobile application by whether it is a web application, a native (on-device) application, or some combination of the two. The following sections briefly describe some of the characteristics of each of these models.

Mobile web applications

A mobile web application is delivered to a mobile device using a combination of HTML, cascading stylesheets (CSS), and JavaScript. You can can more or less customize mobile devices. You can also customize specific devices.

The advantage of this model is that it is based on familiar web programming skills. It does not require the end user to install any special application since it depends on the built-in browser. It is easy to iterate development. Upgrades are provided without user action simply by upgrading the content provided by the server. It has the advantage that the same, or nearly the same, content serves the ever-growing family of mobile devices.

Version 7 of WebSphere Commerce contains a Madisons Mobile store that provides a customized web experience for smartphones. However, there are some disadvantages to this approach, including the fact that the application may lack the look and feel of the native applications, possible performance impacts, and lack of access to some device capabilities, such as the local address book or camera.

This approach also depends on the capabilities of the mobile browser, and this can be a serious source of incompatibility, especially for older handsets. The advent of HTML5 and the increasing adoption of the Webkit browser engine help alleviate some of these concerns, but only as newer, more capable devices gain market share.

Native applications

By contrast, native applications are coded specifically for a single mobile platform, using device-specific functions and libraries provided by the manufacturer. Such applications can easily incorporate the platform's unique look and feel, and generally perform better than web applications. They can access any of the native device capabilities for which an API is provided.

The problem is that you must write the application from scratch for each supported device. The number of such devices is large, and constantly changing at a furious rate, as numerous companies fight for market share with ever more capable devices. Writing a native application also requires specialized skills, which may not transfer easily from one device to another.

Hybrid applications

Hybrid applications attempt to combine the strong points of both the web and the native application, and are the focus of this article. The basic idea is that a native application shell is written, but the main content provided is done through the use of web views, incorporating HTML, CSS, and JavaScript as usual. The native skeleton may include libraries, accessible from JavaScript, which provide access to device capabilities such as the local address list, the GPS, the camera, and other device features. This allows the web application to include code accessing device capabilities.

One of the key features of the hybrid application model is bridging between a JavaScript routine and a native device capability. If this framework had to be developed anew for each supported device by each user, the task is substantial. But, in fact, a number of frameworks of exactly this nature are available, both proprietary and open source. When such a framework supports multiple devices, it means that you can use the same server-side code for all supported devices, with at minimum, minor customization, representing a potentially huge savings in required resources.

Note that the difference between native and hybrid applications is mainly at the implementation level. From the end user's perspective, a hybrid application is typically indistinguishable from a native application. A hybrid application is available to end users through the platform's application marketplace (for example, the iPhone App Store), and offers a native user experience, just like a native application.


Fundamentals of a hybrid application

A hybrid application is basically a native application with embedded web content. The UI of a hybrid application is typically composed of a combination of native views and web views - views that can display local or remote web content. One example is an application in which the main content area is composed of a single web view. You can use platform-specific CSS to give the web content a native look and feel, while you can use native views to render platform-specific controls that are difficult to construct with web content and CSS, such as menus and tab bars.

Note that there is no set rule on using native views versus web content. Some scenarios allow for a mostly web content approach, while others might demand a heavier use of native views.

In Figure 1, on the left, you see the Madisons Mobile starter store on Mobile Safari (web content in blue). On the right, you see the same page in a hybrid application (web content in blue, native views in red).

Figure 1. User interfaces of hybrid vs. mobile web applications
User interfaces of hybrid vs. mobile web applications

In Figure 2, you can compare the UI elements on the different mobile platforms.

Figure 2. Comparing UI elements on different mobile platforms
Comparing UI elements on different mobile platforms

Interactions between native code and embedded web content

On many mobile platforms, an application's native code can execute JavaScript functions in the embedded web content. This allows the native code to retrieve information from the web content (for example, the value of an HTML form input) and trigger actions in the web content (for example, hide/unhide an HTML element). Conversely, the application's native code can intercept requests made by the embedded web content and execute native functions on behalf of the web content. This is, in effect, a two-way bridge between native code and embedded web content, and is commonly used to give embedded web content access to device capabilities normally unavailable to web content.

One example is giving the embedded web content access to the native address book. Normally, the native address book of a mobile device is inaccessible to the web content. But, with this two-way bridge between the native code and web content, the embedded web content of a hybrid application can request the native code to display the native address book on behalf of the web content. Once the user has performed the required action in the native address book (for example, selects an e-mail address), the native code can execute a JavaScript callback function to pass control and data back to the embedded web content (see Figure 3).

Figure 3. Interactions between native code and embedded web content
Interactions between native code and embedded web content

If multiple mobile platforms are being targeted, there is a definite benefit to making this bridge between JavaScript and native code as similar as possible across all the platforms. This allows the same web content (and hence, the same JavaScript) to be used across multiple mobile platforms. One such development framework, though not used in the prototypes discussed here, is the open source PhoneGap.

In the following case studies, we will look at how you can apply these patterns to build iPhone and Android applications for WebSphere Commerce.


Case study 1. Building an iPhone application for WebSphere Commerce using the hybrid model

Our first case study is the Madisons iPhone application that we have built as a proof of concept. It is basically the Madisons Mobile starter store packaged as an iPhone application with a native look and feel, plus additional features not found in the mobile web application, including:

  • An enhanced home screen with interactive e-marketing spots
  • An enhanced store locator with geolocation support and integrated map view
  • A swipeable product details screen
  • Integration with the iPhone address book

Figure 4 shows the home screen of the application. You see the interactive e-marketing spots occupying the main content area of the home screen, as well as the native navigation controls of the application.

Figure 4. Madisons iPhone application - home screen
Madisons iPhone application - home screen

Figure 5 shows the catalog browsing flow of the application. You see how a shopper can navigate to a product and add it to the wish list or shopping cart.

Figure 5. Madisons iPhone application - catalog browsing
Madisons iPhone application - browsing the catalog

Figure 6 shows the store locator flow of the application. You see how a shopper can locate nearby stores and view their maps using the application's store locator and geolocation support.

Figure 6. Madisons iPhone application - store locator
Madisons iPhone application - store locator

Figure 7 shows the checkout flow of the application. You see how a shopper can make a purchase for in-store pickup using the application.

Figure 7. Madisons iPhone application - shopping cart and checkout
Madisons iPhone application - shopping cart and checkout

High-level design

We designed the Madisons iPhone application as a productivity application that extensively uses the navigation and tab bar interfaces. It has a screen flow almost identical to the page flow of the Madisons Mobile starter store. The application is organized into tabs, each corresponding to a section of the starter store. The screens under each tab are managed by a navigation interface, replacing the title bar and breadcrumb trail in the starter store. The main content area of the application is composed of UIWebView instances displaying remote Madisons Mobile pages with an iPhone-specific CSS, while the navigation and tab bar interfaces are rendered by native views and controllers.

Like all iPhone applications, the Madisons iPhone application was developed using the iPhone SDK. The iPhone SDK contains a set of development tools for writing and testing iPhone applications. Highlights include:

  • Xcode: This the integrated development environment.
  • Interface builder: This is the tool to create and update the UIs of iPhone applications in the form of interface builder (NIB) files.
  • iPhone simulator: This is the tool to run and test iPhone applications on a simulated device.

Note that the following sections assume a basic understanding of iPhone application development. For more details on iPhone application development, refer to the iPhone Dev Center and the iPhone Human Interface Guidelines.


Server-side changes

Device detection

We used the device detection function of WebSphere Commerce to identify requests from the Madisons iPhone application. We created a wc-devices.xml file in the WC_eardir/xml/config/com.ibm.commerce.foundation-ext directory to map the UIWebView user agent to a device format ID. We then used this device format ID to:

  • Map view requests from the application to the Madisons Mobile JSP files in the Struts configuration entries.
  • Toggle the inclusion of the iPhone-specific CSS and execution of iPhone-specific code in the JSP files.

Listing 1 shows the custom wc-devices.xml file that maps requests from the application to the appropriate device format. See Table 1 for the locations of the changed files.

Listing 1. wc-devices.xml file mapping the UIWebView user agent to device format ID -12
<_config:Devices xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.ibm.com/xmlns/prod/commerce/foundation/config \
 ../xsd/wc-devices.xsd"
 xmlns:_config="http://www.ibm.com/xmlns/prod/commerce/foundation/config">

 <_config:DeviceGroup internalID="-11" channelID="-6">
  <_config:Device name="BlackBerry Bold 9000" userAgentPattern="BlackBerry9000.*"/>
  <_config:Device name="BlackBerry Storm 9530" userAgentPattern="BlackBerry9500.*"/>
  <_config:Device name="BlackBerry Curve 8320" userAgentPattern="BlackBerry8320.*"/>
  <_config:Device name="HTC S740" userAgentPattern=".*MSIEMobile.*"/>
  <_config:Device name="Nokia S60"
   userAgentPattern=".*SymbianOS.*Series60/3.1.*Nokia3250.*" />
  <_config:Device name="Nokia N97"
   userAgentPattern=".*SymbianOS.*Series60/5.0.*Nokia3250.*" />
  <!-- Maps the Mobile Safari user agent to device format ID -11. -->
  <_config:Device name="Apple iPhone"
   userAgentPattern=".*iPhone.*Safari.*"/>
 </_config:DeviceGroup>

 <!--
  Maps the UIWebView user agent to device format ID -12.
  The UIWebView user agent is different from the Mobile Safari user agent in that the
  UIWebView user agent does NOT contain the substring "Safari".
 -->
 <_config:DeviceGroup internalID="-12" channelID="-6" >
  <_config:Device name="Apple iPhone Native App"
   userAgentPattern=".*iPhone.*"/>
 </_config:DeviceGroup>

</_config:Devices>

Listing 2 shows the custom Struts configuration entries added to struts-config-ext.xml. This file maps requests from our application to the Madisons Mobile starter store.

Listing 2. Excerpt of Struts configuration entries mapping view requests from device format ID -12 to the Madisons Mobile starter store JSP files
<forward className="com.ibm.commerce.struts.ECActionForward"
 name="ChangePassword/10000001/-12"
 path="/mobile/UserArea/AccountSection/PasswordSubsection/PasswordUpdateForm.jsp"/>
<forward className="com.ibm.commerce.struts.ECActionForward"
 name="ReLogonFormView/10000001/-12"
 path="/mobile/UserArea/AccountSection/LogonSubsection/UserTimeoutView.jsp"/>
<forward className="com.ibm.commerce.struts.ECActionForward"
 name="RememberMeLogonFormView/10000001/-12"
 path="/mobile/UserArea/AccountSection/LogonSubsection/logon.jsp"/>
<forward className="com.ibm.commerce.struts.ECActionForward"
 name="AccessControlErrorView/10000001/-12"
 path="/mobile/GenericError.jsp"/>

Listing 3 shows the changes made to the JSTLEnvironmentSetup.jspf file to support web content enhancements specific to our application, in particular the iPhone-specific CSS.

Listing 3. JSTLEnvironmentSetup.jspf code that changes the CSS path based on the device format ID
<%-- Set variables for device specific rendering --%>
<c:if test="${EC_deviceAdapter.deviceFormatId == -12}">
 <c:set var="_iPhoneNativeApp" value="true"/>
 <c:set var="mobileBasePath" value="mobile/iPhone"/>
 <c:set var="pageMax1" value="500"/>
 <c:set var="pageMax2" value="500"/>
</c:if>
<c:set var="cssPath" value="${jspStoreImgDir}${mobileBasePath}/${vfileStylesheet}"/>

Note that we have already incorporated these changes into WebSphere Commerce V7 Feature Pack 1. The Madisons Mobile starter store will be updated with these changes if you have installed Feature Pack 1 and published the Madisons Mobile enhancements store archive.

iPhone-specific CSS

We used an iPhone-specific CSS (Listing 4) to give a native look and feel to the embedded web content. We also used the CSS to:

  • Disable the default tap highlight color, since we used the :active pseudo-class to highlight active (tapped) items.
  • Disable text selection and the copy-and-paste callout.

Note that the CSS uses many properties prefixed with -webkit-, WebKit implementations of CSS3 properties. Most of them work on both iPhone and Android, as both platforms use WebKit as the rendering engine for the web content.

Listing 4. Excerpt of the iPhone-specific CSS
/* Defines the look of section headers */
div.heading_container {
 /* Gives the background a slight gradient */
 background-image:
  -webkit-gradient(linear, left top, left bottom, from(white), to(#e0e0e0));
 border-color: #e0e0e0;
 border-style: solid;
 border-width: 1px 0px 0px 0px;
 margin: 0px;
 padding: 5px 10px;
}

/* Defines the look of section titles */
div.heading_container > h2 {
 margin: 0px;
 padding: 0px;
 color: gray;
 font-weight: bold;
 font-size: 14px;
 overflow: hidden;
 /* Truncates the title and adds a trailing ellipsis (...) if it overflows */
 text-overflow: ellipsis;
 white-space : nowrap;
 /* Gives the title an embossed look */
 text-shadow: white 0px 1px 0px;
}

div.content_box > ul {
 margin: 0px;
 padding: 0px;
 list-style: none;
}

/* Hides the bullets (>>) */
span.bullet {
	display: none
}

/* Gives lists the look of edge-to-edge tables */
div.content_box > ul > li {
 background-color: white;
 border-color: #e0e0e0;
 border-style: solid;
 border-width: 0px 0px 1px 0px;
 margin: 0px;
 padding: 10px;
}

/* Gives clickable list items the look of table items */
div.content_box > ul > li > a {
 /* Adds trailing chevrons (>) to the table items */
 background-image: url('../images/chevron.png'), url('../images/link.png');
 background-position: center right, top left;
 background-repeat: no-repeat, repeat-x;
 display: block;
 margin: -10px;
 padding: 10px 20px 10px 10px;
 color: black;
 font-size: 17px;
 font-weight: bold;
 overflow: hidden;
 /* Truncates the text and adds a trailing ellipsis (...) if it overflows */
 text-decoration: none;
 text-overflow: ellipsis;
 white-space : nowrap;
 -webkit-background-size: auto auto, auto 100%;
}

/* Defines the look of table items when tapped */
a.arrow:active {
 background-color: #ff5000;
 /* Reverses the color of the chevron and text */
 background-image: url('../images/chevron_white.png'), url('../images/link.png');
 color: white;
}

* {
 /* Disables the default tap highlight color */
 -webkit-tap-highlight-color: rgba(0,0,0,0);
 /* Disable text selection and the copy-and-paste callout */
 -webkit-touch-callout: none;
 -webkit-user-select: none;
}

Figure 8 shows the before and after HTML lists.

Figure 8. HTML lists, before and after
HTML lists, before and after

Again, the iPhone-specific CSS is included in the Feature Pack 1 Madisons Mobile enhancements store archive. It is found in the mobile/iPhone/css directory under the store directory, once the store archive has been published.

Table 1 shows a summary of the server-side changes.

Table 1. Summary of server-side changes
PathDescription
WC_eardir/xml/config/com.ibm.commerce.foundation/wc-devices.xmlMapping for iPhone (UIWebView) user agent
WC_eardir/Stores.war/WEB-INF/struts-config-ext.xmlStruts configuration entries for iPhone
WC_eardir/Stores.war/Madisons/mobile/include/JSTLEnvironmentSetup.jspfSet CSS and style paths for iPhone
WC_eardir/Stores.war/Madisons/mobile/iPhone/css/common1_1.cssMain iPhone-specific CSS
WC_eardir/Stores.war/Madisons/mobile/iPhone/css/general.css
WC_eardir/Stores.war/Madisons/mobile/iPhone/css/table.css
...
Additional CSSs included by main iPhone-specific CSS
WC_eardir/Stores.war/Madisons/mobile/iPhone/images/chevron.png
WC_eardir/Stores.war/Madisons/mobile/iPhone/images/chevron_white.png
...
iPhone-specific CSS images
WC_eardir/Stores.war/Madisons/mobile/iPhone/include/styles/style1/CachedHeaderDisplay.jspReplacement page header for iPhone
WC_eardir/Stores.war/Madisons/mobile/iPhone/include/styles/style1/CachedFooterDisplay.jspReplacement page footer for iPhone

Application delegate and view hierarchy

We used the Interface Builder to define the view hierarchy of the Madisons iPhone application in the application's main NIB file (MainWindow.xib). It is composed of a set of views managed by the native navigation and tab bar interfaces - composite views controlled by UINavigationController and UITabBarController instances. At the top of the view hierarchy is a tab bar interface, right under the application's UIWindow instance. The tab bar interface is controlled by a UITabBarController instance, which constructs and manages the views under the tab bar interface, including the tab bar and its tabs, with each tab having a stack of views managed by a navigation interface. Each navigation interface is controlled by a UINavigationController instance, which constructs and manages the views under the navigation interface, including the navigation bar and its stack of views.

tructure of MainWindow.xib

  • UITabBarController - controls the tab bar interface
    • UITabBar
    • UINavigationController - controls the navigation interface of the home tab
      • UINavigationBar
      • WebViewController - controls the home page, i.e. root view of the home tab navigation interface
        • UINavigationItem
      • UITabBarItem - defines the title/image of the home tab
    • UINavigationController - controls the navigation interface of the store locator tab
      • ...
      • ...
    • UINavigationController - controls the navigation interface of the store locator tab
      • ...
      • ...

As you can see in Figure 9, the top view of each navigation interface is a view controlled by a custom view controller (class WebViewController). The view is defined in its own NIB file (WebViewController.xib) and is composed of a UIWebView instance and a UIActivityIndicatorView instance. When the application launches, the application delegate instructs each of these WebViewController instances to load the initial web content of each tab, such as http://host/webapp/wcs/stores/servlet/mIndex_storeId_catalogId_langId for the Home tab.

Figure 9. The application's view hierarchy
The application's view hierarchy

Listing 5 shows the code used in the TestUIShellAppDelegate class to specify the initial URL of each tab's root view.

Listing 5. TestUIShellAppDelegate - applicationDidFinishLaunching
- (void)applicationDidFinishLaunching:(UIApplication *)application {
 
 NSString *langId = NSLocalizedString(@"langId", @"");
 
 WebViewController *webViewController
  = (WebViewController *)homeTabNavigationController.topViewController;
 NSString *urlString = [NSString
  stringWithFormat:@"http://%@/webapp/wcs/stores/servlet/mIndex_%@_%@_%@",
  host, storeId, catalogId, langId];
 NSURLRequest *request = [NSURLRequest
  requestWithURL:[NSURL URLWithString:urlString]];
 webViewController.request = request;
 
 webViewController
  = (WebViewController *)storeLocatorTabNavigationController
  .topViewController;
 urlString = [NSString
  stringWithFormat:@"http://%@/webapp/wcs/stores/servlet/mStoreLocatorView?
   catalogId=%@ \
  &fromPage=&storeId=%@&langId=%@",
  host, catalogId, storeId, langId];
 request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
 webViewController.request = request;
 
 // Similar code for the other tabs...
 
 [window addSubview:tabBarController.view];
 [window makeKeyAndVisible];
 
}

Listing 6 shows the code used in the WebViewController class to load the URL request.

Listing 6. WebViewController - viewDidLoad
// Implement viewDidLoad to do additional setup after loading the view, typically from a
// nib.
// The view is loaded from the NIB file when the view first appears,
// or when it needs to reappear after being unloaded due to memory warning
- (void)viewDidLoad {
 [super viewDidLoad];
 // Load the request
 [webView loadRequest:request];
}

Navigation interface

Navigation among UIWebView instances (for example, the user navigating from the home screen to the category screen by tapping on a category) is managed by UINavigationController and WebViewController instances. To recap, each UIWebView instance in the application's view hierarchy is controlled by a WebViewController instance. The WebViewController instance is also configured as the delegate of the UIWebView instance (the WebViewController class implements the UIWebViewDelegate protocol), allowing the WebViewController instance to intercept URL requests made by the web content of the UIWebView instance.

For example, when the user taps a category on the home screen (see figure 10), the web content of the UIWebView instance (the Madisons Mobile home page) makes a URL request to load the category page. The WebViewController instance, acting as the delegate of the UIWebView instance, intercepts the URL request, pushes a new WebViewController instance onto the navigation stack of the UINavigationController instance, and prompts the new WebViewController instance to load the URL request in its UIWebView instance.

Figure 10. Navigation interface
Navigation interface

When loading starts, the UIActivityIndicatorView instance is shown to indicate that the screen is loading. When loading is finished, the UIActivityIndicatorView instance is hidden and the document title is extracted from the web content via the stringByEvaluatingJavaScriptFromString: method and set as the title of the screen. The back button on the navigation bar is automatically programmed to pop the top view controller from the navigation stack when tapped.

Listing 7 shows the code used in the WebViewController class to intercept and handle normal URL requests.

Listing 7. Excerpt of WebViewController.m - handling normal URL requests
- (BOOL)webView:(UIWebView *)aWebView
 shouldStartLoadWithRequest:(NSURLRequest *)aRequest
 navigationType:(UIWebViewNavigationType)navigationType {
 if (webView.request == nil || [activityIndicatorView isAnimating]) {
  // Continue loading if it is the initial request of the web view or if it is 
  // a redirect (in which case loading is not "finished")
  return YES;
 }
 else if (...) {
  // Code to intercept other URL requests
 }
 else {
  // Push a new WebViewController instance onto the navigation stack and use it 
  // to load the request
  WebViewController *newWebViewController
   = [[WebViewController alloc] initWithNibName:@"WebViewController" bundle:nil];
  [webViewController.navigationController
   pushViewController:newWebViewController animated:YES];
  [newWebViewController release];
  return NO;
 }
 return NO;
}

- (void)webViewDidStartLoad:(UIWebView *)aWebView {
 webView.hidden = YES;
 [activityIndicatorView startAnimating];
} 

- (void)webViewDidFinishLoad:(UIWebView *)aWebView {
 // Extract the document title from the web content and set it as the title 
 // of the view
 if (self.navigationItem.title == nil) {
  self.navigationItem.title
   = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
 }
 webView.hidden = NO;
 [activityIndicatorView stopAnimating];
}

Swipeable product details screen

To enhance the usability of the product details screen, we defined a custom view controller (class ProductScrollViewController) to give users the ability to scroll horizontally among products by swiping. Its view contains a UIScrollView instance, a scrollable view in which product details are shown side by side, and a UIPageControl instance, which acts as the page indicator.

When the user taps a product on the subcategory screen (Figure 11), the web content of the UIWebView instance (the Madisons Mobile subcategory page) will make a URL request to load the product details page. The WebViewController instance, acting as the delegate of the UIWebView instance, intercepts the URL request, and pushes a new ProductScrollViewController instance instead of the typical WebViewController instance onto the navigation stack.

Figure 11. Swipeable product details screen
Swipeable product details screen

When its view finishes loading with an empty UIScrollView instance, the ProductScrollViewController instance starts loading the product details views using WebViewController instances and adds them as subviews under the UIScrollView instance. The ProductScrollViewController instance starts by loading the views of the current product as well as the ones previous and next to it. When the user scrolls the UIScrollView instance, the ProductScrollViewController instance loads the views of the new product (if not already loaded) as well as the ones previous and next to it.

Listing 8 shows the code used in the ProductScrollViewController class to handle page scrolling.

Listing 8. Excerpt of ProductScrollViewController.m
- (void)viewDidLoad {
	
 [super viewDidLoad];
 
 pageControl.numberOfPages = [urls count];
 pageControl.currentPage = currentPage;
 
 scrollView.pagingEnabled = YES;
 scrollView.contentSize
  = CGSizeMake(scrollView.frame.size.width * pageControl.numberOfPages,
  scrollView.frame.size.height);
 
 scrollView.showsHorizontalScrollIndicator = NO;
 scrollView.showsVerticalScrollIndicator = NO;
 scrollView.scrollsToTop = NO;
 scrollView.delegate = self;
 
 [scrollView setContentOffset:CGPointMake(scrollView.frame.size.width * 
  currentPage, 0)];
 [scrollView setBackgroundColor:[UIColor blackColor]];
 
 [self loadScrollViewWithPage:pageControl.currentPage - 1];
 [self loadScrollViewWithPage:pageControl.currentPage];
 [self loadScrollViewWithPage:pageControl.currentPage + 1];
}

- (void)loadScrollViewWithPage:(int)page {
 if (page < 0 || page >= pageControl.numberOfPages) {
  return;
 }
 WebViewController *controller = [viewControllers objectAtIndex:page];
 if (controller == [NSNull null]) {
  controller = [[WebViewController alloc] initWithNibName:
    @"WebViewController"
   bundle:[NSBundle mainBundle]];
  controller.navigationController
   = (UINavigationController *)self.parentViewController;
  controller.request = [NSURLRequest requestWithURL:[URLS 
   objectAtIndex:page]];
  [viewControllers replaceObjectAtIndex:page withObject:controller];
  [controller release];
 }
 if (nil == controller.view.superview) {
  CGRect frame = scrollView.frame;
  frame.origin.x = frame.size.width * page;
  frame.origin.y = 0;
  controller.view.frame = frame;
  [scrollView addSubview:controller.view];
 }
}

- (void)scrollViewDidScroll:(UIScrollView *)sender {
 if (pageControlUsed) {
  // do nothing - the scroll was initiated from the page control,
  // not the user dragging
  return;
 }
 
 // Switch the indicator when more than 50% of
 // the previous/next page is visible
 CGFloat pageWidth = scrollView.frame.size.width;
 int page = floor((scrollView.contentOffset.x - pageWidth / 2) / 
  pageWidth) + 1; 
 pageControl.currentPage = page;

 [self loadScrollViewWithPage:page - 1];
 [self loadScrollViewWithPage:page];
 [self loadScrollViewWithPage:page + 1];
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
 pageControlUsed = NO;
}

- (IBAction)changePage:(id)sender {
 int page = pageControl.currentPage;
 [self loadScrollViewWithPage:page - 1];
 [self loadScrollViewWithPage:page];
 [self loadScrollViewWithPage:page + 1];
 
 CGRect frame = scrollView.frame;
 frame.origin.x = frame.size.width * page;
 frame.origin.y = 0;
 [scrollView scrollRectToVisible:frame animated:YES];
 // Scroll initiated by page control
 pageControlUsed = YES;
}

Integration with the iPhone address book

The same technique coupled with the ability to execute JavaScript code against the web content is used by the application to allow the Madisons Mobile e-mail wish list page to access e-mail addresses in the iPhone address book. A link with a specially-crafted URL is added to the e-mail wish list page, such as testuishell:///emailpicker#callbackHandler. The protocol testuishell:// is used to distinguish it from normal HTTP requests, while fragment callbackHandler is the name of the JavaScript callback function that is executed when the action completes.

Note: There is no specific requirement on the format of the specially-crafted URL (including its protocol), other than it is syntactically correct and is distinguished from normal HTTP requests.

Figure 12. Integration with the iPhone address book
Integration with the iPhone address book

When the user taps the link (Figure 12), the page fires the specially-crafted URL request. When the WebViewController instance intercepts the URL request, it creates a new ABPeoplePickerNavigationController instance and uses its presentModalViewController:animated: method to show the iPhone address book modally. Once the user selects an e-mail address, the message peoplePickerNavigationController:shouldContinueAfterSelectingPerson:property:identifier: is sent to the WebViewController instance, which was set as the delegate of the ABPeoplePickerNavigationController instance. Upon receiving this message, the WebViewController instance calls the JavaScript callback function callbackHandler with the e-mail address as an argument. On the e-mail wish list page, the JavaScript function callbackHandler() populates the e-mail address input field with the e-mail address passed in as an argument.

Listings 9 shows the code used in the WebViewController class to intercept the testuishell:///emailpicker request and to display the iPhone address book. It also shows the code used in the class to call the JavaScript callback function when the user selects the e-mail address.

Listing 9. WebViewController.m - integration with the iPhone address book
- (BOOL)webView:(UIWebView *)aWebView
 shouldStartLoadWithRequest:(NSURLRequest *)aRequest
 navigationType:(UIWebViewNavigationType)navigationType {
 if (webView.request == nil || [activityIndicatorView isAnimating]) {
  // Continue loading if it is the initial request of the web view or if it is 
  // a redirect (in which case loading is not "finished")
  return YES;
 }
 else if ([aRequest.URL.scheme isEqualToString:@"testuishell"]) {
  if ([aRequest.URL.path isEqualToString:@"/emailpicker"]) {
   // Code to handle e-mail address selection
   // Remember the request
   self.api = aRequest.URL.path;
   // Extract the JavaScript callback function from the URL and remember it
   self.callbackHandler = aRequest.URL.fragment;
   // Create the address book view controller...
   ABPeoplePickerNavigationController *peoplePickerNavigationController
    = [[ABPeoplePickerNavigationController alloc] init];
   peoplePickerNavigationController.displayedProperties
    = [NSArray arrayWithObject:[NSNumber numberWithInt:kABPersonEmailProperty]];
   peoplePickerNavigationController.peoplePickerDelegate = self;
   // ... and present it modally
   [self presentModalViewController:peoplePickerNavigationController 
     animated:YES];
   [peoplePickerNavigationController release];
  }
  else if (...) {
   // Code to handle physical address selection
  }
 }
 else if (...) {
  // Code to intercept other URL requests
 }
 else {
  // Code to intercept normal URL requests
  // See listing 7
 }
 return NO;
}

- (void)peoplePickerNavigationControllerDidCancel:
 (ABPeoplePickerNavigationController *)peoplePicker {
 [self dismissModalViewControllerAnimated:YES];
}

- (BOOL)peoplePickerNavigationController:
 (ABPeoplePickerNavigationController *)peoplePicker
 shouldContinueAfterSelectingPerson:(ABRecordRef)person {
 if ([api isEqualToString:@"/emailpicker"]) {
  // Continue to show the address book until an e-mail address has been 
  // selected
  return YES;
 }
 else if (...) {
  // Code to handle physical address selection
 }
}

- (BOOL)peoplePickerNavigationController:
 (ABPeoplePickerNavigationController *)peoplePicker
  shouldContinueAfterSelectingPerson:(ABRecordRef)person
  property:(ABPropertyID)property
  identifier:(ABMultiValueIdentifier)identifier {
 if ([api isEqualToString:@"/emailpicker"]) {
  // E-mail address has been selected, extract e-mail address from address book
  ABMultiValueRef emails = ABRecordCopyValue(person, property);
  CFIndex index = ABMultiValueGetIndexForIdentifier(emails, identifier);
  CFStringRef email = ABMultiValueCopyValueAtIndex(emails, index);
  // Construct the JavaScript callback (i.e. callback function + argument)
  NSString *callback = [NSString stringWithFormat:@"%@('%@')", callbackHandler, 
   email];
  [webView stringByEvaluatingJavaScriptFromString:callback];
  CFRelease(email);
  CFRelease(emails);
  // Dismiss the address book view controller
  [self dismissModalViewControllerAnimated:YES];
 }
 return NO;
}

Listing 10 shows the button added to the EmailWishList.jsp file to trigger the native code. It also shows the JavaScript callback function added to the file to handle the callback from the native code.

Listing 10. EmailWishlist.jsp - integration with the iPhone address book
<c:if test="${_iPhoneNativeApp == true}">
 <a class="button"
  href="testuishell:///emailpicker#emailPickerCallbackHandler">
  Address Book
 </a>
 <script type="text/javascript">
  function emailPickerCallbackHandler(email) {
   document.getElementById("recipient").value = email;
  }
 </script>
</c:if>

Integrated map views

You can use the URL request interception technique that is used to show the scrollable product details screen to replace web content with native views, as in the case of store maps. The Madisons Mobile store locator is customized to provide store map links to Google® Maps. In Mobile Safari, such URL requests are routed to the native Maps application where the store maps are shown. This works in embedded web content as well, but causes the application to exit and the Maps application to launch. To prevent that from happening, the application intercepts such an URL request, and instead, shows the store map using an MKMapView instance and a custom UIViewController instance (class MapViewController) (Figure 13).

Figure 13. Integrated map views
Integrated map views

Listing 11 shows the code used in the WebViewController class to intercept and handle requests normally routed to the native Maps application.

Listing 11. WebViewController.m - integrated map views
- (BOOL)webView:(UIWebView *)aWebView
 shouldStartLoadWithRequest:(NSURLRequest *)aRequest
 navigationType:(UIWebViewNavigationType)navigationType {
 if (webView.request == nil || [activityIndicatorView isAnimating]) {
  // Continue loading if it is the initial request of the web view or if 
  // it is a redirect (in which case loading is not "finished")
  return YES;
 }
 else if (...) {
  // Code to intercept other URL requests
 }
 else if ([aRequest.URL.host isEqualToString:@"maps.google.com"]) {
  // Code to intercept Google Maps URL requests
  // Extract latitude, longitude and query (i.e. the address) from 
  // the URL
  NSString *ll = nil;
  NSString *q = nil;
  NSString *query = aRequest.URL.query;
  NSArray *parameters = [query componentsSeparatedByString:@"&"];
  for (NSString *parameter in parameters) {
   if ([parameter hasPrefix:@"ll="]) {
    ll = [[[parameter substringFromIndex:3]
     stringByReplacingOccurrencesOfString:@"+" withString:@" "]
     stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
   }
   if ([parameter hasPrefix:@"q="]) {
    q = [[[parameter substringFromIndex:2]
     stringByReplacingOccurrencesOfString:@"+" withString:@" "]
     stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
   }
  }
  NSArray *latitudeAndLongitude = [ll componentsSeparatedByString:@","];
  NSArray *addressAndName = [q componentsSeparatedByString:@" ("];
  // Create the map view controller
  MapViewController *mapViewController
   = [[MapViewController alloc] initWithNibName:@"MapViewController"
   bundle:[NSBundle mainBundle]];
  // Create the annotation pin
  MyAnnotation *annotation = [[MyAnnotation alloc] init];
  CLLocationCoordinate2D coordinate;
  coordinate.latitude = [[latitudeAndLongitude objectAtIndex:0] 
   doubleValue];
  coordinate.longitude = [[latitudeAndLongitude objectAtIndex:1] 
   doubleValue];
  annotation.coordinate = coordinate;
  annotation.title = [addressAndName objectAtIndex:1];
  annotation.title
   = [annotation.title substringToIndex:annotation.title.length - 1];
  annotation.subtitle = [addressAndName objectAtIndex:0];
  mapViewController.annotation = annotation;
  [annotation release];
  // Push the map view controller onto the navigation stack
  UINavigationController *theNavigationController
   = (UINavigationController *)self.parentViewController;
  if (theNavigationController == nil) {
   theNavigationController = self.navigationController;
  }
  [theNavigationController pushViewController:mapViewController
   animated:YES];
  [mapViewController release];
  return NO;
 }
 else if (...) {
  // Code to intercept other URL requests
 }
 else {
  // Code to intercept normal URL requests
  // See listing 7
 }
 return NO;
}

Geolocation support

Since iPhone OS 3, the HTML5 geolocation API is supported by Mobile Safari and UIWebView. This is used by the application to support the store location by geolocation without the use of native code (Figure 14).

Figure 14. Geolocation support
Geolocation support

Listing 12 shows the JavaScript code used to obtain the geolocation information.

Listing 12. Sample JavaScript code to perform geolocation
function captureCurrentLocation() {
 // Get location no more than 10 minutes old. 600000 ms = 10 minutes.
 navigator.geolocation.getCurrentPosition(showLocation, showError,
  {enableHighAccuracy:true,maximumAge:600000});
}

function showLocation(position) {
 var fromForm = document.getElementById("store_locator_gps_form");
 var geoCodeLatitude = fromForm.geoCodeLatitude;
 var geoCodeLongitude = fromForm.geoCodeLongitude; 
 geoCodeLatitude.value = position.coords.latitude;
 geoCodeLongitude.value = position.coords.longitude;
 fromForm.action = "mStoreLocatorResultView";
 fromForm.submit();
}

Adapting the application for iPads

The hybrid model is applicable to building iPad® applications as well by using the same techniques. Although iPhone applications can run on iPads as-is, it is a good idea to create an iPad-specific version of the application (such as "Madisons HD"), or enhance the existing application to support both iPhones and iPads to take advantage of the new platform. Be aware that the iPad has its own set of human interface guidelines that are quite different from the iPhone ones. You need to factor that into the application's design - for example, the iPad HIG puts an emphasis on reducing full-screen transitions, which you can achieve by an increased use of split screens and popups. This is done by using the device detection mechanism described earlier to distinguish between requests from iPhones and iPads.


Case study 2. Building an Android application

Android is an open source, software stack that has been optimized for mobile devices under the guidance of the Open Handset Alliance. It is based on a version of the Linux kernel and provides a software development kit that is based on a version of Java™ as a development language. It includes a set of libraries and a number of development utilities. It uses the open source WebKit engine as the basis of its browser, and also provides web views embedded within applications (an approach shared with the iPhone, which also uses WebKit).

An application typically consists of one or more "activities,", each of which corresponds to a window or dialog. Screen layouts are created dynamically by Java code, or described statically in XML files. Another key concept in Android is the "intent,", which is a system message. Applications may register to receive certain types of intents (such as an incoming phone call), and may in turn generate intents for other applications to catch. The application model also includes "content providers,", which provide access to data stored on the device, and "services", which normally operate in the background to monitor or process various classes of events. The Android application model encourages a loose coalition of applications, content providers, and services that can interoperate, to make it easy for an application to take advantage of other applications, content, and services available on the device.


High-level design

The hybrid approach being described here involves creating a stand-alone application, written in Java, but which incorporates most of the content a user will see as a web view, served by the WebSphere Commerce server with minimal customization. In those cases where special access to a device capability is needed (such as access to the GPS or other location-determination mechanism in the store locator), the web page delivered by the server may contain JavaScript functions that call native (Java) library functions, which in turn obtain and return the requested information. This approach implies the existence of one or more of these "bridging libraries" that contain JavaScript-callable methods to access local device capabilities. Of course, it is immediately apparent that if these Android-specific libraries are constructed to present the same API as the iPhone-specific libraries described in Case study 1, in most cases, the same server-side content may be used for both platforms, without change.


Look and feel

By far, the greatest amount of screen real estate on the mobile device is taken up by a WebView, which is essentially a web page embedded within the native application skeleton. Many of the same considerations that apply to the iPhone also apply to Android devices, since most are touch enabled. This implies that buttons and links must all be sufficiently large so that they are selected by the touch of a finger. Similarly, text must be large enough to be read easily. Most of these aspects of presentation are controlled by style sheets, or CSS, embedded within the web page supplied by the WebSphere Commerce server.

One difference is that while the iPhone model currently supports only two screen sizes, one for the iPhone and iPod Touch, and the other for the iPad, Android devices embody a much more diverse range of screen sizes. This has implications in selecting appropriate image sizes for the content page, as well as in selecting font and button sizes.


The application menu

One of the typical Android conventions that is difficult to incorporate within a pure web application is the application menu, something that for most devices is activated by a dedicated hardware key. Consequently, the provision of the application menu is left to the native skeleton. Figure 15 shows an example of what this might look like.

Figure 15. Madisons Android application - application menu
Madisons Android application - application menu

Listing 13 shows the definition of the menu illustrated here. Android, by default, displays at most six menu items. If more are defined, then it substitutes an item labeled "More," as shown in Figure 15. Touching that selection displays a sub-menu with additional menu items.

Listing 13. Application menu XML
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
 <item android:id="@+id/locator"
  android:title="@string/StoreLocator"
  android:icon="@drawable/ic_menu_compass">
 </item>
 <item android:id="@+id/wishlist"
  android:title="@string/WishList"
  android:icon="@drawable/ic_menu_save">
 </item>
 <item android:id="@+id/cart"
  android:title="@string/ShoppingCart"
  android:icon="@drawable/ic_menu_cart">
 </item>
 <item android:id="@+id/home"
  android:title="@string/Home"
  android:icon="@drawable/ic_menu_home">
 </item>
 <item android:id="@+id/scan"
  android:title="@string/Scan"
  android:icon="@drawable/ic_menu_scan">
 </item>
 <item android:id="@+id/account"
  android:title="@string/MyAccount"
  android:icon="@drawable/ic_menu_account">
 </item>
 <item android:id="@+id/contact"
  android:title="@string/Contact">
 </item>
</menu>

Code is required in the native portion of the application to handle menu events. Listing 14 and Listing 15 show how the menu itself is instantiated.

Listing 14. Instantiating the application menu
@Override
public boolean onCreateOptionsMenu(Menu menu) {
 new MenuInflater(getApplication()).inflate(R.menu.options, menu);
 return (super.onCreateOptionsMenu(menu));
}
Listing 15. Handling menu events
@Override
public boolean onOptionsItemSelected(MenuItem item) {
 String message = "No option selected";
 
 if (item.getItemId() == R.id.home) {
  // Handle "Home" menu item
  mWebView.loadUrl(homePage);
  return (true);
 } else if (item.getItemId() == R.id.cart) {
  // Handle "Shopping Cart" menu item
  mWebView.loadUrl(cartURL);
  return true;
 } else ...  // Other handlers not shown
 
 Toast.makeText(this, message, Toast.LENGTH_LONG).show();
 return (super.onOptionsItemSelected(item));
}

Access to the local address book

Access to the phone's contact list is handled in the same way as described previously for the iPhone. The actual JavaScript that accesses the address book is different in the Android case, but the interface to it is identical between the two prototypes, so that the page served by WebSphere Commerce is identical in both cases.


Barcode scanning

Barcode scanning is an area that is a useful design characteristic of Android for collaboration among applications. In this example of native code, the "Scan" menu item calls the zxing ("Zebra Crossing") barcode library method IntentIntegrator to activate the Barcode Scanner application, turn on the camera, take a picture of a barcode (here, a 2D QR code), decode the image on the device, and return the result to the application. The application interprets the result as the URL of a product page to be displayed in the web view. If by some chance the user's device does not come with a pre-installed Barcode Scanner, this method asks if it needs to be downloaded and installed here and now. See Listing 16.

Listing 16. Barcode scanning
import com.google.zxing.integration.android.IntentIntegrator;
...
// Barcode scan
private void barcodeScan() {
  IntentIntegrator.initiateScan(MyActivity.this); 
}

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 switch (requestCode) {
  case IntentIntegrator.REQUEST_CODE: {
   if (resultCode == RESULT_OK) {
    IntentResult scanResult = IntentIntegrator.parseActivityResult(
     requestCode, resultCode, data);
    if (scanResult != null) {
     String qrCode = scanResult.getContents();
     // Process the returned string.
     mWebView.loadUrl(qrCode);
    }
   }
   break;
  }
 }
}

The code In Listing 16 scans a 2-D QR barcode, interprets that as the URL of a product page, and retrieves that page from the WebSphere Commerce server. Figure 16 shows a typical QR code and the corresponding product page that is retrieved from the WebSphere Commerce server.

Figure 16. QR code and the corresponding product page
QR code and the corresponding product page

Store locator

As with the iPhone version, it is useful to allow the user to find a store relative to the current location of the device as an option.

The browser implementation on Android devices is based on the same open source WebKit browser engine used by iPhone (and a number of other mobile platforms, as well). Current Android versions also support the HTML 5 geolocation capability, so you can use it without the need for native code, as described above for the iPhone.


Conclusion

With the continuing rapid growth in mobile devices and applications, and especially in sales of smartphones and deployment of higher-speed networks, we anticipate that mobile commerce will play an increasingly important role for consumers. In such an environment, being able to provide a compelling experience for mobile consumers is one of the key differentiators for a retailer. This article has outlined one approach to incorporate mobile commerce as a multi-channel strategy.

Resources

Learn

  • A companion paper, Leveraging mobile commerce in your multi-channel Strategy, discusses the market opportunity in mobile commerce, and sketches some approaches to taking advantage of those opportunities within WebSphere Commerce.
  • The iPhone Dev Center is the starting place when developing for the iPhone, iPod Touch, or iPad devices.
  • Guidance on user interface design for the iPhone family is provided at the iPhone Human Interface Guidelines site.
  • The Android developer site is the place to get started when developing for the Android platform.
  • PhoneGap is an open source development framework that allows web applications to use JavaScript to access native device capabilities and supports multiple mobile platforms.

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 WebSphere on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere, Industries
ArticleID=518321
ArticleTitle=Building mobile applications for WebSphere Commerce using the hybrid application programming model
publish-date=09222010