The Salesforce Mobile SDK supports three development approaches for building mobile apps: native, HTML5, and hybrid. This article gives you everything you need to get started building hybrid mobile apps with the Salesforce Mobile SDK.

Overview

A native mobile app is written in a low-level programming language such as Objective-C (for iOS) or Java (for Android), compiled to run natively on the device. Native mobile apps have access to all device functions, such as the camera and accelerometer, and are typically distributed via an app store.

A mobile web app, in contrast, is written in HTML5, CSS3 and JavaScript, and runs in the device browser. Web apps run in web pages, served on-demand by web servers (like any other web page) and have access to a very limited amount of device functionality such as geolocation and local storage, via HTML5.

Hybrid mobile apps combine aspects of the two, written in HTML5, CSS3 and JavaScript, but with access to device functions via a mobile development framework. Typically, a significant proportion of hybrid app code can be leveraged across a range of device platforms. Hybrid apps are packaged as native apps and can be distributed via an app store.

The Salesforce Mobile SDK builds on Apache Cordova (formerly known as PhoneGap) for hybrid app development, targeting (at present) iOS and Android. Apache Cordova is an open source mobile development framework that allows you to build apps using web technologies such as HTML5, CSS3 and JavaScript, access native device functionality such as the camera or contact list, and package them for distribution just like native apps written in Objective C or Java.

This article explains how to get started developing hybrid apps with the Salesforce Mobile SDK and shows how a hybrid app for iOS or Android can access the device camera and upload image data to a Force.com Contact record in just a few lines of JavaScript.

Prerequisites

For all target devices, you will need:

If you are developing apps for iOS devices, you will also need Xcode 4.2 or above.

If you are developing apps for Android devices, you will also need:

Note that this article is based on version 1.1 of the Salesforce Mobile SDK, released on March 14, 2012. If you downloaded version 1.0 of the SDK, rename or move the old SDK source directories and download the new code as described below.

Installing the Salesforce Mobile SDK for iOS

Clone the SalesforceMobileSDK-iOS project from GitHub:

git clone https://github.com/forcedotcom/SalesforceMobileSDK-iOS.git

Run the install script from the Terminal command line:

cd SalesforceMobileSDK-iOS
./install.sh

The install script pulls submodule dependencies from GitHub, and builds all the library files you will need. It also installs Xcode project templates in the default Xcode template location.

Installing the Salesforce Mobile SDK for Android

Clone the SalesforceMobileSDK-Android project from GitHub:

git clone https://github.com/forcedotcom/SalesforceMobileSDK-Android.git

Mac OS X/Linux

Run the install script from the Terminal command line:

cd SalesforceMobileSDK-Android
./install.sh

Windows

Run the install script from the Windows command line:

cd SalesforceMobileSDK-Android
cscript install.vbs

Create a Hybrid Mobile Application

First, you will configure a Remote Access Application in Force.com. Doing so registers your app with Force.com and allows it to access Force.com APIs on behalf of users via the OAuth 2.0 protocol.

Create a new Force.com Developer Edition (DE) account, if you do not already have one. You will need to provide a working email address so that you can complete the account setup process and set a password.

Log in to your DE account, and navigate to Remote Access (Your Name | Setup | App Setup | Develop | Remote Access). Click 'New' and enter the following information:

  • Application: Give your app a name, e.g. My Hybrid App.
  • Contact Email: Your email address.
  • Callback URL: for iOS, use https://login.salesforce.com/services/oauth2/success; for Android use myapp:///mobilesdk/detect/oauth/done. You should change myapp to be specific to your application.

You can also enter a description for your app, the URL of a logo image and other optional information.

RemoteAccessSetup.png

Click Save. You will see the details of the Remote Access App you just created. Note the Consumer Key; you will need this value when you set up your hybrid app shortly. Mobile apps do not use the Consumer Secret, so you can just ignore this value.

RemoteAccessDetail.png

Creating a Hybrid App Project for iOS

In Xcode, create a new "Hybrid Force.com App" project (Command-Shift-N in Xcode). These parameters are required:

  • Consumer Public Key: The Consumer Key from your Remote Access app.
  • OAuth Redirect URL: The Callback URL from your Remote Access app.
  • Company Identifier: Something like com.mycompany.foo - this should correspond with an App ID you created in your Apple iOS dev center account.
  • Use Automatic Reference Counting: Uncheck

At the time of writing, there is a bug in the Hybrid Force.com App template that causes the app to be incorrectly packaged. If you try to run the app, it will fail with "ERROR: Start Page at 'www/bootstrap.html' was not found" in the output console in Xcode.

To fix this, you must right click the yellow www folder and delete it, clicking the Remove References Only button.

YellowWwwFolder.png

Right click your project folder, select Add Files to "My Project" and navigate to the www directory inside the project directory. Ensure that Create folder references for any added folders is selected. Notice that the www folder is now shown in blue.

BlueWwwFolder.png

Now the app will run correctly.

Creating a Hybrid App Project for Android

In Eclipse, select File | Import | General | Existing Projects into Workspace. Locate the sample ContactExplorer project: SalesforceMobileSDK-Android/hybrid/SampleApps/ContactExplorer and select it as the root directory. Ensure that 'Copy projects into workspace' is checked, and click Finish.

To avoid confusion with the standard ContactExplorer sample, right click the ContactExplorer project and rename it. Open res/values/strings.xml, and edit app_name to match the new project name.

To configure your app with its Remote Access parameters, open assets/www/bootconfig.js and edit the following values:

  • remoteAccessConsumerKey: The Consumer Key from your Remote Access app.
  • oauthRedirectURI: The Callback URL from your Remote Access app.

You will need to create an Android Virtual Device, if you have not already done so. In Eclipse, select Window | AVD Manager and click New. You can enable camera support in the device if you wish.

Running the Sample Hybrid App

You should now be able to compile and run the sample project, either on the simulator or a physical device. In both environments, you can select either a connected physical device or a simulator on which to run the app. If you are using an iOS device you must configure a profile as described in the Xcode 4 User Guide. Similarly, Android devices must be set up as described in the Android developer documentation.

However you run the app, after showing an initial 'splash screen', you should see the Salesforce login screen.

MobileLoginScreen.png

Login with your DE username and password, and you will be prompted to allow your app access to your data in Salesforce.

MobileAuthzScreen.png

Click Allow and you should be able to retrieve lists of contacts and accounts from your DE account.

SampleHybridApp.png

Click the buttons to retrieve Contact and Account records from your DE account.

SampleHybridContacts.png

Notice that the app can also retrieve contacts from the device - something that an equivalent web app would be unable to do. Let's take a closer look at how the app can do this.

How the Sample App Works

After completing the login process, the sample app displays index.html (located in the www folder). When the page has completed loading and the mobile framework is ready, the onDeviceReady() function calls regLinkClickHandlers() (in inline.js). regLinkClickHandlers() sets up five click handlers for the various functions in the sample app.

$j('#link_fetch_device_contacts').click(function() {
    SFHybridApp.logToConsole("link_fetch_device_contacts clicked");
    var options = new ContactFindOptions();
    options.filter = ""; // empty search string returns all contacts
    options.multiple = true;
    var fields = ["name"];
    navigator.contacts.find(fields, onSuccessDevice, onErrorDevice, options);
});

This handler calls find() on the navigator.contacts object to retrieve the contact list from the device. The onSuccessDevice() renders the contact list into the index.html page.

$j('#link_fetch_sfdc_contacts').click(function() {
    SFHybridApp.logToConsole("link_fetch_sfdc_contacts clicked");
    forcetkClient.query("SELECT Name FROM Contact", onSuccessSfdcContacts, onErrorSfdc); 
});

The #link_fetch_sfdc_contacts handler runs a query using the forcetkClient object. This object is set up during the initial OAuth 2.0 interaction, and gives access to the Force.com REST API in the context of the authenticated user. Here we retrieve the names of all the contacts in the DE account and onSuccessSfdcContacts() renders them as a list on the index.html page.

$j('#link_fetch_sfdc_accounts').click(function() {
    SFHybridApp.logToConsole("link_fetch_sfdc_accounts clicked");
    forcetkClient.query("SELECT Name FROM Account", onSuccessSfdcAccounts, onErrorSfdc); 
});

The #link_fetch_sfdc_accounts handler is very similar to the previous one, fetching Account records via the Force.com REST API. The remaining handlers, #link_reset and #link_logout, clear the displayed lists and log out the user respectively.

Customize your Hybrid Mobile Application to Use the Device Camera

Clone the GlueCon 2012 Salesforce Mobile SDK Demo project into a new directory:

git clone https://github.com/metadaddy-sfdc/GlueCon2012-Salesforce-Mobile-SDK-Demo.git

Copy the files and images from GlueCon2012-Salesforce-Mobile-SDK-Demo/www directory into your hybrid app's www directory, overwriting forcetk.js, index.html and inline.js, and creating a new images folder.

Before you run the app, you will also need to make a couple of customizations to the Contact standard object in your DE account. As explained below, the app uploads images to ContentVersion records, and updates the Contact record with the new ContentVersion record's ID. You will need to create one custom field to hold the image ID, and another to display the image on the Contact Page Layout.

Login to your DE account and select Your Name | Setup | App Setup | Customize | Contacts | Fields. Scroll down to Contact Custom Fields & Relationships and click New. Select Text as the field type and click Next. On the next screen, enter the following:

  • Field Label: Image ID
  • Length: 18

Click Next, then click Next again to accept the field-level security defaults. On the next screen, uncheck all of the page layouts - we don't want the Image ID field to be visible to the user - and click Save & New.

Now select Formula as the field type and click next. On the next screen, enter the following:

  • Field Label: Image
  • Formula Return Type: Text

Click Next and enter the following as the formula:

IF( Image_ID__c != null , 
    IMAGE( '/sfc/servlet.shepherd/version/download/' & Image_ID__c, '') , 
    IMAGE('' , '' , 0 , 0))

Click Next, then click Next again to accept the field-level security defaults, and click Save to accept the page layout defaults - the Image field should be displayed on all page layouts.

Now you are ready to run the demo app and upload images to contacts! Since the demo app uses the camera, if you are targeting iOS, you will need to run it on a physical device (there is no camera in the iOS simulator). Launch the app, login if necessary, and click on the Fetch SFDC contacts button. In the demo app, you can click on a contact in the list to access a contact detail page, including a placeholder for a photo of the contact.

SampleHybridContact.png

Tap the placeholder image and the camera will activate. Take a picture, and the contact detail page will be updated with the image, the image data will be uploaded to a ContentVersion record, and associated with the contact.

SampleHybridContact2.png

If you login to your DE account from a browser and click the Contacts tab, you will see the contact in the Recent Contacts list. Click on the contact and you will see the photo alongside the standard Contact data.

ContactWithImage.png

How the Demo App Works

Looking at index.html and inline.js, we can see a number of differences from the sample app. The device contacts and accounts lists have been removed from index.html, and there is a new 'Contact Detail' page comprising an image and Name, Account Name and Phone Number fields.

In inline.js, look at onSuccessSfdcContactList() first.

function onSuccessSfdcContactList(response) {
    var $j = jQuery.noConflict();

    SFHybridApp.logToConsole("onSuccessSfdcContactList: received " + 
            response.totalSize + " contacts");

    $j("#div_sfdc_contact_list").html("")
    var ul = $j('<ul data-role="listview" data-inset="true" data-theme="a" data-dividertheme="a"></ul>');
    $j("#div_sfdc_contact_list").append(ul);

    ul.append($j('<li data-role="list-divider">Salesforce Contacts: ' + 
            response.totalSize + '</li>'));
    $j.each(response.records, function(i, contact) {
        var id = contact.Id;
        var newLi = $j("<li><a href='#'>" + (i+1) + " - " + contact.Name + "</a></li>");
        newLi.click(function(e){
            e.preventDefault();
            $j.mobile.showPageLoadingMsg();
            forcetkClient.query("SELECT Id, Name, Account.Name, Phone, Image_ID__c "+
                    "FROM Contact WHERE Id = '"+id+"'", 
                    onSuccessSfdcSingleContact, onErrorSfdc);
        });
        ul.append(newLi);
    });

    $j("#div_sfdc_contact_list").trigger( "create" )
}

For each record passed in in the response, we create a list item with its name, and set up a click handler on the list item to retrieve the contact's name, account name, phone number, and image.

The query callback, onSuccessSfdcSingleContact(), populates the contact detail page. Notice the code to display the contact image:

    //Set up image
    $j('#Image').attr('data-id', contact.Id);
    $j('#Image').attr('data-name', contact.Name);
    if (contact.Image_ID__c) {
        // Load image data
        $j('#Image').attr('src', "images/loading.png");
        
        $j.mobile.changePage('#jqm-detail');
        
        forcetkClient.retrieveBlobField("ContentVersion", 
                contact.Image_ID__c, "VersionData", function(response) {
            var base64data = base64ArrayBuffer(response);
            $j('#Image').attr('src', "data:image/png;base64,"+base64data);
            $j.mobile.hidePageLoadingMsg();
        }, onErrorSfdc);
    } else {
        // Display a default image
        $j.mobile.hidePageLoadingMsg();
        $j('#Image').attr('src', "images/blank.png");
        $j.mobile.changePage('#jqm-detail');
    }

The contact id and name are set as attributes on the image element, and, if there is an ID in the Image_ID__c custom field, a 'loading' image is displayed, and the image data is retrieved via retrieveBlobField(). The base64ArrayBuffer() utility function converts the JavaScript ArrayBuffer object to base64-encoded data in string, and the callback sets the image data as a data URI.

Looking at regLinkClickHandlers(), this function loses the #link_fetch_device_contacts and #link_fetch_sfdc_accounts handlers, but gains a new one:

$j('#Image').click(function() {
    getPhotoAndUploadToContact($j(this).attr('data-name'), $j(this).attr('data-id'));
});

Clicking on the image calls the getPhotoAndUploadToContact() function, passing in the contact name and ID attributes from the image element.

function getPhotoAndUploadToContact(name, contactId) {
    SFHybridApp.logToConsole("in capturePhoto, contactId = "+contactId);

    $j('#Image').attr('data-old-src', $j('#Image').attr('src'));
    $j('#Image').attr('src', "images/camera.png");

    navigator.camera.getPicture(function(imageData){
        onPhotoDataSuccess(imageData, name, contactId);
    }, function(errorMsg){
        // Most likely error is user cancelling out of camera
        $j('#dialog-text').html(errorMsg);
        $j.mobile.changePage('#jqm-dialog');
        $j('#Image').attr('src', $j('#Image').attr('data-old-src'));
        $j('#Image').removeAttr('data-old-src');
    }, {
        quality: 50, 
        sourceType: Camera.PictureSourceType.CAMERA,
        destinationType: Camera.DestinationType.DATA_URL
    });
}

getPhotoAndUploadToContact() really shows the power of the hybrid approach. The getPicture() function on navigator.camera provides easy access to the device camera. The options passed to getPicture() specify the required image quality, source (camera in this case, as opposed to the device's photo library), and the format in which the picture should be returned. Camera.DestinationType.DATA_URL returns the image as a base64 encoded string, while Camera.DestinationType.FILE_URI returns a URI to a file on the device.

When a picture is successfully taken, the onPhotoDataSuccess() callback is invoked:

function onPhotoDataSuccess(imageData, name, contactId) {
    var $j = jQuery.noConflict();

    SFHybridApp.logToConsole("in onPhotoDataSuccess, contactId = "+contactId);

    // Update the image on screen
    $j('#Image').attr('src', "data:image/jpeg;base64," + imageData);

    // Upload the image data to Content
    $j.mobile.showPageLoadingMsg();
    forcetkClient.create('ContentVersion', {
        "PathOnClient" : name + ".png",
        "VersionData" : imageData
    }, function(data){
        // Now update the Contact record with the new ContentVersion Id
        SFHybridApp.logToConsole('Created ContentVersion ' + data.id);
        forcetkClient.update('Contact', contactId, { 
            "Image_ID__c" : data.id 
        }, function(){
            $j.mobile.hidePageLoadingMsg();
            SFHybridApp.logToConsole('Updated Contact '+contactId);
        }, onErrorSfdc);
    }, onErrorSfdc);    
}

After updating the on-screen image, a ContentVersion record is created. Note that we indicate the type of the data via the extension on the PathOnClient field. On successful creation, the contact is updated with the id of the new ContentVersion record.

Conclusion

The hybrid approach allows rapid cross-platform mobile application development, leveraging existing web toolkits, such as jQuery, and developers' existing web programming skills. This article showed how a hybrid app can access the device camera and upload image data to Force.com in just a few lines of JavaScript.

Resources

About the Author

Pat Patterson has been working with Internet technologies since 1997, building secure Web services and web access management software at Sun Microsystems, and cloud storage infrastructure software at Huawei Technologies. Before joining the platform developer evangelism team at salesforce.com in late 2010, Pat was best known as the community lead for the OpenSSO project. Describing himself as an ‘articulate techie’, Pat hacks all manner of code from Force.com apps down to Linux kernel drivers, writing it all up on Developer Force, Superpatterns, and, of course, Twitter.