Mobile SDK
Mobile SDK
Mobile SDK
0: Winter ’14
© Copyright 2000–2013 salesforce.com, inc. All rights reserved. Salesforce.com is a registered trademark of salesforce.com, inc., as are other
names and marks. Other marks appearing herein may be trademarks of their respective owners.
Table of Contents
Table of Contents
i
Table of Contents
ii
Table of Contents
iii
Table of Contents
Index...............................................................................................................................................146
iv
Chapter 1
Introduction to Mobile Development
In this chapter ... Force.com has proven itself as an easy, straightforward, and highly productive
platform for cloud computing. Developers can define application components,
• Intended Audience such as custom objects and fields, workflow rules, Visualforce pages, and Apex
• About Native, HTML5, and Hybrid classes and triggers, using point-and-click tools of the Web interface, and
Development assembling the components into killer apps. As a mobile developer, you might
• Enough Talk; I’m Ready be wondering how you can leverage the power of the Force.com platform to
• Development Prerequisites create sophisticated apps.
• Mobile SDK Installation
The Mobile SDK seamlessly integrates with the Force.com cloud architecture
• Keeping Up With the Mobile SDK
by providing:
Note:
Be sure to visit Salesforce Platform Mobile Services website regularly
for tutorials, blog postings, and other updates.
1
Introduction to Mobile Development Intended Audience
Intended Audience
This guide is primarily for developers who are already familiar with mobile technology, OAuth2, and REST APIs, and who
probably have some Force.com experience. But if that doesn’t exactly describe you, don’t worry. We’ve tried to make this guide
usable by a wider audience. For example, you might be a Salesforce admin tasked with developing a new mobile app to support
your organization, or you might be a mobile developer who’s entirely new to Force.com. If either of those descriptions fit you,
then you should be able to follow along just fine.
If you’re an admin setting up users for mobile devices, you’re probably looking for the Salesforce Mobile Implementation
Guide.
• Native apps are specific to a given mobile platform (iOS or Android) and use the development tools and language that
the respective platform supports (for example, Xcode and Objective-C with iOS, Eclipse and Java with Android). Native
apps look and perform best but require the most development effort.
• HTML5 apps use standard web technologies—typically HTML5, JavaScript and CSS—to deliver apps through a mobile
Web browser. This “write once, run anywhere” approach to mobile development creates cross-platform mobile applications
that work on multiple devices. While developers can create sophisticated apps with HTML5 and JavaScript alone, some
challenges remain, such as session management, secure offline storage, and access to native device functionality (such as
camera, calendar, notifications, and so on).
• Hybrid apps combine the ease of HTML5 Web app development with the power of the native platform by wrapping a
Web app inside the Salesforce container. This combined approach produces an application that can leverage the device’s
native capabilities and be delivered through the app store. You can also create hybrid apps using Visualforce pages delivered
through the Salesforce hybrid container.
2
Introduction to Mobile Development About Native, HTML5, and Hybrid Development
Native Apps
Native apps provide the best usability, the best features, and the best overall mobile experience. There are some things you get
only with native apps:
• Fast graphics API—the native platform gives you the fastest graphics, which might not be a big deal if you’re showing a
static screen with only a few elements, or a very big deal if you’re using a lot of data and require a fast refresh.
• Fluid animation—related to the fast graphics API is the ability to have fluid animation. This is especially important in
gaming, highly interactive reporting, or intensely computational algorithms for transforming photos and sounds.
• Built-in components—The camera, address book, geolocation, and other features native to the device can be seamlessly
integrated into mobile apps. Another important built-in component is encrypted storage, but more about that later.
• Ease of use—The native platform is what people are accustomed to. When you add that familiarity to the native features
they expect, your app becomes that much easier to use.
Native apps are usually developed using an integrated development environment (IDE). IDEs provide tools for building,
debugging, project management, version control, and other tools professional developers need. You need these tools because
native apps are more difficult to develop. Likewise, the level of experience required is higher than in other development
scenarios. If you’re a professional developer, you don’t have to be sold on proven APIs and frameworks, painless special effects
through established components, or the benefits of having all your code in one place.
3
Introduction to Mobile Development About Native, HTML5, and Hybrid Development
HTML5 Apps
An HTML5 mobile app is basically a web page, or series of web pages, that are designed to work on a small mobile device
screen. As such, HTML5 apps are device agnostic and can be opened with any modern mobile browser. Because your content
is on the web, it’s searchable, which can be a huge benefit for certain types of apps (shopping, for example).
If you’re new to mobile development, the technological bar is lower for Web apps; it’s easier to get started here than in native
or hybrid development. Unfortunately, every mobile device seems to have its own idea of what constitutes usable screen size
and resolution. This diversity imposes an additional burden of testing on different devices. Browser incompatibility is especially
common on Android devices, for example.
An important part of the "write once, run anywhere" HTML5 methodology is that distribution and support is much easier
than for native apps. Need to make a bug fix or add features? Done and deployed for all users. For a native app, there are
longer development and testing cycles, after which the consumer typically must log into a store and download a new version
to get the latest fix.
If HTML5 apps are easier to develop, easier to support, and can reach the widest range of devices, where do these apps lose
out?
• Secure offline storage — HTML5 browsers support offline databases and caching, but with no out-of-the-box encryption
support. You get all three features in Mobile SDK native applications.
• Security — In general, implementing even trivial security measures on a native platform can be complex tasks for a mobile
Web developer. It can also be painful for users. For example, a web app with authentication requires users to enter their
credentials every time the app restarts or returns from a background state.
• Native features — the camera, address book, and other native features are accessible on limited, if any, browser platforms.
• Native look and feel — HTML5 can only emulate the native look, while customers won’t be able to use familiar compound
gestures.
Hybrid Apps
Hybrid apps are built using HTML5 and JavaScript wrapped inside a thin container that provides access to native platform
features. For the most part, hybrid apps provide the best of both worlds, being almost as easy to develop as HTML5 apps with
all the functionality of native. In addition, hybrid apps can use the SmartSync Data Framework in JavaScript to model Salesforce
data, query and search it, edit it, securely cache it for offline use, and synchronize it with the Salesforce server.
You know that native apps are installed on the device, while HTML5 apps reside on a Web server, so you might be wondering
whether hybrid apps store their files on the device or on a server? You can implement a hybrid app locally or remotely.
Local
You can package HTML and JavaScript code inside the mobile application binary, in a structure similar to a native
application. In this scenario you use REST APIs and Ajax to move data back and forth between the device and the
cloud.
Server
Alternatively, you can implement the full web application from the server (with optional caching for better performance).
Your container app retrieves the full application from the server and displays it in a browser window.
4
Introduction to Mobile Development Enough Talk; I’m Ready
Development Prerequisites
It’s helpful to have some experience with Database.com or Force.com. You’ll need either a Database.com account or a Force.com
Developer Edition organization.
This guide also assumes you are familiar with the following technologies and platforms:
• OAuth, login and passcode flows, and Salesforce connected apps. See Authentication, Security, and Identity in Mobile
Apps.
• To build iOS applications (hybrid or native), you’ll need Mac OS X “Lion” or higher, iOS 6.0 or higher, and Xcode 4.5
or higher.
• To build Android applications (hybrid or native), you’ll need the Java JDK 6, Eclipse, Android ADT plugin, and the
Android SDK.
• To build remote hybrid applications, you’ll need an organization that has Visualforce.
• Most of our resources are on GitHub, a social coding community. You can access all of our files in our public repository,
but we think it’s a good idea to join. https://github.com/forcedotcom
Note: If you choose to use Database.com, you can’t develop Visualforce–driven hybrid apps.
5
Introduction to Mobile Development Sign Up for Force.com
Note: NPM packages do not support source control, so you can’t update your installation dynamically for new releases.
Instead, you install each release separately. To upgrade to new versions of the SDK, go to the npmjs.org website and
download the new package.
6
Introduction to Mobile Development Mobile SDK GitHub Repository
• https://github.com/forcedotcom/SalesforceMobileSDK-iOS
• https://github.com/forcedotcom/SalesforceMobileSDK-Android
• You can always find the most current releases in the NPM registry or our Mobile SDK GitHub Repository
• Keep up to date with What’s New.
• The latest articles, blog posts, tutorials, and webinars are on http://www2.developerforce.com/mobile/resources.
• Join the conversation on our message boards at http://boards.developerforce.com/t5/Mobile/bd-p/mobile.
7
Chapter 2
Native iOS Development
In this chapter ... Salesforce Mobile SDK delivers libraries and sample Xcode projects for
developing mobile apps on iOS.
• iOS Native Quick Start
• Native iOS Requirements Two main things that the iOS native SDK provides are:
• Installing and Uninstalling Salesforce • Automation of the OAuth2 login process, making it easy to integrate OAuth
Mobile SDK for iOS with your app.
• Creating a Native iOS App in Xcode • Access to the REST API with infrastructure classes (including third-party
• Developing a Native iOS App libraries such as RestKit) to make that access as easy as possible.
• iOS Sample Applications
When you create a native app using the forceios application, your project starts
as a fully functioning native sample app. This simple app allows you to connect
to a Salesforce organization and run a simple query. It doesn’t do much, but it
lets you know things are working as designed.
8
Native iOS Development iOS Native Quick Start
For important information on using various versions of XCode, see the Readme at
https://github.com/forcedotcom/SalesforceMobileSDK-iOS/blob/master/readme.md.
3. At a command prompt, type npm and press Return to make sure your installation was successful. If you don’t see a page
of usage information, revisit Step 2 to find out what’s missing.
4. Use the forceios package to install the Mobile SDK either globally (recommended) or locally.
a. To install Salesforce Mobile SDK in a global location, use the sudo command and append the “global” option, -g:
With the -g option, you can run npm install from any directory. The NPM utility installs the package under
/usr/local/lib/node_modules, and links binary modules in /usr/local/bin. Most users need the sudo
option because they lack read-write permissions in /usr/local.
b. To install Salesforce Mobile SDK in a local folder, cd to that folder and use the NPM command without sudo or –g:
This command installs Salesforce Mobile SDK in a node_modules folder under your current folder. It links binary
modules in ./node_modules/.bin/. In this scenario, you rarely use sudo because you typically install in a local
folder where you already have read-write permissions.
9
Native iOS Development Creating a Native iOS App in Xcode
$ pwd
/Users/joeuser
$ sudo npm uninstall forceios -g
$
To uninstall a package that you installed locally, run the uninstall command from the folder where you installed the package.
For example:
$ pwd
/Users/joeuser
cd <my_projects/my_sdk_folder>
npm uninstall forceios
If you try to uninstall a local installation from the wrong directory, you’ll get an error message similar to this:
(Optional) Clone the Salesforce Mobile SDK Source Code from GitHub
If you’re adventurous or just curious, you can also install the Salesforce iOS SDK source code from its GitHub repository.
Doing so allows you to contribute to the open source and keep up with source code changes.
1. Clone the Mobile SDK iOS repository to your local file system by issuing the following command at the OS X Terminal
app: git clone git://github.com/forcedotcom/SalesforceMobileSDK-iOS.git
Note: If you have the GitHub app for Mac OS X, click Clone in Mac. In your browser, navigate to the Mobile
SDK iOS GitHub repository: https://github.com/forcedotcom/SalesforceMobileSDK-iOS.
2. In the OS X Terminal app, change to the directory where you installed the cloned repository. By default, this is the
SalesforceMobileSDK-iOS directory.
3. Run the install script from the command line: ./install.sh
10
Native iOS Development Creating a Native iOS App in Xcode
$ forceios
Usage:
forceios create
--apptype=<Application Type> (native, hybrid_remote, hybrid_local)
--appname=<Application Name>
--companyid=<Company Identifier> (com.myCompany.myApp)
--organization=<Organization Name> (Your company's/organization's name)
--startpage=<App Start Page> (The start page of your remote app. Only required for
hybrid_remote)
[--outputdir=<Output directory> (Defaults to the current working directory)]
[--appid=<Salesforce App Identifier> (The Consumer Key for your app. Defaults to the
sample app.)]
[--callbackuri=<Salesforce App Callback URL (The Callback URL for your app. Defaults
to the sample app.)]
Using this information, type forceios create, followed by your options and values. For example:
11
Native iOS Development Running the Xcode Project Template App
--startpage (hybrid remote apps only) Server path to the remote start page.
For example: /apex/MyAppStartPage.
1. Press Command-R and the default template app runs in the iOS simulator.
2. On startup, the application starts the OAuth authentication flow, which results in an authentication page. Enter your
credentials, and click Login.
3. Tap Allow when asked for permission
12
Native iOS Development Developing a Native iOS App
You should now be able to compile and run the sample project. It’s a simple app that logs you into an org via OAuth2, issues
a select Name from Account SOQL query, and displays the result in a UITableView instance.
• Classes and interfaces that make it easy to call the Salesforce REST API
• Fully implemented OAuth login and passcode protocols
• SmartStore library for securely managing user data offline
The native iOS SDK requires you to be proficient in Objective-C coding. You also need to be familiar with iOS application
development principles and frameworks. If you’re a newbie, Start Developing iOS Apps Today is a good place to begin learning.
See Native iOS Requirements on page 9 for additional prerequisites.
In a few Mobile SDK interfaces, you’re required to override some methods and properties. SDK header (.h) files include
comments that indicate mandatory and optional overrides.
13
Native iOS Development AppDelegate Class
AppDelegate Class
The AppDelegate class is the true entry point for an iOS app. In Mobile SDK apps, AppDelegate implements the standard
iOS UIApplicationDelegate interface. The Mobile SDK template application for creating native iOS apps implements
most of the Salesforce-specific startup functionality for you.
To customize the AppDelegate template, populate the following static variables with information from your Force.com
Connected Application:
• RemoteAccessConsumerKey
• OAuthRedirectURI
OAuth functionality resides in an independent module. This separation makes it possible for you to use Salesforce authentication
on demand. You can start the login process from within your AppDelegate implementation, or you can postpone login until
it’s actually required—for example, you can call OAuth from a sub-view.
Initialization
The following high-level overview shows how the AppDelegate initializes the template app. Keep in mind that you can
change any of these details to suit your needs.
1. When the [AppDelegate init] message runs, it:
• Initializes configuration items, such as Connected App identifiers, OAuth scopes, and so on.
• Adds notification observers that listen to SFAuthenticationManager, logoutInitiated, and loginHostChanged
notifications.
The logoutInitiated notification lets the app respond when a user logs out voluntarily or is logged out involuntarily
due to invalid credentials. The loginHostChanged notification lets the app respond when the user changes the login
14
Native iOS Development About View Controllers
host (for example, from Production to Sandbox). See the logoutInitiated: and loginHostChanged: handler
methods in the sample app.
• Initializes authentication "success" and "failure" blocks for the [SFAuthenticationManager
loginWithCompletion:failure:] message. These blocks determine what happens when the authentication
process completes.
You can customize any part of this process. At a minimum, change setupRootViewController to display your own
controller after authentication. You can also customize initializeAppViewState to display your own launch page, or the
InitialViewController to suit your needs. You can also move the authentication details to where they make the most
sense for your app. The Mobile SDK does not stipulate when—or if—actions must occur, but standard iOS conventions apply.
For example, self.window must have a rootViewController by the time
application:didFinishLaunchingWithOptions: completes.
First entry point when your app launches. Called only when the process first starts (not after a
backgrounding/foregrounding cycle).
applicationDidBecomeActive
Called every time the application is foregrounded. The iOS SDK provides no default parent behavior; if you use it, you
must implement it from the ground up.
For a list of all UIApplication event handlers, see “UIApplicationDelegate Protocol Reference” in the iOS Developer
Library.
The most important view controller in your app is the one that manages the first view that displays, after login or—if login is
postponed—after launch. This controller is called your root view controller because it controls everything else that happens
in your app. The Mobile SDK for iOS project template provides a skeletal class named RootViewController that
demonstrates the minimal required implementation.
If your app needs additional view controllers, you’re free to create them as you wish. The view controllers used in Mobile SDK
projects reveal some possible options. For example, the Mobile SDK iOS template project bases its root view class on the
15
Native iOS Development RootViewController Class
UITableViewController interface, while the RestAPIExplorer sample project uses the UIViewController interface.
Your only technical limits are those imposed by iOS itself and the Objective-C language.
RootViewController Class
The RootViewController class exists only as part of the template project and projects generated from it. It implements
the SFRestDelegate protocol to set up a framework for your app’s interactions with the Salesforce REST API. Regardless
of how you define your root view controller, it must implement SFRestDelegate if you intend to use it to access Salesforce
data through the REST APIs.
RootViewController Design
As an element of a very basic app built with the Mobile SDK, the RootViewController class covers only the bare essentials.
Its two primary tasks are:
• Use Salesforce REST APIs to query Salesforce data
• Display the Salesforce data in a table
To do these things, the class inherits UITableViewController and implements the SFRestDelegate protocol. The
action begins with an override of the UIViewController:viewDidLoad method:
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = @"Mobile SDK Sample App";
The iOS runtime calls viewDidLoad only once in the view’s life cycle, when the view is first loaded into memory. The
intention in this skeletal app is to load only one set of data into the app’s only defined view. If you plan to create other views,
you might need to perform the query somewhere else. For example, if you add a detail view that lets the user edit data shown
in the root view, you’ll want to refresh the values shown in the root view when it reappears. In this case, you can perform the
query in a more appropriate method, such as viewWillAppear.
After calling the superclass method, this code sets the title of the view, then issues a REST request in the form of an
asynchronous SOQL query. The query in this case is a simple SELECT statement that gets the Name property from each
User object and limits the number of rows returned to ten. Notice that the requestForQuery and send:delegate:
messages are sent to a singleton shared instance of the SFRestAPI class. Use this singleton object for all REST requests. This
object uses authenticated credentials from the singleton SFAccountManager object to form and send authenticated requests.
The Salesforce REST API responds by passing status messages and, hopefully, data to the delegate listed in the send message.
In this case, the delegate is the RootViewController object itself:
The RootViewController object can act as an SFRestAPI delegate because it implements the SFRestDelegate protocol.
This protocol declares four possible response callbacks:
• request:didLoadResponse: — Your request was processed. The delegate receives the response in JSON format. This
is the only callback that indicates success.
• request:didFailLoadWithError: — Your request couldn’t be processed. The delegate receives an error message.
• requestDidCancelLoad — Your request was canceled by some external factor, such as administrator intervention, a
network glitch, or another unexpected event. The delegate receives no return value.
• requestDidTimeout — The Salesforce server failed to respond in time. The delegate receives no return value.
16
Native iOS Development About Salesforce REST APIs
The response arrives in one of the callbacks you’ve implemented in RootViewController. Place your code for handling
Salesforce data in the request:didLoadResponse: callback. For example:
- (void)request:(SFRestRequest *)request
didLoadResponse:(id)jsonResponse {
NSArray *records = [jsonResponse objectForKey:@"records"];
NSLog(@"request:didLoadResponse: #records: %d", records.count);
self.dataRows = records;
[self.tableView reloadData];
}
As the use of the id data type suggests, this code handles JSON responses in generic Objective-C terms. It addresses the
jsonResponse object as an instance of NSDictionary and treats its records as an NSArray object. Because
RootViewController implements UITableViewController, it’s simple to populate the table in the view with extracted
records.
A call to request:didFailLoadWithError: results from one of the following conditions:
• If you use invalid request parameters, you get a kSFRestErrorDomain error code. For example, if you pass nil to
requestForQuery:, or you try to update a non-existent object.
• If an OAuth access token expires, the framework tries to obtain a new access token and, if successful, retries the query. If
a request for a new access token or session ID fails, you get a kSFOAuthErrorDomain error code. For example, if the
access token expires, and the OAuth refresh token is invalid. This scenario rarely occurs.
• If the low-level HTTP request fails, you get an RKRestKitErrorDomain error code. For example, if a Salesforce server
becomes temporarily inaccessible.
The other callbacks are self-describing, and don’t return an error code. You can choose to handle the result however you want:
display an error message, write to the log, retry the request, and so on.
See the Force.com REST API Developer’s Guide for information on Salesforce REST response formats.
Supported Operations
The iOS REST APIs support the standard object operations offered by Salesforce REST and SOAP APIs. Salesforce Mobile
SDK offers delegate and block versions of its REST request APIs. Delegate request methods are defined in the SFRestAPI
class, while block request methods are defined in the SFRestAPI (Blocks) category. Supported operations are:
17
Native iOS Development Supported Operations
SOQL query
requestForQuery: performSOQLQuery:failBlock:completeBlock:
Executes the given
SOQL string and
returns the resulting
data set
SOSL search
requestForSearch: performSOSLSearch:failBlock:completeBlock:
Executes the given
SOSL string and
returns the resulting
data set
Metadata
requestForMetadataWithObjectType: performMetadataWithObjectType:failBlock:
completeBlock:
18
Native iOS Development Supported Operations
Describe global
requestForDescribeGlobal performDescribeGlobalWithFailBlock:completeBlock:
Returns a list of all
available objects in your
org and their metadata
Retrieve
requestForRetrieveWithObjectType: performRetrieveWithObjectType:objectId:
Retrieves a single objectId:fieldList: fieldList:failBlock:completeBlock:
record by object ID
Update
requestForUpdateWithObjectType: performUpdateWithObjectType:objectId:
Updates an object with objectId:fields: fields:failBlock:completeBlock:
the given map
Upsert
requestForUpsertWithObjectType: performUpsertWithObjectType:externalIdField:
Updates or inserts an externalIdField:externalId::fields: externalId:fields:failBlock:completeBlock:
object from external
data, based on whether
the external ID
currently exists in the
external ID field
Create
requestForCreateWithObjectType:fields: performCreateWithObjectType:fields:
Creates a new record in failBlock:completeBlock:
the specified object
Delete
requestForDeleteWithObjectType:objectId: performDeleteWithObjectType:objectId:
Deletes the object of failBlock:completeBlock:
the given type with the
given ID
Versions
requestForVersions performRequestForVersionsWithFailBlock:
Returns Salesforce completeBlock:
version metadata
Resources
requestForResources performRequestForResourcesWithFailBlock:
19
Native iOS Development SFRestAPI Interface
SFRestAPI Interface
SFRestAPI defines the native interface for creating and formatting Salesforce REST requests. It works by formatting and
sending your requests to the Salesforce service, then relaying asynchronous responses to your implementation of the
SFRestDelegate protocol.
SFRestAPI serves as a factory for SFRestRequest instances. It defines a group of methods that represent the request types
supported by the Salesforce REST API. Each SFRestAPI method corresponds to a single request type. Each of these methods
returns your request in the form of an SFRestRequest instance. You then use that return value to send your request to the
Salesforce server. The HTTP coding layer is encapsulated, so you don’t have to worry about REST API syntax.
For a list of supported query factory methods, see Supported Operations on page 17
SFRestDelegate Protocol
When a class adopts the SFRestDelegate protocol, it intends to be a target for REST responses sent from the Salesforce
server. When you send a REST request to the server, you tell the shared SFRestAPI instance which object receives the
response. When the server sends the response, Mobile SDK routes the response to the appropriate protocol method on the
given object.
The SFRestDelegate protocol declares four possible responses:
• request:didLoadResponse: — Your request was processed. The delegate receives the response in JSON format. This
is the only callback that indicates success.
• request:didFailLoadWithError: — Your request couldn’t be processed. The delegate receives an error message.
• requestDidCancelLoad — Your request was canceled by some external factor, such as administrator intervention, a
network glitch, or another unexpected event. The delegate receives no return value.
• requestDidTimeout — The Salesforce server failed to respond in time. The delegate receives no return value.
The response arrives in your implementation of one of these delegate methods. Because you don’t know which type of response
to expect, you must implement all of the methods.
request:didLoadResponse: Method
The request:didLoadResponse: method is the only protocol method that handles a success condition, so place your
code for handling Salesforce data in that method. For example:
- (void)request:(SFRestRequest *)request
didLoadResponse:(id)jsonResponse {
NSArray *records = [jsonResponse objectForKey:@"records"];
NSLog(@"request:didLoadResponse: #records: %d", records.count);
self.dataRows = records;
[self.tableView reloadData];
}
20
Native iOS Development Creating REST Requests
At the server, all responses originate as JSON strings. Mobile SDK receives these raw responses and reformats them as iOS
SDK objects before passing them to the request:didLoadResponse: method. Thus, the jsonResponse payload arrives
as either an NSDictionary object or an NSArray object. The object type depends on the type of JSON data returned. If
the top level of the server response represents a JSON object, jsonResponse is an NSDictionary object. If the top level
represents a JSON array of other data, jsonResponse is an NSArray object.
If your method cannot infer the data type from the request, use [NSObject isKindOfClass:] to determine the data type.
For example:
You can address the response as an NSDictionary object and extract its records into an NSArray object. To do so, send the
NSDictionary:objectForKey: message using the key “records”.
request:didFailLoadWithError: Method
A call to the request:didFailLoadWithError: callback results from one of the following conditions:
• If you use invalid request parameters, you get a kSFRestErrorDomain error code. For example, you pass nil to
requestForQuery:, or you try to update a non-existent object.
• If an OAuth access token expires, the framework tries to obtain a new access token and, if successful, retries the query. If
a request for a new access token or session ID fails, you get a kSFOAuthErrorDomain error code. For example, the access
token expires, and the OAuth refresh token is invalid. This scenario rarely occurs.
• If the low-level HTTP request fails, you get an RKRestKitErrorDomain error code. For example, a Salesforce server
becomes temporarily inaccessible.
• For standard SOQL queries and SOSL searches, SFRestAPI methods create query strings based on minimal data input
and package them in an SFRestRequest object that can be sent to the Salesforce server.
• If you are using a Salesforce REST API that isn’t based on SOQL or SOSL, SFRestRequest methods let you configure
the request itself to match the API format.
• The SFRestAPI (QueryBuilder) category provides methods that create free-form SOQL queries and SOSL search
strings so you don’t have to manually format the query or search string.
• Request methods in the SFRestAPI (Blocks) category let you pass callback code as block methods, instead of using a
delegate object.
21
Native iOS Development SFRestRequest Class
At runtime, Mobile SDK creates a singleton instance of SFRestAPI. You use this instance to obtain an SFRestRequest
object and to send that object to the Salesforce server.
To send a REST request to the Salesforce server from an SFRestAPI delegate:
3. Send the send:delegate: message to the shared SFRestAPI instance. Use your new SFRestRequest object as the
send: parameter. The second parameter designates an SFRestDelegate object to receive the server’s response. In the
following example, the class itself implements the SFRestDelegate protocol, so it sets delegate: to self.
SFRestRequest Class
Salesforce Mobile SDK provides the SFRestRequest interface as a convenience class for apps. SFRestAPI provides request
methods that use your input to form a request. This request is packaged as an SFRestRequest instance and returned to your
app. In most cases you don’t manipulate the SFRestRequest object. Typically, you simply pass it unchanged to the
SFRestAPI:send:delegate: method.
If you’re sending a REST request that isn’t directly supported by the Mobile SDK—for example, if you want to use the Chatter
REST API—you can manually create and configure an SFRestRequest object.
To send a non-SOQL and non-SOSL REST request using the Mobile SDK:
22
Native iOS Development SFRestAPI (Blocks) Category
[request setEndpoint:kSFDefaultRestEndpoint];
[request setMethod:SFRestMethodGET];
[request setPath:[NSString stringWithFormat:@"/v26.0/chatter/feeds/record/%@/feed-items",
recordId]];
[[SFRestAPI sharedInstance] send:request delegate:self];
4. Alternatively, you can create the same request using the requestWithMethod:path:queryParams class method.
SFRestRequest *request =
[SFRestRequest requestWithMethod:SFRestMethodGET
path:[NSString stringWithFormat:
@"/v26.0/chatter/feeds/record/%@/feed-items",
recordId]
queryParams:nil];
[[SFRestAPI sharedInstance] send:request delegate:self];
5. To perform a request with parameters, create a parameter string, and then use the SFJsonUtils:objectFromJSONString
static method to wrap it in an NSDictionary object. (If you prefer, you can create your NSDictionary object directly,
before the method call, instead of creating it inline.)
The following example performs a POST operation that adds a comment to a Chatter feed.
SFRestRequest *request =
[SFRestRequest requestWithMethod:SFRestMethodPOST
path:[NSString stringWithFormat:
@"/v26.0/chatter/feeds/record/%@/feed-items",
recordId]
queryParams:(NSDictionary *)[SFJsonUtils objectFromJSONString:body]];
[[SFRestAPI sharedInstance] send:request delegate:self];
1. In addition to copying the REST API parameters, each method requires two blocks: a fail block of type SFRestFailBlock,
and a complete block of type SFRestDictionaryResponseBlock or type SFRestArrayResponseBlock, depending
on the expected response data.
2. Block-based methods send your request for you, so you don’t need to call a separate send method. If your request fails, you
can use the SFRestRequest * return value to retry the request. To do this, use the
SFRestAPI:sendRESTRequest:failBlock:completeBlock: method.
Judicious use of blocks and delegates can help fine-tune your app’s readability and ease of maintenance. Prime conditions for
using blocks often correspond to those that mandate inline functions in C++ or anonymous functions in Java. However, this
observation is just a general suggestion. Ultimately, you need to make a judgement call based on research into your app’s
real-world behavior.
23
Native iOS Development SFRestAPI (QueryBuilder) Category
SFRestAPI (QueryBuilder) provides two static methods each for SOQL queries and SOSL searches: one takes minimal
parameters, while the other accepts a full list of options.
SOSL Methods
SOSL query builder methods are:
set the key to “Widget__c” and its value to “name WHERE createddate = “THIS_FISCAL_QUARTER”. For
example:
[SFRestAPI
SOSLSearchWithSearchTerm:@"all of these will be escaped:~{]"
objectScope:[NSDictionary
dictionaryWithObject:@"name WHERE
createddate="THIS_FISCAL_QUARTER"
forKey:@"Widget__c"]];
24
Native iOS Development SFRestAPI (QueryBuilder) Category
• limit—If you want to limit the number of results returned, set this parameter to the maximum number of results you
want to receive.
SOQL Methods
SOQL QueryBuilder methods that construct SOQL strings are:
Parameters for the SOQL methods correspond to SOQL query syntax. All parameters except fields and sObject can be
set to nil.
SOSL Sanitizing
The QueryBuilder category also provides a class method for cleaning SOSL search terms:
This method escapes every SOSL reserved character in the input string, and returns the escaped version. For example:
25
Native iOS Development iOS Sample Applications
• The RestAPIExplorer sample app exercises all of the native REST API wrappers. It is in the Mobile SDK for iOS under
native/SampleApps/RestAPIExplorer.
• The NativeSqlAggregator sample app shows SQL aggregation examples as well as a native SmartStore implementation.
It resides in the Mobile SDK for iOS under native/SampleApps/NativeSqlAggregator.
26
Chapter 3
Native Android Development
In this chapter ... Salesforce Mobile SDK delivers libraries and sample projects for developing
native mobile apps on Android.
• Android Native Quick Start
• Native Android Requirements The Android native SDK provides two main features:
• Installing and Uninstalling Salesforce • Automation of the OAuth2 login process, making it easy to integrate the
Mobile SDK for Android process with your app.
• Creating a New Android Project • Access to the Salesforce REST API, with utility classes that simplify that
• Setting Up Sample Projects in Eclipse access.
• Developing a Native Android App
The Android Salesforce Mobile SDK includes several sample native applications.
• Android Sample Applications
It also provides an ant target for quickly creating a new application.
27
Native Android Development Android Native Quick Start
Note: For best results, install all previous versions of the Android SDK as well as your target version.
The SalesforceSDK project is built with the Android 3.0 (Honeycomb) library. The primary reason for this is that we want
to be able to make a conditional check at runtime for file system encryption capabilities. This check is bypassed on earlier
Android platforms; thus, you can still use the salesforcesdk.jar in earlier Android application versions, down to the
mininum-supported Android 2.2.
3. At a command prompt, type npm and press Return to make sure your installation was successful. If you don’t see a page
of usage information, revisit Step 2 to find out what’s missing.
4. Use the forcedroid package to install the Mobile SDK either globally (recommended) or locally.
28
Native Android Development Installing and Uninstalling Salesforce Mobile SDK for Android
a. To install Salesforce Mobile SDK in a global location, append the “global” option, -g, to the end of the command.
For non-Windows environments, use the sudo command:
On Windows:
With the -g option, you run npm install from any directory. In non-Windows environments, the NPM utility
installs the package under /usr/local/lib/node_modules, and links binary modules in /usr/local/bin. Most
users need the sudo option because they lack read-write permissions in /usr/local. In Windows environments,
global packages are installed in %APPDATA%\npm\node_modules, and binaries are linked in %APPDATA%\npm.
b. To install Salesforce Mobile SDK in a local directory, cd to that directory and use the NPM command without sudo
or the –g option:
This command installs Salesforce Mobile SDK in a node_modules directory under your current directory. It links
binary modules in ./node_modules/.bin/. In this scenario, you rarely use sudo because you typically install in a
local folder where you already have read-write permissions.
$ pwd
/Users/joeuser
$ sudo npm uninstall forcedroid -g
$
If you installed the package locally, run the uninstall command from the folder where you installed the package. For
example:
cd <my_projects/my_sdk_folder>
npm uninstall forcedroid
If you try to uninstall a local installation from the wrong directory, you’ll get an error message similar to this:
(Optional) Clone the Salesforce Mobile SDK Source Code from GitHub
If you’re adventurous or just curious, you can choose to install the Salesforce Mobile SDK source code from its GitHub
repository. Doing so allows you to contribute to the open source and keep up with source code changes.
1. In your browser, navigate to the Mobile SDK Android GitHub repository:
https://github.com/forcedotcom/SalesforceMobileSDK-Android.
2. Clone the repository to your local file system by issuing the following command: git clone
git://github.com/forcedotcom/SalesforceMobileSDK-Android.git
29
Native Android Development Creating a New Android Project
3. Open a command prompt in the directory where you installed the cloned repository, and run the install script from the
command line: ./install.sh
Note: Windows users: Run cscript install.vbs.
Note: These variables are for your own convenience. If you don’t set up these variables, make sure to replace
$ANDROID_SDK_DIR, $SALESFORCE_SDK_DIR, $NATIVE_DIR and $TARGET_DIR in the various code snippets
in this guide with the actual paths.
$ node_modules/.bin/forcedroid
Usage:
forcedroid create
30
Native Android Development Creating a New Android Project
Using this information, type forcedroid create, followed by your options and values. For example:
cd <your_project_directory>
$ANDROID_SDK_DIR/tools/android update project -p . -t <id>
ant clean debug
2. If your emulator is not running, use the Android AVD Manager to start it. If you’re using a device, connect it.
3. Type the following command at the command prompt:
ant installd
It seems that there are sub-projects. If you want to update them please use the
--subprojects parameter.
The Android project you created contains a simple application you can build and run.
31
Native Android Development Setting Up Sample Projects in Eclipse
When you’ve finished importing the projects, Eclipse automatically builds your workspace. This process can take several
minutes. When the status bar reports zero errors, you’re ready to run the project.
1. In your Eclipse workspace, Control-click or right-click your project.
2. From the popup menu, choose Run As > Android Application.
Eclipse launches your app in the emulator or on your connected Android device.
--apexpage (hybrid remote apps only) Server path to the Apex start page.
For example: /apex/MyAppStartPage.
32
Native Android Development Android Project Files
1. SalesforceSDK—The SalesforceSDK, which provides support for OAuth2 and REST API calls
2. test/SalesforceSDKTest—Tests for the SalesforceSDK project
3. TemplateApp—Template used when creating new native applications using SalesforceSDK
4. test/TemplateAppTest—Tests for the TemplateApp project
5. SampleApps/RestExplorer—App using SalesforceSDK to explore the REST API calls
6. SampleApps/NativeSqlAggregator —A native app that uses SmartStore
<resources>
<string name="remoteAccessConsumerKey">3MVG92.uWdyphVj4bnolD7yuIpCQsNgddW
tqRND3faxrv9uKnbj47H4RkwheHA2lKY4cBusvDVp0M6gdGE8hp</string>
<string name="oauthRedirectURI">sfdc:///axm/detect/oauth/done</string>
<string-array name="oauthScopes">
<item>api</item>
</string-array>
</resources>
33
Native Android Development Android Application Structure
The create_native script pre-populates oauthRedirectURI and remoteAccessConsumerKey strings with dummy
values. Replace those values with the strings from your connected app definition.
You define and customize user interface layouts, image sizes, strings, and other resources in XML files. Internally, the SDK
uses an R class instance to retrieve and manipulate your resources. However, the Mobile SDK makes its resources directly
accessible to client apps, so you don’t need to write code to manage these features.
34
Native Android Development Native API Packages
35
Native Android Development Overview of Native Classes
SalesforceSDKManager Class
The SalesforceSDKManager class is the entry point for all native Android applications that use the Salesforce Mobile
SDK. It provides mechanisms for:
36
Native Android Development SalesforceSDKManager Class
• Passcodes
• Encryption and decryption of user data
• String conversions
• User agent access
• Application termination
• Application cleanup
initNative() Method
During startup, you initialize the singleton SalesforceSDKManager object by calling its static initNative() method.
This method takes four arguments:
In this example, KeyImpl is the app’s implementation of KeyInterface. MainActivity subclasses SalesforceActivity
and is designated here as the first activity to be called after login.
logout() Method
The SalesforceSDKManager.logout() method clears user data. For example, if you’ve introduced your own resources
that are user-specific, you don’t want them to persist into the next user session. SmartStore destroys user data and account
information automatically at logout.
Always call the superclass method somewhere in your method override, preferably after doing your own cleanup. Here’s a
pseudo-code example.
@Override
public void logout(Activity frontActivity) {
// Clean up all persistent and non-persistent app artifacts
// Call superclass after doing your own cleanup
super.logout(frontActivity);
}
getLoginActivityClass() Method
This method returns the descriptor for the login activity. The login activity defines the WebView through which the Salesforce
server delivers the login dialog.
37
Native Android Development KeyInterface Interface
getUserAgent() Methods
The Mobile SDK builds a user agent string to publish the app’s versioning information at runtime. This user agent takes the
following form.
isHybrid() Method
Imagine that your Mobile SDK app creates libraries that are designed to serve both native and hybrid clients. Internally, the
library code switches on the type of app that calls it, but you need some way to determine the app type at runtime. To determine
the type of the calling app in code, call the boolean SalesforceSDKManager.isHybrid() method. True means hybrid,
and false means native.
KeyInterface Interface
KeyInterface is a required interface that you implement and pass into the SalesforceSDKManager.initNative() method.
getKey() Method
You are required to return a Base64-encoded encryption key from the getKey() abstract method. Use the Encryptor.hash()
and Encryptor.isBase64Encoded() helper methods to generate suitable keys. The Mobile SDK uses your key to encrypt
app data and account information.
AccountWatcher Class
AccountWatcher informs your app when the user’s account is removed through Settings. Without AccountWatcher, the
application gets no notification of these changes. It’s important to know when an account is removed so that its passcode and
data can be disposed of properly, and logout can begin.
AccountWatcher defines an internal interface, AccountRemoved, that each app must implement. SalesforceSDKManager
implements this interface to terminate the app’s current (front) activity and reset the passcode, if used, and encryption key.
PasscodeManager Class
The PasscodeManager class manages passcode encryption and displays the passcode page as required. It also reads mobile
policies and caches them locally. This class is used internally to handle all passcode-related activities with minimal coding on
your part. As a rule, apps call only these three PasscodeManager methods:
38
Native Android Development Encryptor class
You get this implementation for free in any activity that extends SalesforceActivity, SalesforceListActivity, or
SalesforceExpandableListActivity.
@Override
public void onPause() {
passcodeManager.onPause(this);
super.onPause();
}
Use the boolean return value of PasscodeManager.onResume() method as a condition for resuming other actions. In your
app’s onResume() implementation, be sure to call the superclass method before calling the PasscodeManager version. For
example:
@Override
public void onResume() {
super.onResume();
// Bring up passcode screen if needed
passcodeManager.onResume(this);
}
recordUserInteraction()
This method saves the time stamp of the most recent user interaction. Call PasscodeManager.recordUserInteraction()
in the activity's onUserInteraction() method. For example:
@Override
public void onUserInteraction() {
passcodeManager.recordUserInteraction();
}
Encryptor class
The Encryptor helper class provides static helper methods for encrypting and decrypting strings using the hashes required
by the SDK. It’s important for native apps to remember that all keys used by the Mobile SDK must be Base64-encoded. No
other encryption patterns are accepted. Use the Encryptor class when creating hashes to ensure that you use the correct
encoding.
Most Encryptor methods are for internal use, but apps are free to use this utility as needed. For example, if an app implements
its own database, it can use Encryptor as a free encryption and decryption tool.
Each of these classes provides a free implementation of PasscodeManager calls. When possible, it’s a good idea to extend
one of these classes for all of your app’s activities, even if your app doesn’t currently use passcodes.
39
Native Android Development UI Classes
For passcode-protected apps: If any of your activities don’t extend SalesforceActivity, SalesforceListActivity,
or SalesforceExpandableListActivity, you’ll need to add a bit of passcode protocol to each of those activities. See
Using Passcodes on page 42
Each of these activity classes contain a single abstract method:
This method overloads the Activity.onResume() method, which is implemented by the class. The class method calls
your overload after it instantiates a RestClient instance. Use this method to cache the client that’s passed in, and then use
that client to perform your REST requests.
UI Classes
Activities in the com.salesforce.androidsdk.ui package represent the UI resources that are common to all Mobile
SDK apps. You can style, skin, theme, or otherwise customize these resources through XML. With the exceptions of
SalesforceActivity, SalesforceListActivity, and SalesforceExpandableListActivity, do not override
these activity classes with intentions of replacing the resources at runtime.
• getRestClient()
• peekRestClient()
The getRestClient() method asynchronously creates a RestClient instance for querying Salesforce data. Asynchronous
in this case means that this method is intended for use on UI threads. The peekRestClient() method creates a RestClient
instance synchronously, for use in non-UI contexts.
Once you get the RestClient instance, you can use it to send REST API calls to Salesforce. Again, the method you call
depends on whether you’re calling from a UI context. The RestClient methods for sending HTTP requests are:
You can choose from three overloads of RestClient.sendSync(), depending on the degree of information you can provide
for the request.
LoginActivity Class
LoginActivity defines the login screen. The login workflow is worth describing because it explains two other classes in the
activity package. In the login activity, if you press the Menu button, you get three options: Clear Cookies, Reload, and Pick
Server. Pick Server launches an instance of the ServerPickerActivity class, which displays Production, Sandbox, and
Custom Server options. When a user chooses Custom Server, ServerPickerActivity launches an instance of the
CustomServerURLEditor class. This class displays a popover dialog that lets you type in the name of the custom server.
Other UI Classes
Several other classes in the ui package are worth mentioning, although they don’t affect your native API development efforts.
40
Native Android Development UpgradeManager Class
The PasscodeActivity class provides the UI for the passcode screen. It runs in one of three modes: Create, CreateConfirm,
and Check. Create mode is presented the first time a user attempts to log in. It prompts the user to create a passcode. After
the user submits the passcode, the screen returns in CreateConfirm mode, asking the user to confirm the new passcode.
Thereafter, that user sees the screen in Check mode, which simply requires the user to enter the passcode.
SalesforceR is a deprecated class. This class was required when the Mobile SDK was delivered in JAR format, to allow
developers to edit resources in the binary file. Now that the Mobile SDK is available as a library project, SalesforceR is not
needed. Instead, you can override resources in the SDK with your own.
SalesforceDroidGapActivity and SalesforceGapViewClient are used only in hybrid apps.
UpgradeManager Class
UpgradeManager provides a mechanism for silently upgrading the SDK version installed on a device. This class stores the
SDK version information in a shared preferences file on the device. To perform an upgrade, UpgradeManager queries the
current SalesforceSDKManager instance for its SDK version and compares its version to the device’s version information.
If an upgrade is necessary—for example, if there are changes to a database schema or to encryption patterns—UpgradeManager
can take the necessary steps to upgrade SDK components on the device. This class is intended for future use. Its implementation
in Mobile SDK 2.0 simply stores and compares the version string.
Utility Classes
Though most of the classes in the util package are for internal use, several of them can also benefit third-party developers.
Class Description
EventsObservable See the source code for a list of all events that the Mobile SDK
for Android propagates.
EventsObserver Implement this interface to eavesdrop on any event. This
functionality is useful if you’re doing something special when
certain types of events occur.
TokenRevocationReceiver This class handles what happens when an administrator
revokes a user’s refresh token. See Handling Refresh Token
Revocation in Android Native Apps on page 126.
UriFragmentParser You can directly call this static helper class. It parses a given
URI, breaks its parameters into a series of key/value pairs, and
returns them in a map.
ForcePlugin Class
All classes in thecom.salesforce.androidsdk.phonegap package are intended for hybrid app support. Most of these
classes implement Javascript plugins that access native code. The base class for these Mobile SDK plugins is ForcePlugin.
If you want to implement your own Javascript plugin in a Mobile SDK app, extend ForcePlugin, and implement the abstract
execute() function.
ForcePlugin extends CordovaPlugin, which works with the Javascript framework to let you create a Javascript module
that can call into native functions. PhoneGap provides the bridge on both sides: you create a native plugin with CordovaPlugin,
then you create a Javascript file that mirrors it. Cordova calls the plugin’s execute() function when a script calls one of the
plugin’s Javascript functions.
41
Native Android Development Using Passcodes
Using Passcodes
User data in Mobile SDK apps is secured by encryption. The administrator of your Salesforce org has the option of requiring
the user to enter a passcode for connected apps. In this case, your app uses that passcode as an encryption hash key. If the
Salesforce administrator doesn’t require a passcode, you’re responsible for providing your own key.
Salesforce Mobile SDK does all the work of implementing the passcode workflow. It calls the passcode manager to obtain the
user input, and then combines the passcode with prefix and suffix strings into a hash for encrypting the user's data. It also
handles decrypting and re-encrypting data when the passcode changes. If an organization changes its passcode requirement,
the Mobile SDK detects the change at the next login and reacts accordingly. If you choose to use a passcode, your only
responsibility is to implement the SalesforceSDKManager.getKey() method. All your implementation has to do in this
case is return a Base64-encoded string that can be used as an encryption key.
Internally, passcodes are stored as Base64-encoded strings. The SDK uses the Encryptor class for creating hashes from
passcodes. You should also use this class to generate a hash when you provide a key instead of a passcode. Passcodes and keys
are used to encrypt and decrypt SmartStore data as well as oAuth tokens, user identification strings, and related security
information. To see exactly what security data is encrypted with passcodes, browse the ClientManager.changePasscode()
method.
Mobile policy defines certain passcode attributes, such as the length of the passcode and the timing of the passcode dialog.
Mobile policy files for connected apps live on the Salesforce server. If a user enters an incorrect passcode more than ten
consecutive times, the user is logged out. The Mobile SDK provides feedback when the user enters an incorrect passcode,
apprising the user of how many more attempts are allowed. Before the screen is locked, the PasscodeManager class stores
a reference to the front activity so that the same activity can be resumed if the screen is unlocked.
If you define activities that don’t extend SalesforceActivity, SalesforceListActivity, or
SalesforceExpandableListActivity in a passcode-protected app, be sure to call these three PasscodeManager
methods from each of those activity classes:
• PasscodeManager.onPause()
• PasscodeManager.onResume(Activity)
• PasscodeManager.recordUserInteraction()
Call onPause() and onResume() from your activity's methods of the same name. Call recordUserInteraction()
from your activity’s onUserInteraction() method. Pass your activity class descriptor to onResume(). These calls ensure
that your app enforces passcode security during these events. See PasscodeManager Class on page 38.
Resource Handling
Salesforce Mobile SDK resources are configured in XML files that reside in the native/SalesforceSDK/res folder. You
can customize many of these resources by making changes in this folder.
Resources in the /res folder are grouped into categories, including:
42
Native Android Development Resource Handling
• Menus
• XML
Drawable, layout, and value resources are subcategorized into folders that correspond to a variety of form factors. These
categories handle different device types and screen resolutions. Each category is defined in its folder name, which allows the
resource file name to remain the same for all versions. For example, if the developer provides various sizes of an icon named
icon1.png, for example, the smart phone version goes in one folder, the low-end phone version goes in another folder, while
the tablet icon goes into a third folder. In each folder, the file name is icon1.png. The folder names use the same root but
with different suffixes.
The following table describes the folder names and suffixes.
The compiler looks for a resource in the folder whose name matches the target device configuration. If the requested resource
isn’t in the expected folder (for example, if the target device is a tablet, but the compiler can’t find the requested icon in the
drawables-xlarge or drawables-xlarge-port folder) the compiler looks for the icon file in the generic drawable
folder.
Layouts
Layouts in the Mobile SDK describe the screen resources that all apps use. For example, layouts configure dialog boxes that
handle logins and passcodes.
The name of an XML node in a layout indicates the type of control it describes. For example, the following EditText node
from res/layout/sf__passcode.xml describes a text edit control:
<EditText android:id="@+id/sf__passcode_text"
style="@style/SalesforceSDK.Passcode.Text.Entry"
android:inputType="textPassword" />
In this case, the EditText control uses an android:inputType attribute. Its value, “textPassword”, tells the operating
system to obfuscate the typed input.
The style attribute references a global style defined elsewhere in the resources. Instead of specifying style attributes in place,
you define styles defined in a central file, and then reference the attribute anywhere it’s needed. The value
43
Native Android Development Using REST APIs
<style name="SalesforceSDK.Passcode.Text.Entry">
<item name="android:layout_width">wrap_content</item>
<item name="android:lines">1</item>
<item name="android:maxLength">10</item>
<item name="android:minWidth">@dimen/sf__passcode_text_min_width</item>
<item name="android:imeOptions">actionGo</item>
</style>
You can override any style attribute with a reference to one of your own styles. Rather than changing sf__styles.xml,
define your styles in a different file, such as xyzcorp__styles.xml. Place your file in the res/values for generic device
styles, or the res/values-xlarge folder for tablet devices.
Values
The res/values and res/values-xlarge folders contain definitions of style components, such as dimens and colors, string resources,
and custom styles. File names in this folder indicate the type of resource or style component. To provide your own values,
create new files in the same folders using a file name prefix that reflects your own company or project. For example, if your
developer prefix is XYZ, you can override sf__styles.xml in a new file named XYZ__styles.xml.
sf__strings.xml Strings referenced by Mobile SDK styles; error messages can be overridden
You can override the values in strings.xml. However, if you used the create_native script to create your app, strings
in strings.xml already reflect appropriate values.
Other Resources
Two other folders contain Mobile SDK resources.
• res/menu defines menus used internally. If your app defines new menus, add them as resources here in new files.
• res/xml includes one file that you must edit: servers.xml. In this file, change the default Production and Sandbox
servers to the login servers for your org. The other files in this folder are for internal use. The authenticator.xml file
configures the account authentication resource, and the config.xml file defines PhoneGap plugins for hybrid apps.
44
Native Android Development Using REST APIs
With Android native apps, you do only minimal coding to access Salesforce data through REST calls. The classes in the
com.salesforce.androidsdk.rest package initialize the communication channels and encapsulate low-level HTTP
plumbing. These classes include:
• ClientManager—Serves as a factory for RestClient instances. It also handles account logins and handshakes with the
Salesforce server. Implemented by the Mobile SDK.
• RestClient—Handles protocol for sending REST API requests to the Salesforce server. Don’t directly create instances
of RestClient. Instead, call the ClientManager.getRestClient() method. Implemented by the Mobile SDK.
• RestRequest—Formats REST API requests from the data your app provides. Also serves as a factory for instances of
itself. Don’t directly create instances of RestRequest. Instead, call an appropriate RestRequest static getter function
such as RestRequest.getRequestForCreate(). Implemented by the SDK.
• RestResponse—Formats the response content in the requested format, returns the formatted response to your app, and
closes the content stream. The RestRequest class creates instances of RestResponse and returns them to your app
through your implementation of the RestClient.AsyncRequestCallback interface. Implemented by the SDK.
The RestRequest class natively handles the standard Salesforce data operations offered by the Salesforce REST and SOAP
APIs. Supported operations are:
To obtain an appropriate RestRequest instance, call the RestRequest static method that matches the operation you want
to perform. Here are the RestRequest static methods.
• getRequestForCreate()
• getRequestForDelete()
• getRequestForDescribe()
45
Native Android Development Using REST APIs
• getRequestForDescribeGlobal()
• getRequestForMetadata()
• getRequestForQuery()
• getRequestForResources()
• getRequestForRetrieve()
• getRequestForSearch()
• getRequestForUpdate()
• getRequestForUpsert()
• getRequestForVersions()
These methods return a RestRequest object which you pass to an instance of RestClient. The RestClient class provides
synchronous and asynchronous methods for sending requests: sendSync() and sendAsync(). UsesendAsync() when
you’re sending a request from a UI thread. Use sendSync() only on non-UI threads, such as a service or a worker thread
spawned by an activity.
Here’s the basic procedure for using the REST classes on a UI thread:
4. Call a static RestRequest() getter method to obtain the appropriate RestRequest object for your needs. For example,
to get a description of a Salesforce object:
5. Pass the RestRequest object you obtained in the previous step to RestClient.sendAsync() or
RestClient.sendSync(). If you’re on a UI thread and therefore calling sendAsync():
46
Native Android Development Android Template App: Deep Dive
If you’re calling the sendSync() method from a service, use the same procedure with the following changes:
TemplateApp Class
Every native Android app requires an instance of android.app.Application. Here’s the entire class:
package com.salesforce.samples.templateapp;
import android.app.Application;
import com.salesforce.androidsdk.app.SalesforceSDKManager;
47
Native Android Development MainActivity Class
/**
* Application class for our application.
*/
public class TemplateApp extends Application {
@Override
public void onCreate() {
super.onCreate();
SalesforceSDKManager.initNative(getApplicationContext(), new KeyImpl(), MainActivity.class);
}
}
Most native Android apps can use similar code. For this small amount of work, your app gets free implementations of passcode
and login/logout mechanisms, plus a few other benefits. See SalesforceActivity, SalesforceListActivity, and
SalesforceExpandableListActivity Classes on page 39.
MainActivity Class
In Mobile SDK apps, the main activity begins immediately after the user logs in. Once the main activity is running, it can
launch other activities, which in turn can launch sub-activities. When the application exits, it does so by terminating the main
activity. All other activities terminate in a cascade from within the main activity.
The MainActivity class for the Template app extends
com.salesforce.androidsdk.ui.sfnative.SalesforceActivity. This superclass is the Mobile SDK's basic
abstract activity class.SalesforceActivity, gives you free implementations of mandatory passcode and login protocols. If
you use another base activity class instead, you’re responsible for implementing those protocols. MainActivity initializes
the app's UI and implements its UI buttons. The UI includes a list view that can show the user's Salesforce Contacts or
Accounts. When the user clicks one of these buttons, the MainActivity object performs a couple of basic queries to populate
the view. For example, to fetch the user's Contacts from Salesforce, the onFetchContactsClick() message handler sends
a simple SOQL query:
Internally, the private sendRequest() method formulates a server request using the RestRequest class and the given
SOQL string:
48
Native Android Development TemplateApp Manifest
onError(e);
}
}
@Override
public void onError(Exception exception)
{
Toast.makeText(MainActivity.this,
MainActivity.this.getString(
SalesforceSDKManager.getInstance().getSalesforceR().stringGenericError(),
exception.toString()),
Toast.LENGTH_LONG).show();
}
});
}
This method uses an instance of the com.salesforce.androidsdk.rest.RestClient class, client, to process its
SOQL query. The RestClient class relies on two helper classes—RestRequest and RestResponse—to send the query
and process its result. The sendRequest() method calls RestClient.sendAsync() to process the SOQL query
asynchronously.
To support the sendAsync() call, the sendRequest() method constructs an instance of
com.salesforce.androidsdk.rest.RestRequest, passing it the API version and the SOQL query string. The resulting
object is the first argument for sendAsync(). The second argument is a callback object. When sendAsync() has finished
running the query, it sends the results to this callback object. If the query is successful, the callback object uses the query
results to populate a UI list control. If the query fails, the callback object displays a toast popup to display the error message.
Java Note:
In the call toRestClient.sendAsync() the code instantiates a new AsyncRequestCallback object as its second
argument. However, the AsyncRequestCallbackconstructor is followed by a code block that overrides a couple of
methods: onSuccess() and onError(). If that code looks strange to you, take a moment to see what's happening.
ASyncRequestCallback is defined as an interface, so it has no implementation. In order to instantiate it, the code implements
the two ASyncRequestCallback methods inline to create an anonymous class object. This technique gives TemplateApp
an sendAsync() implementation of its own that can never be called from another object and doesn't litter the API landscape
with a group of specialized class names.
TemplateApp Manifest
A look at the AndroidManifest.xml file in the TemplateApp project reveals the components required for Mobile SDK
native Android apps. Required components include:
49
Native Android Development Android Sample Applications
Because apps created by the create_native script are based on the TemplateApp project, you don’t need to add these
components to the manifest. As with any Android app, you can add other components, such as custom activities or services,
using the Android Manifest editor in Eclipse.
In addition to component specifications, the manifest grants Android permissions to the app. Grants in TemplateApp include:
• android.permission.INTERNET
• android.permission.MANAGE_ACCOUNTS
• android.permission.AUTHENTICATE_ACCOUNTS
• android.permission.GET_ACCOUNTS
• android.permission.USE_CREDENTIALS
• android.permission.ACCESS_NETWORK_STATE
Most of these permissions provide access to Android user accounts. For details, search for manifest permissions in the Android
SDK documentation.
1. To run the application from your Eclipse workspace, right-click the RestExplorer project and choose Run As > Android
Application.
2. To run the tests, right-click the RestExplorerTest project and choose Run As > Android JUnit Test.
NativeSqlAggregator is a sample app that demonstrates SQL aggregation with SmartSQL. As such, it also demonstrates a
native implementation of SmartStore. To run the application from your Eclipse workspace, right-click the NativeSqlAggregator
project and choose Run As > Android Application.
50
Chapter 4
Introduction to Hybrid Development
In this chapter ... Hybrid apps combine the ease of HTML5 Web app development with the
power and features of the native platform. They run within the Salesforce
• iOS Hybrid Development Mobile Container
• Android Hybrid Development
• JavaScript Files for Hybrid , a native layer that translates the app into device-specific code.
Applications Hybrid apps depend on HTML and JavaScript files. These files can be stored
• Versioning and Javascript Library on the device or on the server.
Compatibility
• Device—Hybrid apps developed with forcetk.mobilesdk wrap a Web
• Managing Sessions in Hybrid
app inside the Salesforce Mobile Container. In this scenario, the JavaScript
Applications
and HTML files are stored on the device.
• Example: Serving the Appropriate
Javascript Libraries • Server — Hybrid apps developed using Visualforce technology store their
HTML and JavaScript files on the Salesforce server and are delivered
through the Salesforce Mobile Container.
51
Introduction to Hybrid Development iOS Hybrid Development
• AccountEditor: Demonstrates how to use the SmartSync Data Framework to access Salesforce data.
• ContactExplorer: The ContactExplorer sample app uses PhoneGap (also known as Cordova) to retrieve local device
contacts. It also uses the forcetk.mobilesdk.js toolkit to implement REST transactions with the Salesforce REST
API. The app uses the OAuth2 support in Salesforce SDK to obtain OAuth credentials, then propagates those credentials
to forcetk.mobilesdk.js by sending a JavaScript event.
• VFConnector: The VFConnector sample app demonstrates how to wrap a Visualforce page in a native container. This
example assumes that your org has a Visualforce page called BasicVFTest. The app first obtains OAuth login credentials
using the Salesforce SDK OAuth2 support, then uses those credentials to set appropriate webview cookies for accessing
Visualforce pages.
• SmartStoreExplorer: Lets you explore SmartStore APIs.
• Use -—apptype=”hybrid_local” for a hybrid app with all code in the local project. Put your HTML and JavaScript
files in ${target.dir}/assets/www/.
• Use -—apptype=”hybrid_remote” for a hybrid app with code in a Visualforce app on the server
• AccountEditor: Demonstrates how to use the SmartSync Data Framework to access Salesforce data.
• SampleApps/ContactExplorer: The ContactExplorer sample app uses PhoneGap (also known as Cordova) to retrieve
local device contacts. It also uses the forcetk.mobilesdk.js toolkit to implement REST transactions with the Salesforce
52
Introduction to Hybrid Development JavaScript Files for Hybrid Applications
REST API. The app uses the OAuth2 support in Salesforce SDK to obtain OAuth credentials, then propagates those
credentials to forcetk.mobilesdk.js by sending a javascript event.
• SampleApps/test/ContactExplorerTest: Tests for the ContactExplorer sample app.
• SampleApps/VFConnector: The VFConnector sample app demonstrates how to wrap a Visualforce page in a native
container. This example assumes that your org has a Visualforce page called BasicVFTest. The app first obtains OAuth
login credentials using the Salesforce SDK OAuth2 support, then uses those credentials to set appropriate webview cookies
for accessing Visualforce pages.
• SampleApps/test/VFConnectorTest: Test for the VFConnector sample app.
• SampleApps/SmartStoreExplorer: Lets you explore SmartStore APIs.
• SampleApps/test/SmartStoreExplorerTest: Tests for the SmartStoreExplorer sample app.
53
Introduction to Hybrid Development Versioning and Javascript Library Compatibility
• cordova.js
• cordova.force.js
• forcetk.mobilesdk.js
userAgent = ApexPages.currentPage().getHeaders().get('User-Agent');
54
Introduction to Hybrid Development Managing Sessions in Hybrid Applications
On the client, you can do the same in Javascript using the navigator object:
userAgent = navigator.userAgent;
getInfo(callback)
This method returns an associative array that provides the following information:
The following code retrieves the information stored in the sdkinfo plugin and displays it in alert boxes.
See Also:
Example: Serving the Appropriate Javascript Libraries
55
Introduction to Hybrid Development Managing Sessions in Hybrid Applications
App Architecture Proactive Behavior in SDK Reactive Behavior in SDK Steps for Upgrading Code
1.3 and Earlier 1.4
REST API Background session refresh Refresh from JavaScript No change for
forcetk.mobilesdk.js. For other
frameworks, add refresh code.
JavaScript Remoting in Restart app Refresh session and CSRF Catch timeout, then either
Visualforce token from JavaScript reload page or load a new
iFrame.
JQuery Mobile Restart app Reload page Catch timeout, then reload
page.
cordova.require("salesforce/plugin/oauth").getAuthCredentials(salesforceSessionRefreshed,
getAuthCredentialsError);
• salesforceSessionRefreshed() function:
function salesforceSessionRefreshed(credsData) {
forcetkClient = new forcetk.Client(credsData.clientId, credsData.loginUrl);
forcetkClient.setSessionToken(credsData.accessToken, apiVersion,
credsData.instanceUrl);
forcetkClient.setRefreshToken(credsData.refreshToken);
forcetkClient.setUserAgentString(credsData.userAgent);
}
<Controller>.<Method>(
<params>,
function(result, event) {
if (hasSessionExpired(event)) {
// Reload will try to redirect to login page, container will intercept
// the redirect and refresh the session before reloading the origin page
window.location.reload();
} else {
// Everything is OK. You can go ahead and use the result.
56
Introduction to Hybrid Development Example: Serving the Appropriate Javascript Libraries
},
{escape: true}
);
function hasSessionExpired(event) {
return (event.type == "exception" && event.message.indexOf("Logged in?") != -1);
}
Advanced developers: Reloading the entire page might not provide the optimal user experience. If you want to avoid reloading
the entire page, you’ll need to:
1. Refresh the access token
2. Refresh the Visualforce domain cookies
3. Finally, refresh the CSRF token
In hasSessionExpired(), instead of fully reloading the page as follows:
window.location.reload();
},
function(error) {
console.log("Refresh failed");
}
);
JQuery Mobile
JQueryMobile makes Ajax calls to transfer data for rendering a page. If a session expires, a 302 error is masked by the framework.
To recover, incorporate the following code to force a page refresh.
57
Introduction to Hybrid Development Example: Serving the Appropriate Javascript Libraries
1. For each Salesforce Mobile SDK version that your application supports, do the following.
a. Create a ZIP file containing the Javascript libraries from the intended SDK version.
b. Upload the ZIP file to your org as a static resource.
For example, if you ship a client that uses Salesforce Mobile SDK v. 1.3, add these files to your ZIP file:
• cordova.force.js
• SalesforceOAuthPlugin.js
• bootconfig.js
• cordova-1.8.1.js, which you should rename as cordova.js
Note: In your bundle, it’s permissible to rename the Cordova Javascript library as cordova.js (or PhoneGap.js
if you’re packaging a version that uses a PhoneGap-x.x.js library.)
2. Create an Apex controller that determines which bundle to use. In your controller code, parse the user agent string to find
which version the client is using.
if (userAgent.contains('SalesforceMobileSDK/1.3')) {
return 'sdklib13';
}
// Add additional if statements for other SalesforceSDK versions
// for which you provide library bundles.
}
}
3. Create a Visualforce page for each library in the bundle, and use that page to redirect the client to that library.
For example, for the SalesforceOAuthPlugin library:
c. Reference the VisualForce page in a <script> tag in your HTML code. Be sure to point to the page you created in
step 3b. For example:
Note: Provide a separate <script> tag for each library in your bundle.
58
Chapter 5
HTML5 Development
In this chapter ... HTML5 lets you create lightweight mobile interfaces without installing software
on the target device. Any mobile, touch or desktop device can access these
• HTML5 Development Requirements mobile interfaces.
• Delivering HTML5 Content With
Visualforce You can create an HTML5 application that leverages the Force.com platform
• Accessing Salesforce Data: by:
Controllers vs. APIs • Using Visualforce to deliver the HTML content
• Using JavaScript remoting to invoke Apex controllers for fetching records
from Force.com
59
HTML5 Development HTML5 Development Requirements
Note: This type of development uses Visualforce. You can’t use Database.com.
</apex:page>
This code sets up an Apex page that can contain HTML5 content, but, of course, it produces an empty page. With the use
of static resources and third-party libraries, you can add HTML and JavaScript code to build a fully interactive mobile app.
60
HTML5 Development Accessing Salesforce Data: Controllers vs. APIs
also defines a callback function to process the value returned from getItemId(). In the Apex controller, the @RemoteAction
annotation exposes the getItemId() function to external JavaScript code.
@RemoteAction
global static String getItemId(String objectName) { ... }
See this Dreamforce 2012 session for a more detailed comparison between the JavaScript remoting and actionFunction.
See http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_classes_annotation_RemoteAction.htm to read
more about @RemoteAction annotations.
SalesforceProxy-SID
Current user session ID
For tips on accessing this proxy through JavaScript, see AJAX Proxy.
The following code sample uses the jQuery Mobile library for the user interface. To run this code, your Visualforce page must
include jQuery and the forcetk toolkit. To add these resources:
1. Create an archive file, such as a ZIP file, that contains app.js, forcetk.js, jquery.js, and any other static resources
your project requires.
2. In Salesforce, upload the archive file via Your Name > App Setup > Develop > Static Resources.
After obtaining an instance of the jQuery Mobile library, the sample code creates a forcetk client object and initializes it with
a session ID. It then calls the asynchronous forcetk query() method to process a SOQL query. The query callback function
uses jQuery Mobile to display the first Name field returned by the query as HTML in an object with ID “accountname.” At
the end of the Apex page, the HTML5 content defines the accountname element as a simple <span> tag.
<apex:page>
<apex:includeScript value="{!URLFOR($Resource.static, 'jquery.js')}" />
<apex:includeScript value="{!URLFOR($Resource.static, 'forcetk.js')}" />
61
HTML5 Development Accessing Salesforce Data: Controllers vs. APIs
<script type="text/javascript">
// Get a reference to jQuery that we can work with
$j = jQuery.noConflict();
// Get an instance of the REST API client and set the session ID
var client = new forcetk.Client();
client.setSessionToken('{!$Api.Session_ID}');
Note:
• Using the REST API—even from a Visualforce page—consumes API calls.
• SalesforceAPI calls made through a Mobile SDK container or through a Cordova webview do not require proxy
services. Cordova webviews disable same-origin policy, so you can make API calls directly. This exemption applies
to all Mobile SDK hybrid and native apps.
Additional Options
You can use the SmartSync Data Framework in HTML5 apps. Just include the required JavaScript libraries as static resources.
Take advantage of the model and routing features. Offline access is disabled for this use case. See Using SmartSync to Access
Salesforce Objects on page 63.
Salesforce Developer Marketing provides developer mobile packs that can help you get a quick start with HTML5 apps.
Offline Limitations
Read these articles for tips on using HTML5 with Force.com in offline situations.
• http://blogs.developerforce.com/developer-relations/2011/06/using-html5-offline-with-forcecom.html
• http://blogs.developerforce.com/developer-relations/2013/03/using-javascript-with-force-com.html
62
Chapter 6
Using SmartSync to Access Salesforce Objects
In this chapter ... The SmartSync Data Framework is a Mobile SDK library that represents
Salesforce objects as JavaScript objects. Using SmartSync in a hybrid app, you
• About Backbone Technology can create models of Salesforce objects and manipulate the underlying records
• Models and Model Collections just by changing the model data. If you perform a SOQL or SOSL query, you
• Using the SmartSync Data receive the resulting records in a model collection rather than as a JSON string.
Framework in JavaScript
Underlying the SmartSync technology is the backbone.js open-source
• Offline Caching
JavaScript library. Backbone.js defines an extensible mechanism for modeling
• Conflict Detection
data. To understand the basic technology behind the SmartSync Data
• Tutorial: Creating a SmartSync
Framework, browse the examples and documentation at backbonejs.org.
Application
• SmartSync Sample Apps Three sample hybrid applications demonstrate SmartSync.
63
Using SmartSync to Access Salesforce Objects About Backbone Technology
• Models with key-value binding and custom events, for modeling your information
• Collections with a rich API of enumerable functions, for containing your data sets
• Views with declarative event handling, for displaying information in your models
• A router for controlling navigation between views
Salesforce SmartSync Data Framework extends the Model and Collection core Backbone objects to connect them to the
Salesforce REST API. SmartSync also provides optional offline support through SmartStore, the secure storage component
of the Mobile SDK.
To learn more about Backbone, see http://backbonejs.org/ and http://backbonetutorials.com/. You can also search online for
“backbone javascript” to find a wealth of tutorials and videos.
• Models
• Model collections
Definitions for these objects extend classes defined in backbone.js, a popular third-party JavaScript framework. For
background information, see http://backbonetutorials.com.
Models
Models on the client represent server records. In SmartSync, model objects are instances of Force.SObject, a subclass of
the Backbone.Model class. SObject extends Model to work with Salesforce APIs and, optionally, with SmartStore.
You can perform the following CRUD operations on SObject model objects:
• Create
• Destroy
• Fetch
• Save
• Get/set attributes
In addition, model objects are observable: Views and controllers can receive notifications when the objects change.
Properties
Force.SObject adds the following properties to Backbone.Model:
sobjectType
Required. The name of the Salesforce object that this model represents. This value can refer to either a standard object
or a custom object.
fieldlist
Required. Names of fields to fetch, save, or destroy.
64
Using SmartSync to Access Salesforce Objects Model Collections
cacheMode
Offline behavior.
mergeMode
Conflict handling behavior.
cache
For updatable offline storage of records. The SmartSync Data Framework comes bundled with Force.StoreCache, a
cache implementation that is backed by SmartStore.
cacheForOriginals
Contains original copies of records fetched from server to support conflict detection.
Examples
You can assign values for model properties in several ways:
• As properties on a Force.SObject instance.
• As methods on a Force.SObject sub-class. These methods take a parameter that specifies the desired CRUD action
(“create”, “read”, “update”, or “delete”).
• In the options parameter of the fetch(), save(), or destroy() function call.
For example, these code snippets are equivalent.
Model Collections
Model collections in the SmartSync Data Framework are containers for query results. Query results stored in a model collection
can come from the server via SOQL, SOSL, or MRU queries. Optionally, they can also come from the cache via SmartSQL
(if the cache is SmartStore), or another query mechanism if you use an alternate cache.
Model collection objects are instances of Force.SObjectCollection, a subclass of the Backbone.Collection class.
SObjectCollection extends Collection to work with Salesforce APIs and, optionally, with SmartStore.
Properties
Force.SObjectCollection adds the following properties to Backbone.Collection:
65
Using SmartSync to Access Salesforce Objects Using the SmartSync Data Framework in JavaScript
config
Required. Defines the records the collection can hold (using SOQL, SOSL, MRU or SmartSQL).
cache
For updatable offline storage of records. The SmartSync Data Framework comes bundled with Force.StoreCache, a
cache implementation that’s backed by SmartStore.
cacheForOriginals
Contains original copies of records fetched from server to support conflict detection.
Examples
You can assign values for model collection properties in several ways:
• As properties on a Force.SObject instance
• As methods on a Force.SObject sub-class
• In the options parameter of the fetch(), save(), or destroy() function call
For example, these code snippets are equivalent.
app.models.Account = Force.SObject.extend({
sobjectType: "Account",
66
Using SmartSync to Access Salesforce Objects Using the SmartSync Data Framework in JavaScript
cacheMode: function(method) {
if (app.offlineTracker.get("offlineStatus") == "offline") {
return "cache-only";
}
else {
return (method == "read" ? "cache-first" : "server-first");
}
}
});
Notice that the app.models.Account model object extends Force.SObject, which is defined in SmartSync.js. Also,
the cacheMode() function queries a local offlineTracker object for the device's offline status. You can use the Cordova
library to determine offline status at any particular moment.
SmartSync can perform a fetch or a save operation on the model. It uses the app’s cacheMode value to determine whether to
perform an operation on the server or in the cache. Your cacheMode member can either be a simple string property or a
function returning a string.
setCriteria: function(key) {
this.key = key;
},
config: function() {
// Offline: do a cache query
if (app.offlineTracker.get("offlineStatus") == "offline") {
return {type:"cache", cacheQuery:{queryType:"like",
indexPath:"Name", likeKey: this.key+"%",
order:"ascending"}};
}
// Online
else {
// First time: do a MRU query
if (this.key == null) {
return {type:"mru", sobjectType:"Account",
fieldlist: this.fieldlist};
}
// Other times: do a SOQL query
else {
var soql = "SELECT " + this.fieldlist.join(",")
+ " FROM Account"
+ " WHERE Name like '" + this.key + "%'";
return {type:"soql", query:soql};
}
}
}
});
This model collection uses an optional key that is the name of the account to be fetched from the collection. It also defines a
config() function that determines what information is fetched. If the device is offline, the config() function builds a
cache query statement. Otherwise, if no key is specified, it queries the most recently used record ("mru"). If the key is specified
and the device is online, it builds a standard SOQL query that pulls records for which the name matches the key. The fetch
operation on the Force.SObjectCollection prototype transparently uses the returned configuration to automatically fill
the model collection with query records.
67
Using SmartSync to Access Salesforce Objects Offline Caching
Offline Caching
To provide offline support, your app must be able to cache its models and collections. SmartSync provides a configurable
mechanism that gives you full control over caching operations.
68
Using SmartSync to Access Salesforce Objects Offline Caching
SmartSync updates data in the cache transparently during CRUD operations. You can control the transparency level through
optional flags. Cached objects maintain "dirty" attributes that indicate whether they've been created, updated, or deleted locally.
Cache Modes
When you use a cache, you can specify a mode for each CRUD operation. Supported modes are:
Constant
Mode Description
“cache-only” Force.CACHE_MODE.CACHE_ONLY
Read from, or write to, the
cache. Do not perform the
operation on the server.
“server-only” Force.CACHE_MODE.SERVER_ONLY
Read from, or write to, the
server. Do not perform the
operation on the cache.
“cache-first” Force.CACHE_MODE.CACHE_FIRST
For FETCH operations
only. Fetch the record from
the cache. If the cache
doesn't contain the record,
fetch it from the server and
then update the cache.
“server-first” Force.CACHE_MODE.SERVER_FIRST
Perform the operation on the
(default)
server, then update the
cache.
69
Using SmartSync to Access Salesforce Objects Implementing Offline Caching
To query the cache directly, use a cache query. SmartStore provides query APIs as well as its own query language, Smart SQL.
See Retrieving Data From a Soup.
• Tracking offline status and specifying the appropriate cache control flag for CRUD operations, as shown in the
app.models.Account example.
• Collecting records that were edited locally and saving their changes to the server when the device is back online. The
following example uses a SmartStore cache query to retrieve locally changed records, then calls the SyncPage function to
render the results in HTML.
sync: function() {
var that = this;
var localAccounts = new app.models.AccountCollection();
localAccounts.fetch({
config: {type:"cache", cacheQuery: {queryType:"exact",
indexPath:"__local__", matchKey:true}},
success: function(data) {
that.slidePage(new app.views.SyncPage({model: data}).render());
}
});
}
app.views.SyncPage = Backbone.View.extend({
template: _.template($("#sync-page").html()),
render: function(eventName) {
$(this.el).html(this.template(_.extend(
{countLocallyModified: this.model.length},
this.model.toJSON())));
this.listView = new app.views.AccountListView({el: $("ul",
this.el), model: this.model});
this.listView.render();
return this;
},
...
});
Note: Although StoreCache is intended for use with SmartSync, you can use any cache mechanism with SmartSync
that meets the requirements described in Offline Caching.
70
Using SmartSync to Access Salesforce Objects Using StoreCache For Offline Caching
soupName
Required. The name of the underlying SmartStore soup.
additionalIndexSpecs
Fields to include in the cache index in addition to default index fields. See Registering a Soup for formatting instructions.
keyField
Name of field containing the record ID. If not specified, StoreCache expects to find the ID in a field named "Id."
Soup items in a StoreCache object include four additional boolean fields for tracking offline edits:
• __locally_created__
• __locally_updated__
• __locally_deleted__
• __local__ (set to true if any of the previous three are true)
These fields are for internal use but can also be used by apps. StoreCache indexes each soup on the __local__ field and its
ID field. You can use the additionalIndexSpecs parameter to specify additional fields to include in the index.
To register the underlying soup, call init() on the StoreCache object. This function returns a jQuery promise that resolves
once soup registration is complete.
StoreCache Methods
init()
Registers the underlying SmartStore soup. Returns a jQuery promise that resolves when soup registration is complete.
retrieve(key [, fieldlist])
Returns a jQuery promise that resolves to the record with key in the keyField returned by the SmartStore. The promise
resolves to null when no record is found or when the found record does not include all the fields in the fieldlist parameter.
key
The key value of the record to be retrieved.
fieldlist
(Optional) A JavaScript array of required fields. For example:
["field1","field2","field3"]
save(record [, noMerge])
Returns a jQuery promise that resolves to the saved record once the SmartStore upsert completes. If noMerge is not
specified or is false, the passed record is merged with the server record with the same key, if one exists.
record
{<field_name1>:"<field_value1>"[,<field_name2>:"<field_value2>",...]}
For example:
71
Using SmartSync to Access Salesforce Objects Using StoreCache For Offline Caching
noMerge
(Optional) Boolean value indicating whether the passed record is to be merged with the matching server record.
Defaults to false.
saveAll(records [, noMerge])
Identical to save(), except that records is an array of records to be saved. Returns a jQuery promise that resolves to
the saved records.
records
An array of records. Each item in the array is formatted as demonstrated for the save() function.
noMerge
(Optional) Boolean value indicating whether the passed record is to be merged with the matching server record.
Defaults to false.
remove(key)
Returns a jQuery promise that resolves when the record with the given key has been removed from the SmartStore.
key
Key value of the record to be removed.
find(querySpec)
Returns a jQuery promise that resolves once the query has been run against the SmartStore. The resolved value is an
object with the following fields:
Field Description
records All fetched records
hasMore Function to check if more records can be retrieved
getMore Function to fetch more records
closeCursor Function to close the open cursor and disable further fetch
querySpec
where query_type_params match the format of the related SmartStore query function call. See Retrieving Data
From a Soup on page 106.
Here are some examples:
{queryType:"exact", indexPath:"<indexed_field_to_match_on>",
matchKey:<value_to_match>, order:"ascending"|"descending",
pageSize:<entries_per_page>}
{queryType:"range", indexPath:"<indexed_field_to_match_on>",
beginKey:<start_of_Range>, endKey:<end_of_range>, order:"ascending"|"descending",
pageSize:<entries_per_page>}
{queryType:"like", indexPath:"<indexed_field_to_match_on>",
likeKey:"<value_to_match>", order:"ascending"|"descending",
pageSize:<entries_per_page>}
72
Using SmartSync to Access Salesforce Objects Conflict Detection
Examples
The following example shows how to create, initialize, and use a StoreCache object.
The next example shows how to use the saveAll() function and the results of the find() function.
// initialization
var cache = new Force.StoreCache("agents", [ {path:"Name", type:"string"}, {path:"Mission",
type:"string"} ]);
cache.init()
.then(function() {
// saving some records
return cache.saveAll([{Id:"007", Name:"JamesBond"},{Id:"008", Name:"Agent008"}, {Id:"009",
Name:"JamesOther"}]);
})
.then(function() {
// doing an exact query
return cache.find({queryType:"exact", indexPath:"Name", matchKey:"Agent008",
order:"ascending", pageSize:1});
})
.then(function(result) {
alert("Agent mission is:" + result.records[0]["Mission"];
});
Conflict Detection
Model objects support optional conflict detection to prevent unwanted data loss when the object is saved to the server. You
can use conflict detection with any save operation, regardless of whether the device is returning from an offline state.
To support conflict detection, you specify a secondary cache to contain the original values fetched from the server. SmartSync
keeps this cache for later reference. When you save or delete, you specify a merge mode. The following table summarizes the
supported modes. To understand the mode descriptions, consider "theirs" to be the current server record, "yours" the current
local record, and "base” the record that was originally fetched from the server.
73
Using SmartSync to Access Salesforce Objects Conflict Detection
Constant
Mode Description
“overwrite” Force.MERGE_MODE.OVERWRITE
Write "yours" to the server,
without comparing to
"theirs" or "base”. (This is
the same as not using
conflict detection.)
“merge-accept-yours” Force.MERGE_MODE.MERGE_ACCEPT_YOURS
Merge "theirs" and "yours".
If the same field is changed
both locally and remotely,
the local value is kept.
“merge-fail-if-conflict” Force.MERGE_MODE.MERGE_FAIL_IF_CONFLICT
Merge "theirs" and "yours".
If the same field is changed
both locally and remotely,
the operation fails.
“merge-fail-if-changed” Force.MERGE_MODE.MERGE_FAIL_IF_CHANGED
Merge "theirs" and "yours".
If any field is changed
remotely, the operation
fails.
If a save or delete operation fails, you receive a report object with the following fields:
MERGE_MODE.OVERWRITE
In the MERGE_MODE.OVERWRITE diagram, the client changes A and B, and the server changes B and C. Changes to B conflict,
whereas changes to A and C do not. However, the save operation blindly writes all the client’s values to the server, overwriting
any changes on the server.
74
Using SmartSync to Access Salesforce Objects Conflict Detection
MERGE_ACCEPT_YOURS
In the MERGE_MODE.MERGE_ACCEPT_YOURS diagram, the client changes A and B, and the server changes B and C. Client
changes (A and B) overwrites corresponding fields on the server, regardless of whether conflicts exist. However, fields that
the client leaves unchanged (C) do not overwrite corresponding server values.
MERGE_FAIL_IF_CONFLICT (Fails)
In the first MERGE_MODE.MERGE_FAIL_IF_CONFLICT diagram, both the client and the server change B. These conflicting
changes cause the save operation to fail.
MERGE_FAIL_IF_CONFLICT (Succeeds)
In the second MERGE_MODE.MERGE_FAIL_IF_CONFLICT diagram, the client changed A, and the server changed B. These
changes don’t conflict, so the save operation succeeds.
75
Using SmartSync to Access Salesforce Objects Mini-Tutorial: Conflict Detection
3. Let's assume that the account has Name:"Acme" and Industry:"Software". Change the name to “Acme2.”
Account.set("Name", "Acme2");
4. Save to the server without specifying a merge mode, so that the default "overwrite" merge mode is used:
account.save(null);
The account’s Name is now "Acme2" and its Industry is "Software" Let's assume that Industry changes on the server to
"Electronics."
5. Change the account Name again:
Account.set("Name", "Acme3");
You now have a change in the cache (Name) and a change on the server (Industry).
6. Save again, using "merge-fail-if-changed" merge mode.
The error callback is called because the server record has changed.
7. Save again, using "merge-fail-if-conflict" merge mode. This merge succeeds because no conflict exists between the change
on the server and the change on the client.
The account’s Name is now "Acme3" (yours) and its Industry is "Electronics" (theirs). Let's assume that, meanwhile, Name
on the server changes to "NewAcme" and Industry changes to "Services."
8. Change the account Name again:
Account.set("Name", "Acme4");
76
Using SmartSync to Access Salesforce Objects Tutorial: Creating a SmartSync Application
9. Save again, using "merge-fail-if-changed" merge mode. The error callback is called because the server record has changed.
The error callback is called because both the server and the cache change the Name field, resulting in a conflict:
11. Save again, using "merge-accept-yours" merge mode. This merge succeeds because your merge mode tells the save()
function which Name value to accept. Also, since you haven’t changed Industry, that field doesn’t conflict.
Name is “Acme4” (yours) and Industry is “Services” (theirs), both in the cache and on the server.
• Backbone.js
• Ratchet
• HTML5
• JavaScript
1. Once you’ve installed Mobile SDK, create a local hybrid project for your platform.
a. For iOS: At the command terminal, enter the following command:
77
Using SmartSync to Access Salesforce Objects Edit the Application HTML File
b. For Android: At the command terminal or the Windows command prompt, enter the following command:
2. Follow the onscreen instructions to open the new project in Eclipse (for Android) or Xcode (for iOS).
3. Open the www folder.
4. Remove the inline.js file from the project.
5. Create a new folder. Name it css.
6. Copy the ratchet.css file into your new css folder.
7. In the www folder, open index.html in your code editor and delete all of its contents.
<!DOCTYPE html>
<html>
<head>
</head>
<body>
</body>
</html>
<script src="jquery/jquery-2.0.0.min.js"></script>
<script src="backbone/underscore-1.4.4.min.js"></script>
<script src="backbone/backbone-1.0.0.min.js"></script>
<script src="cordova-2.3.0.js"></script>
<script src="forcetk.mobilesdk.js"></script>
<script src="cordova.force.js"></script>
<script src="SmartSync.js"></script>
78
Using SmartSync to Access Salesforce Objects Edit the Application HTML File
3. Now let’s start adding content to the body. In the <body> block, add a div tag to contain the app UI.
<body>
<div id="content"></div>
It’s good practice to keep your objects and classes in a namespace. In this sample, we use the app namespace to contain
our models and views.
4. In a <script> tag, create an application namespace. Let’s call it app.
<script>
var app = {
models: {},
views: {}
}
For the remainder of this procedure, continue adding your code in the <script> block.
5. Add an event listener and handler to wait for jQuery, and then call Cordova to start the authentication flow. Also, specify
a callback function, appStart, to handle the user’s credentials.
jQuery(document).ready(function() {
document.addEventListener("deviceready", onDeviceReady,false);
});
function onDeviceReady() {
cordova.require("salesforce/plugin/oauth").getAuthCredentials(appStart);
}
Once the application has initialized and authentication is complete, the Salesforce OAuth plugin calls appStart() and
passes it the user’s credentials. The appStart() function passes the credentials to SmartSync by calling Force.init(),
which initializes SmartSync. The appStart() function also creates a Backbone Router object for the application.
6. Add the appStart() function definition at the end of the <script> block.
function appStart(creds) {
Force.init(creds, null, null,
cordova.require("salesforce/plugin/oauth").forcetkRefresh);
app.router = new app.Router();
Backbone.history.start();
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0,
maximum-scale=1.0; user-scalable=no" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<link rel="stylesheet" href="css/ratchet.css"/>
<script src="jquery/jquery-2.0.0.min.js"></script>
<script src="backbone/underscore-1.4.4.min.js"></script>
<script src="backbone/backbone-1.0.0.min.js"></script>
<script src="cordova-2.3.0.js"></script>
<script src="forcetk.mobilesdk.js"></script>
<script src="cordova.force.js"></script>
<script src="SmartSync.js"></script>
</head>
<body>
<div id="content"></div>
<script id="search-page" type="text/template">
<header class="bar-title">
79
Using SmartSync to Access Salesforce Objects Create a SmartSync Model and a Collection
<h1 class="title">Users</h1>
</header>
<div class="content">
<ul class="list"></ul>
</div>
</script>
<script>
var app = {
models: {},
views: {}
};
jQuery(document).ready(function() {
document.addEventListener("deviceready", onDeviceReady,false);
});
function onDeviceReady() {
cordova.require("salesforce/plugin/oauth").getAuthCredentials(appStart);
}
function appStart(creds) {
console.log(JSON.stringify(creds));
Force.init(creds, null, null,
cordova.require("salesforce/plugin/oauth").forcetkRefresh);
app.router = new app.Router();
Backbone.history.start();
} </script>
</body>
</html>
These objects extend Backbone.Model, so they support the Backbone.Model.extend() function. To extend an object
using this function, pass it a JavaScript object containing your custom properties and functions.
1. In the <body> tag, create a model object for the Salesforce User sObject. Extend Force.SObject to specify the sObject
type and the fields we are targeting.
app.models.User = Force.SObject.extend({
sobjectType: "User",
fieldlist: ["Id", "FirstName", "LastName", "SmallPhotoUrl", "Title", "Email",
"MobilePhone","City"]
})
80
Using SmartSync to Access Salesforce Objects Create a Template
2. Immediately after setting the User object, create a collection to hold user search results.
ExtendForce.SObjectCollection to indicate your new model (app.models.User) as the model for items in the
collection.
app.models.UserCollection = Force.SObjectCollection.extend({
model: app.models.User
});
// Models
app.models.User = Force.SObject.extend({
sobjectType: "User",
fieldlist: ["Id", "FirstName", "LastName", "SmallPhotoUrl", "Title", "Email",
"MobilePhone","City"]
});
app.models.UserCollection = Force.SObjectCollection.extend({
model: app.models.User
});
Create a Template
Templates let you describe an HTML layout within another HTML page. You can define an inline template in your HTML
page by using a <script> tag of type “text/template”. Your JavaScript code can use the template as the page design when it
instantiates a new HTML page at runtime.
The search page template is simple. It includes a header, a search field, and a list to hold the search results.
1. Add a new script block. Place the block within the <body> block just after the “content” <div> tag.
2. In the new <script> block, define the search page HTML template using Ratchet styles.
<div class="content">
<ul class="list"></ul>
</div>
</script>
81
Using SmartSync to Access Salesforce Objects Add the Search View
1. In the <body> block, create a Backbone.View extension named SearchPage in the app.views array.
app.views.SearchPage = Backbone.View.extend({
});
For the remainder of this procedure, add all code to the extend({}) block.
2. Load the search-page template by calling the _.template() function. Pass it the raw HTML content of the search-page
script tag.
template: _.template($("#search-page").html()),
3. Instantiate a sub-view named UserListView to contain the list of search results. (You’ll define the
app.views.UserListView view later.)
initialize: function() {
this.listView = new app.views.UserListView({model: this.model});
},
4. Create a render() function for the search page view. Rendering the view consists simply of loading the template as the
app’s HTML content. Restore any criteria previously typed in the search field and render the sub-view inside the <ul>
element.
render: function(eventName) {
$(this.el).html(this.template());
$(".search-key", this.el).val(this.model.criteria);
this.listView.setElement($("ul", this.el)).render();
return this;
},
5. Add a keyup event handler that performs a search when the user types a character in the search field.
events: {
"keyup .search-key": "search"
},
search: function(event) {
this.model.criteria = $(".search-key", this.el).val();
var soql = "SELECT Id, FirstName, LastName, SmallPhotoUrl, Title FROM User WHERE Name
like '" + this.model.criteria + "%' ORDER BY Name LIMIT 25 ";
this.model.fetch({config: {type:"soql", query:soql}});
}
This function defines a SOQL query. It then uses the backing model to send that query to the server and fetch the results.
app.views.SearchPage = Backbone.View.extend({
template: _.template($("#search-page").html()),
initialize: function() {
this.listView = new app.views.UserListView({model: this.model});
},
render: function(eventName) {
$(this.el).html(this.template());
$(".search-key", this.el).val(this.model.criteria);
this.listView.setElement($("ul", this.el)).render();
return this;
},
82
Using SmartSync to Access Salesforce Objects Add the Search Result List View
events: {
"keyup .search-key": "search"
},
search: function(event) {
this.model.criteria = $(".search-key", this.el).val();
var soql = "SELECT Id, FirstName, LastName, SmallPhotoUrl, Title FROM User WHERE
Name like '" + this.model.criteria + "%' ORDER BY Name LIMIT 25 ";
this.model.fetch({config: {type:"soql", query:soql}});
}
});
1. In the <body> block, create the view for the search result list by extending Backbone.View. Let’s add an array for list
item views as well as an initialize() function.
app.views.UserListView = Backbone.View.extend({
listItemViews: [],
initialize: function() {
this.model.bind("reset", this.render, this);
},
For the remainder of this procedure, add all code to the extend({}) block.
2. Create the render() function to clean up any existing list item views by calling close() on each one.
render: function(eventName) {
_.each(this.listItemViews, function(itemView) { itemView.close(); });
3. In the render() function, create a new set of list item views for the records in the underlying collection. Each of these
views is just an entry in the list. You’ll define the app.views.UserListItemView later.
app.views.UserListView = Backbone.View.extend({
listItemViews: [],
initialize: function() {
this.model.bind("reset", this.render, this);
},
render: function(eventName) {
_.each(this.listItemViews, function(itemView) { itemView.close(); });
this.listItemViews = _.map(this.model.models, function(model) {
return new app.views.UserListItemView({model: model}); });
83
Using SmartSync to Access Salesforce Objects Add the Search Result List Item View
$(this.el).append(_.map(this.listItemViews, function(itemView) {
return itemView.render().el;} ));
return this;
}
});
1. In the <body> block, create a template for a search result list item.
2. Immediately after the template, create the view for the search result list item. Once again, subclassBackbone.View and
indicate that the whole view should be rendered as a list by defining the tagName member. For the remainder of this
procedure, add all code in the extend({}) block.
app.views.UserListItemView = Backbone.View.extend({
tagName: "li",
});
3. Load template by calling _.template() with the raw content of the user-list-item script.
template: _.template($("#user-list-item").html()),
4. In the render() function, simply render the template using data from the model.
render: function(eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
5. Add a close() method to be called from the list view to do necessary cleanup and avoid memory leaks.
close: function() {
this.remove();
this.off();
}
84
Using SmartSync to Access Salesforce Objects Router
app.views.UserListItemView = Backbone.View.extend({
tagName: "li",
template: _.template($("#user-list-item").html()),
render: function(eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
close: function() {
this.remove();
this.off();
}
});
Router
A Backbone router defines navigation paths among views. To learn more about routers, see What is a router?
1. Just before the closing tag of the <body> block, define the application router by extending Backbone.Router.
app.Router = Backbone.Router.extend({
});
For the remainder of this procedure, add all code in the extend({}) block.
2. Because the app supports only one screen, you need only one “route”. Add a routes object.
routes: {
"": "list"
},
3. Define an initialize() function that creates the search result collections and search page view.
initialize: function() {
Backbone.Router.prototype.initialize.call(this);
4. Define the list() function to handle the only item in this route. When the list screen displays, fetch the search results
and render the search view.
list: function() {
app.searchResults.fetch();
$('#content').html(app.searchView.render().el);
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0;
85
Using SmartSync to Access Salesforce Objects Router
user-scalable=no" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<link rel="stylesheet" href="css/ratchet.css"/>
<script src="jquery/jquery-2.0.0.min.js"></script>
<script src="backbone/underscore-1.4.4.min.js"></script>
<script src="backbone/backbone-1.0.0.min.js"></script>
<script src="cordova-2.3.0.js"></script>
<script src="forcetk.mobilesdk.js"></script>
<script src="cordova.force.js"></script>
<script src="SmartSync.js"></script>
</head>
<body>
<div id="content"></div>
<script id="search-page" type="text/template">
<header class="bar-title">
<h1 class="title">Users</h1>
</header>
<div class="content">
<ul class="list"></ul>
</div>
</script>
<script>
var app = {
models: {},
views: {}
};
jQuery(document).ready(function() {
document.addEventListener("deviceready", onDeviceReady,false);
});
function onDeviceReady() {
cordova.require("salesforce/plugin/oauth").getAuthCredentials(appStart);
}
function appStart(creds) {
console.log(JSON.stringify(creds));
Force.init(creds, null, null, cordova.require("salesforce/plugin/oauth").forcetkRefresh);
// Models
app.models.User = Force.SObject.extend({
sobjectType: "User",
fieldlist: ["Id", "FirstName", "LastName", "SmallPhotoUrl", "Title", "Email",
"MobilePhone","City"]
});
app.models.UserCollection = Force.SObjectCollection.extend({
model: app.models.User
});
86
Using SmartSync to Access Salesforce Objects Router
// Views
app.views.SearchPage = Backbone.View.extend({
template: _.template($("#search-page").html()),
initialize: function() {
this.listView = new app.views.UserListView({model: this.model});
},
render: function(eventName) {
$(this.el).html(this.template());
$(".search-key", this.el).val(this.model.criteria);
this.listView.setElement($("ul", this.el)).render();
return this;
},
events: {
"keyup .search-key": "search"
},
search: function(event) {
this.model.criteria = $(".search-key", this.el).val();
var soql = "SELECT Id, FirstName, LastName, SmallPhotoUrl, Title
FROM User WHERE Name like '" + this.model.criteria + "%'
ORDER BY Name LIMIT 25 ";
this.model.fetch({config: {type:"soql", query:soql}});
}
});
app.views.UserListView = Backbone.View.extend({
listItemViews: [],
initialize: function() {
this.model.bind("reset", this.render, this);
},
render: function(eventName) {
_.each(this.listItemViews, function(itemView) { itemView.close(); });
this.listItemViews = _.map(this.model.models, function(model) { return new
app.views.UserListItemView({model: model}); });
$(this.el).append(_.map(this.listItemViews, function(itemView) { return
itemView.render().el;} ));
return this;
}
});
app.views.UserListItemView = Backbone.View.extend({
tagName: "li",
template: _.template($("#user-list-item").html()),
render: function(eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
close: function() {
this.remove();
this.off();
}
});
// Router
app.Router = Backbone.Router.extend({
routes: {
"": "list"
},
initialize: function() {
Backbone.Router.prototype.initialize.call(this);
87
Using SmartSync to Access Salesforce Objects SmartSync Sample Apps
list: function() {
app.searchResults.fetch();
$('#content').html(app.searchView.render().el);
}
});
</script>
</body>
</html>
88
Using SmartSync to Access Salesforce Objects SmartSync Sample Apps
ant debug
ant installd
Note: If you get any errors saying that the local.properties file does not exist, run the following command
from the directory shown in the error message:
To run the sample in Eclipse, import the following projects into your workspace:
• forcedroid/native/SalesforceSDK
• forcedroid/hybrid/SmartStore
• forcedroid/hybrid/SampleApps/AccountEditor
After Eclipse finishes building, Control-click or right-click AccountEditor in the Package Explorer, then click Run As >
Android application.
89
Using SmartSync to Access Salesforce Objects SmartSync Sample Apps
90
Using SmartSync to Access Salesforce Objects User and Group Search Sample
{
"remoteAccessConsumerKey":
"3MVG9Iu66FKeHhINkB1l7xt7kR8czFcCTUhgoA8Ol2Ltf1eYHOU4SqQRSEitYFDUpqRWcoQ2.dBv_a1Dyu5xa",
"oauthRedirectURI": "testsfdc:///mobilesdk/detect/oauth/done",
"oauthScopes": ["api","web"],
"isLocal": true,
"startPage": "UserAndGroupSearch.html",
"errorPage": "error.html",
"shouldAuthenticate": true,
"attemptOfflineLoad": true
}
To run the app from Xcode in iOS, click Run to launch the AccountEditor project. After you’ve logged in, type at least two
characters in the search box to see matching results.
Script Includes
This sample includes the standard list of libraries for SmartSync applications.
• jQuery—See http://jquery.com/.
• Underscore—Utility-belt library for JavaScript, required by backbone) See http://underscorejs.org/
• Backbone—Gives structure to web applications. Used by SmartSync Data Framework. See http://backbonejs.org/.
• cordova-2.3.0.js—Required for all hybrid application used the SalesforceMobileSDK.
• forcetk.mobilesdk.js—Force.com JavaScript library for making Rest API calls. Required by SmartSync.
• cordova.force.js—As of Mobile SDK 2.0, this file combines all Force.com Cordova plugins. Replaces the
SFHybridApp.js, SalesforceOAuthPlugin.js, and SmartStorePlugin.js files.
• SmartSync.js—The Mobile SDK SmartSync Data Framework.
• fastclick.js—Library used to eliminate the 300 ms delay between physical tap and firing of a click event. See
https://github.com/ftlabs/fastclick.
• stackrouter.js and auth.js—Helper JavaScript libraries used by all three sample applications.
Templates
Templates for this application include:
• search-page—template for the entire search page
91
Using SmartSync to Access Salesforce Objects User Search Sample
Models
This application defines a SearchCollection model.
SearchCollection subclasses the Force.SObjectCollection class, which in turn subclasses the Collection class
from the Backbone library. Its only method configures the SOSL query used by the fetch() method to populate the collection.
app.models.SearchCollection = Force.SObjectCollection.extend({
setCriteria: function(key) {
this.config = {type:"sosl", query:"FIND {" + key + "*} IN ALL FIELDS RETURNING "
+ "CollaborationGroup (Id, Name, SmallPhotoUrl, MemberCount), "
+ "User (Id, FirstName, LastName, SmallPhotoUrl, Title ORDER BY Name)
"
+ "LIMIT 25"
};
}
});
Views
User and Group Search defines three views:
SearchPage
The search page expects a SearchCollection as its model. It watches the search input field for changes and updates
the model accordingly.
events: {
"keyup .search-key": "search"
},
search: function(event) {
var key = $(".search-key", this.el).val();
if (key.length >= 2) {
this.model.setCriteria(key);
this.model.fetch();
}
}
ListView
The list portion of the search screen. ListView also expects a Collection as its model and creates ListItemView
objects for each record in the Collection.
ListItemView
Shows details of a single list item, choosing the User or Group template based on the data.
Router
The router does very little because this application defines only one screen.
92
Using SmartSync to Access Salesforce Objects User Search Sample
{
"remoteAccessConsumerKey":
"3MVG9Iu66FKeHhINkB1l7xt7kR8czFcCTUhgoA8Ol2Ltf1eYHOU4SqQRSEitYFDUpqRWcoQ2.dBv_a1Dyu5xa",
"oauthRedirectURI": "testsfdc:///mobilesdk/detect/oauth/done",
"oauthScopes": ["api","web"],
"isLocal": true,
"startPage": "UserSearch.html",
"errorPage": "error.html",
"shouldAuthenticate": true,
"attemptOfflineLoad": true
}
In Xcode or Eclipse, launch the AccountEditor. Log in if prompted to do so. Unlike the User and Group Search example,
you need to type only a single character in the search box to begin seeing search results. That’s because this application uses
SOQL, rather than SOSL, to query the server.
When you tap an entry in the search results list, you see a basic detail screen.
Script Includes
This sample includes the standard list of libraries for SmartSync applications.
• jQuery—See http://jquery.com/.
• Underscore—Utility-belt library for JavaScript, required by backbone) See http://underscorejs.org/
• Backbone—Gives structure to web applications. Used by SmartSync Data Framework. See http://backbonejs.org/.
• cordova-2.3.0.js—Required for all hybrid application used the SalesforceMobileSDK.
• forcetk.mobilesdk.js—Force.com JavaScript library for making Rest API calls. Required by SmartSync.
• cordova.force.js—As of Mobile SDK 2.0, this file combines all Force.com Cordova plugins. Replaces the
SFHybridApp.js, SalesforceOAuthPlugin.js, and SmartStorePlugin.js files.
• SmartSync.js—The Mobile SDK SmartSync Data Framework.
• fastclick.js—Library used to eliminate the 300 ms delay between physical tap and firing of a click event. See
https://github.com/ftlabs/fastclick.
• stackrouter.js and auth.js—Helper JavaScript libraries used by all three sample applications.
Templates
Templates for this application include:
• search-page—template for the whole search page
• user-list-item—template for user list items
• user-page—template for user detail page
93
Using SmartSync to Access Salesforce Objects User Search Sample
Models
This application defines two models: UserCollection and User.
UserCollection subclasses the Force.SObjectCollection class, which in turn subclasses the Collection class from
the Backbone library. Its only method configures the SOQL query used by the fetch() method to populate the collection.
app.models.UserCollection = Force.SObjectCollection.extend({
model: app.models.User,
fieldlist: ["Id", "FirstName", "LastName", "SmallPhotoUrl", "Title"],
setCriteria: function(key) {
this.key = key;
this.config = {type:"soql", query:"SELECT " + this.fieldlist.join(",")
+ " FROM User"
+ " WHERE Name like '" + key + "%'"
+ " ORDER BY Name "
+ " LIMIT 25 "
};
}
});
• An sobjectType field to indicate which type of sObject it represents (User, in this case).
• A fieldlist field that contains the list of fields to be fetched from the server
Here’s the code:
app.models.User = Force.SObject.extend({
sobjectType: "User",
fieldlist: ["Id", "FirstName", "LastName", "SmallPhotoUrl", "Title", "Email",
"MobilePhone","City"]
});
Views
This sample defines four views:
SearchPage
View for the entire search page. It expects a UserCollection as its model. It watches the search input field for changes
and updates the model accordingly in the search() function.
events: {
"keyup .search-key": "search"
},
search: function(event) {
this.model.setCriteria($(".search-key", this.el).val());
this.model.fetch();
}
UserListView
View for the list portion of the search screen. It also expects a UserCollection as its model and creates
UserListItemView objects for each user in the UserCollection object.
UserListItemView
View for a single list item.
UserPage
View for displaying user details.
94
Using SmartSync to Access Salesforce Objects Account Editor Sample
Router
The router class handles navigation between the app’s two screens. This class uses a routes field to map those view to
router class method.
routes: {
"": "list",
"list": "list",
"users/:id": "viewUser"
},
The list page calls fetch() to fill the search result collections, then brings the search page into view.
list: function() {
app.searchResults.fetch();
// Show page right away - list will redraw when data comes in
this.slidePage(app.searchPage);
},
The user detail page calls fetch() to fill the user model, then brings the user detail page into view.
viewUser: function(id) {
var that = this;
var user = new app.models.User({Id: id});
user.fetch({
success: function() {
app.userPage.model = user;
that.slidePage(app.userPage);
}
});
}
• Accounts search
• Accounts detail
• Sync
When the application first starts, you see the Accounts search screen listing the most recently used accounts. In this screen,
you can:
• Type a search string to find accounts whose names contain the given string.
• Tap an account to launch the account detail screen.
• Tap Create to launch an empty account detail screen.
• Tap Online to go offline. If you are already offline, you can tap the Offline button to go back online. (You can also go
offline by putting the device in airplane mode.)
95
Using SmartSync to Access Salesforce Objects Account Editor Sample
To launch the Account Detail screen, tap an account record in the Accounts search screen. The detail screen shows you the
fields in the selected account. In this screen, you can:
To see the Sync screen, tap Online to go offline, then create, update, or delete an account. When you tap Offline again to
go back online, the Sync screen shows all accounts that you modified on the device.
Tap Process n records to try to save your local changes to the server. If any account fails to save, it remains in the list with a
notation that it failed to sync. You can tap any account in the list to edit it further or, in the case of a locally deleted record,
to undelete it.
Script Includes
This sample includes the standard list of libraries for SmartSync applications.
• jQuery—See http://jquery.com/.
• Underscore—Utility-belt library for JavaScript, required by backbone. See http://underscorejs.org/.
• Backbone—Gives structure to web applications. Used by SmartSync Data Framework. See http://backbonejs.org/.
• cordova-2.3.0.js—Required for hybrid applications using the Salesforce Mobile SDK.
• forcetk.mobilesdk.js—Force.com JavaScript library for making REST API calls. Required by SmartSync.
• cordova.force.js—As of Mobile SDK 2.0, this file combines all Force.com Cordova plugins. Replaces the
SFHybridApp.js, SalesforceOAuthPlugin.js, and SmartStorePlugin.js files.
• SmartSync.js—The Mobile SDK SmartSync Data Framework.
• fastclick.js—Library used to eliminate the 300 ms delay between physical tap and firing of a click event. See
https://github.com/ftlabs/fastclick.
• stackrouter.js and auth.js—Helper JavaScript libraries used by all three sample applications.
Templates
Templates for this application include:
• search-page
• sync-page
96
Using SmartSync to Access Salesforce Objects Account Editor Sample
• account-list-item
• edit-account-page (for the Account detail page)
Models
This sample defines three models: AccountCollection, Account and OfflineTracker.
AccountCollection is a subclass of SmartSync’s Force.SObjectCollection class, which is a subclass of the Backbone
framework’s Collection class.
The AccountCollection.config() method returns an appropriate query to the collection. The query mode can be:
• Most recently used (MRU) if you are online and haven’t provided query criteria
• SOQL if you are online and have provided query criteria
• SmartSQL when you are offline
When the app calls fetch() on the collection, the fetch() function executes the query returned by config(). It then
uses the results of this query to populate AccountCollection with Account objects from either the offline cache or the
server.
AccountCollection uses the two global caches set up by the AccountEditor application: app.cache for offline storage,
and app.cacheForOriginals for conflict detection. The code shows that the AccountCollection model:
• Contains objects of the app.models.Account model (model field)
• Specifies a list of fields to be queried (fieldlist field)
• Uses the sample app’s global offline cache (cache field)
• Uses the sample app’s global conflict detection cache (cacheForOriginals field)
• Defines a config() function to handle online as well as offline queries
Here’s the code (shortened for readability):
app.models.AccountCollection = Force.SObjectCollection.extend({
model: app.models.Account,
fieldlist: ["Id", "Name", "Industry", "Phone", "Owner.Name", "LastModifiedBy.Name",
"LastModifiedDate"],
cache: function() { return app.cache},
cacheForOriginals: function() { return app.cacheForOriginals;},
config: function() {
// Offline: do a cache query
if (!app.offlineTracker.get("isOnline")) {
...
}
// Online
else {
...
}
}
});
Account is a subclass of SmartSync’s Force.SObject class, which is a subclass of the Backbone framework’s Model class.
Code for the Account model shows that it:
• Uses a sobjectType field to indicate which type of sObject it represents (Account, in this case).
• Defines fieldlist as a method rather than a field, because the fields that it retrieves from the server are not the same
as the ones it sends to the server.
• Uses the sample app’s global offline cache (cache field).
• Uses the sample app’s global conflict detection cache (cacheForOriginals field).
• Supports a cacheMode() method that returns a value indicating how to handle caching based on the current offline status.
97
Using SmartSync to Access Salesforce Objects Account Editor Sample
app.models.Account = Force.SObject.extend({
sobjectType: "Account",
fieldlist: function(method) {
return method == "read"
? ["Id", "Name", "Industry", "Phone", "Owner.Name", "LastModifiedBy.Name",
"LastModifiedDate"]
: ["Id", "Name", "Industry", "Phone"];
},
cache: function() { return app.cache;},
cacheForOriginals: function() { return app.cacheForOriginals;},
cacheMode: function(method) {
if (!app.offlineTracker.get("isOnline")) {
return Force.CACHE_MODE.CACHE_ONLY;
}
// Online
else {
return (method == "read" ? Force.CACHE_MODE.CACHE_FIRST :
Force.CACHE_MODE.SERVER_FIRST);
}
}
});
OfflineTracker is a subclass of Backbone’s Model class. This class tracks the offline status of the application by observing
the browser’s offline status. It automatically switches the app to offline when it detects that the browser is offline. However,
it goes online only when the user requests it.
Here’s the code:
app.models.OfflineTracker = Backbone.Model.extend({
initialize: function() {
var that = this;
this.set("isOnline", navigator.onLine);
document.addEventListener("offline", function() {
console.log("Received OFFLINE event");
that.set("isOnline", false);
}, false);
document.addEventListener("online", function() {
console.log("Received ONLINE event");
// User decides when to go back online
}, false);
}
});
Views
This sample defines five views:
• SearchPage
• AccountListView
• AccountListItemView
• EditAccountView
• SyncPage
A view typically provides a template field to specify its design template, an initialize() function, and a render() function.
Each view can also define an events field. This field contains an array whose key/value entries specify the event type and the
event handler function name. Entries use the following format:
98
Using SmartSync to Access Salesforce Objects Account Editor Sample
For example:
events: {
"click .button-prev": "goBack",
"change": "change",
"click .save": "save",
"click .merge": "saveMerge",
"click .overwrite": "saveOverwrite",
"click .toggleDelete": "toggleDelete"
},
SearchPage
View for the entire search screen. It expects an AccountCollection as its model. It watches the search input field for
changes (the keyup event) and updates the model accordingly in the search() function.
events: {
"keyup .search-key": "search"
},
search: function(event) {
this.model.setCriteria($(".search-key", this.el).val());
this.model.fetch();
}
AcountListView
View for the list portion of the search screen. It expects an AccountCollection as its model and creates
AccountListItemView object for each account in the AccountCollection object.
AccountListItemView
View for an item within the list.
EditAccountPage
View for account detail page. This view monitors several events:
A couple of event handler functions deserve special attention. The change() function shows how the view uses the
event target to send user edits back to the model:
change: function(evt) {
// apply change to model
var target = event.target;
this.model.set(target.name, target.value);
$("#account" + target.name + "Error", this.el).hide();
}
99
Using SmartSync to Access Salesforce Objects Account Editor Sample
The toggleDelete() function handles a toggle that lets the user delete or undelete an account. If the user clicks to
undelete, the code sets an internal __locally_deleted__ flag to false to indicate that the record is no longer
deleted in the cache. Else, it attempts to delete the record on the server by destroying the local model.
toggleDelete: function() {
if (this.model.get("__locally_deleted__")) {
this.model.set("__locally_deleted__", false);
this.model.save(null, this.getSaveOptions(null, Force.CACHE_MODE.CACHE_ONLY));
}
else {
this.model.destroy({
success: function(data) {
app.router.navigate("#", {trigger:true});
},
error: function(data, err, options) {
var error = new Force.Error(err);
alert("Failed to delete account: " + (error.type === "RestError" ?
error.details[0].message :
"Remote change detected - delete aborted"));
}
});
}
}
SyncPage
View for the sync page. This view monitors several events:
To see how the all screen is rendered, look at the render method:
render: function(eventName) {
$(this.el).html(this.template(_.extend(
{countLocallyModified: this.model.length},
this.model.toJSON())));
this.listView.setElement($("ul", this.el)).render();
return this;
},
Let’s take a look at what happens when the user taps Process (the sync control).
The sync() function looks at the first locally modified Account in the view’s collection and tries to save it to the server.
If the save succeeds and there are no more locally modified records, the app navigates back to the search screen. Otherwise,
the app marks the account as having failed locally and then calls sync() again.
sync: function(event) {
var that = this;
if (this.model.length == 0 || this.model.at(0).get("__sync_failed__")) {
// we push sync failures back to the end of the list -
// if we encounter one, it means we are done
return;
}
else {
100
Using SmartSync to Access Salesforce Objects Account Editor Sample
var options = {
mergeMode: Force.MERGE_MODE.MERGE_FAIL_IF_CHANGED,
success: function() {
if (that.model.length == 0) {
app.router.navigate("#", {trigger:true});
}
else {
that.sync();
}
},
error: function() {
record = record.set("__sync_failed__", true);
that.model.push(record);
that.sync();
}
};
return record.get("__locally_deleted__") ? record.destroy(options) :
record.save(null, options);
}
});
Router
When the router is initialized, it sets up the two global caches used throughout the sample.
setupCaches: function() {
// Cache for offline support
app.cache = new Force.StoreCache("accounts", [ {path:"Name", type:"string"} ]);
Once the global caches are set up, it also sets up two AccountCollection objects: One for the search screen, and one for
the sync screen.
Finally, it creates the view objects for the Search, Sync, and EditAccount screens.
The router has a routes field that maps actions to methods on the router class.
routes: {
"": "list",
"list": "list",
"add": "addAccount",
"edit/accounts/:id": "editAccount",
101
Using SmartSync to Access Salesforce Objects Account Editor Sample
"sync":"sync"
},
The list action fills the search result collections by calling fetch() and brings the search page into view.
list: function() {
app.searchResults.fetch();
// Show page right away - list will redraw when data comes in
this.slidePage(app.searchPage);
},
The addAccount action creates an empty account object and bring the edit page for that account into view.
addAccount: function() {
app.editPage.model = new app.models.Account({Id: null});
this.slidePage(app.editPage);
},
The editAccount action fetches the specified Account object and brings the account detail page into view.
editAccount: function(id) {
var that = this;
var account = new app.models.Account({Id: id});
account.fetch({
success: function(data) {
app.editPage.model = account;
that.slidePage(app.editPage);
},
error: function() {
alert("Failed to get record for edit");
}
});
}
The sync action computes the localAccounts collection by calling fetch and brings the sync page into view.
sync: function() {
app.localAccounts.fetch();
// Show page right away - list will redraw when data comes in
this.slidePage(app.syncPage);
}
102
Chapter 7
Securely Storing Data Offline
In this chapter ... Mobile devices can lose connection at any time, and environments such as
hospitals and airplanes often prohibit connectivity. To handle these situations,
• Accessing SmartStore in Hybrid it’s important that your mobile apps continue to function when they go offline.
Apps
• Adding SmartStore to Android Apps The Mobile SDK uses SmartStore, a secure offline storage solution on your
• Offline Hybrid Development device. SmartStore allows you to continue working even when the device is not
connected to the Internet. SmartStore stores data as JSON documents in a data
• SmartStore Soups
structure called a soup. A soup is a simple one-table database of “entries” which
• Registering a Soup
can be indexed in different ways and queried by a variety of methods.
• Retrieving Data From a Soup
• Smart SQL Queries Mobile SDK 2.0 provides a StoreCache mechanism that works with SmartStore
• Working With Cursors soups to provide offline synchronization and conflict resolution services. You
• Manipulating Data can control these services by providing simple configuration settings. We
recommend that you use StoreCache to manage operations on Salesforce data.
• Using the Mock SmartStore
See Using StoreCache For Offline Caching on page 70 and Conflict Detection
• NativeSqlAggregator Sample App:
on page 73
Using SmartStore in Native Apps
Note: Pure HTML5 apps store offline information in a browser cache.
Browser caching isn’t part of the Mobile SDK, and we don’t document
it here. SmartStore uses storage functionality on the device. This
strategy requires a native or hybrid development path.
Sample Objects
The code snippets in this chapter use two objects, Account and Opportunity,
which come predefined with every Salesforce organization. Account and
Opportunity have a master-detail relationship; an Account can have more than
one Opportunity.
103
Securely Storing Data Offline Accessing SmartStore in Hybrid Apps
1. Add the SmartStore library project to your project. In Eclipse, choose Properties from the Project menu. Select Android
from the left panel, then click Add on the right-hand side. Choose the hybrid/SmartStore project.
2. In your projectnameApp.java file, import the SalesforceSDKManagerWithSmartStore class instead of SalesforceSDKManager.
Replace this statement:
import com.salesforce.androidsdk.app.SalesforceSDKManager
import com.salesforce.androidsdk.smartstore.app.SalesforceSDKManagerWithSmartStore
3. In your projectnameApp.java file, change your App class to extend the SalesforceSDKManagerWithSmartStore class rather
than SalesforceSDKManager.
SmartStore Soups
You store your offline data in SmartStore in one or more soups. A soup, conceptually speaking, is a logical collection of data
records—represented as JSON objects—that you want to store and query offline. In the Force.com world, a soup will typically
map to a standard or custom object that you wish to store offline, but that is not a hard and fast rule. You can store as many
soups as you want in an application, but remember that soups are meant to be self-contained data sets; there is no direct
104
Securely Storing Data Offline Registering a Soup
correlation between them. In addition to storing the data itself, you can also specify indices that map to fields within the data,
for greater ease and customization of data queries.
Note:
SmartStore data is inherently volatile. Its lifespan is tied to the authenticated user as well as to OAuth token states.
When the user logs out of the app, SmartStore deletes all soup data associated with that user. Similarly, when the
OAuth refresh token is revoked or expires, the user’s app state is reset, and all data in SmartStore is purged. Carefully
consider the volatility of SmartStore data when designing your app. This warning is especially important if your org
sets a short lifetime for the refresh token.
Registering a Soup
In order to access a soup, you first need to register it. Provide a name, index specifications, and names of callback functions
for success and error conditions:
If the soup does not already exist, this function creates it. If the soup already exists, registering gives you access to the existing
soup. To find out if a soup already exists, use:
A soup is indexed on one or more fields found in its entries. Insert, update, and delete operations on soup entries are tracked
in the soup indices. Always specify at least one index field when registering a soup. For example, if you are using the soup as
a simple key/value store, use a single index specification with a string type.
indexSpecs
The indexSpecs array is used to create the soup with predefined indexing. Entries in the indexSpecs array specify how
the soup should be indexed. Each entry consists of a path:type pair. path is the name of an index field; type is either
“string”, “integer”, or “floating”. Index paths are case-sensitive and can include compound paths, such as Owner.Name.
Note: Performance can suffer if the index path is too deep. If index entries are missing any fields described in a
particular indexSpec, they will not be tracked in that index.
"indexSpecs":[
{
"path":"Name",
"type":"string"
}
{
"path":"Id",
"type":"string"
}
{
"path":"ParentId",
"type":"string"
}
{
"path":"lastModifiedDate",
"type":"integer"
}
]
105
Securely Storing Data Offline Retrieving Data From a Soup
Note: Currently, the Mobile SDK supports three index types: “string”, “integer”, and “floating”. These types apply
only to the index itself, and not to the way data is stored or retrieved. It’s OK to have a null field in an index column.
successCallback
The success callback function for registerSoup takes one argument (the soup name).
A successful creation of the soup returns a successCallback that indicates the soup is ready. Wait to complete the transaction
and receive the callback before you begin any activity. If you register a soup under the passed name, the success callback function
returns the soup.
errorCallback
The error callback function for registerSoup takes one argument (the error description string).
During soup creation, errors can happen for a number of reasons, including:
• An invalid or bad soup name
• No index (at least one index must be specified)
• Other unexpected errors, such as a database error
Parameter Description
indexPath This is what you’re searching for; for example a name, account number, or date.
beginKey Optional. Used to define the start of a range query.
endKey Optional. Used to define the end of a range query.
order Optional. Either “ascending” or “descending.”
pageSize Optional. If not present, the native plugin can return whatever page size it sees fit in the
resulting Cursor.pageSize.
Note:
All queries are single-predicate searches. Only SmartSQL queries support joins.
Query Everything
buildAllQuerySpec(indexPath, order, [pageSize]) returns all entries in the soup, with no particular order. Use
this query to traverse everything in the soup.
order and pageSize are optional, and default to ascending and 10, respectively. You can specify:
106
Securely Storing Data Offline Retrieving Data From a Soup
• buildAllQuerySpec(indexPath)
• buildAllQuerySpec(indexPath, order)
• buildAllQuerySpec(indexPath, order, [pageSize])
iOS native:
Android native:
Query by Exact
buildExactQuerySpec(indexPath, matchKey, [pageSize]) finds entries that exactly match the given matchKey
for the indexPath value. Use this to find child entities of a given ID. For example, you can find Opportunities by Status.
However, you can’t specify order in the results.
Sample code for retrieving children by ID:
107
Securely Storing Data Offline Smart SQL Queries
Query by Range
buildRangeQuerySpec(indexPath, beginKey, endKey, [order, pageSize]) finds entries whose indexPath
values fall into the range defined by beginKey and endKey. Use this function to search by numeric ranges, such as a range
of dates stored as integers.
order and pageSize are optional, and default to ascending and 10, respectively. You can specify:
Query by Like
buildLikeQuerySpec(indexPath, likeKey, [order, pageSize]) finds entries whose indexPath values are
like the given likeKey. You can use “foo%” to search for terms that begin with your keyword, “%foo” to search for terms that
end with your keyword, and “%foo%” to search for your keyword anywhere in the indexPath value. Use this function for
general searching and partial name matches. order and pageSize are optional, and default to ascending and 10, respectively.
Note: Query by Like is the slowest of the query methods.
navigator.smartstore.querySoup(soupName,querySpec,successCallback,errorCallback);
108
Securely Storing Data Offline Smart SQL Queries
Syntax
Syntax is identical to the standard SQL SELECT specification but with the following adaptations.
Usage Syntax
To specify a column {<soupName>:<path>}
Sample Queries
Consider two soups: one named Employees, and another named Departments. The Employees soup contains standard fields
such as:
• First name (firstName)
• Last name (lastName)
• Department code (deptCode)
• Employee ID (employeeId)
• Manager ID (managerId)
The Departments soup contains:
• Name (name)
• Department code (deptCode)
Here are some examples of basic Smart SQL queries using these soups:
select {departments:name}
from {departments}
order by {departments:deptCode}
Joins
Smart SQL also allows you to use joins. For example:
109
Securely Storing Data Offline Working With Cursors
Aggregate Functions
Smart SQL support the use of aggregate functions such as:
• COUNT
• SUM
• AVG
For example:
select {account:name},
count({opportunity:name}),
sum({opportunity:amount}),
avg({opportunity:amount}),
{account:id},
{opportunity:accountid}
from {account},
{opportunity}
where {account:id} = {opportunity:accountid}
group by {account:name}
The NativeSqlAggregator sample app delivers a fully implemented native implementation of SmartStore, including SmartSQL
support for aggregate functions and joins. See NativeSqlAggregator Sample App: Using SmartStore in Native Apps on page
113.
Note: For advanced users: Cursors are not snapshots of data; they are dynamic. If you make changes to the soup and
then start paging through the cursor, you will see those changes. The only data the cursor holds is the original query
and your current position in the result set. When you move your cursor, the query runs again. Thus, newly created
soup entries can be returned (assuming they satisfy the original query).
Note: successCallback for those functions should expect one argument (the updated cursor).
110
Securely Storing Data Offline Manipulating Data
Manipulating Data
In order to track soup entries for insert, update, and delete, SmartStore adds a few fields to each entry:
• _soupEntryId—This field is the primary key for the soup entry in the table for a given soup.
• _soupLastModifiedDate—The number of milliseconds since 1/1/1970.
When inserting or updating soup entries, SmartStore automatically sets these fields. When removing or retrieving specific
entries, you can reference them by _soupEntryId.
where soupName is the name of the target soup, and entries is an array of one or more entries that match the soup’s data
structure. The successCallback and errorCallback parameters function much like the ones for registerSoup.
However, the success callback for upsertSoupEntries indicates that either a new record has been inserted, or an existing
record has been updated.
111
Securely Storing Data Offline Manipulating Data
The following code contains sample scenarios. First, it calls upsertSoupEntries to create a new soup entry. In the success
callback, the code retrieves the new record with its newly assigned soup entry ID. It then changes the description and calls
forcetk.mobilesdk methods to create the new account on the server and then update it. The final call demonstrates the
upsert with external ID. To make the code more readable, no error callbacks are specified. Also, because all SmartStore calls
are asynchronous, real applications should do each step in the callback of the previous step.
// Create account on server (sync client -> server for entities created locally)
forcetkClient.create("account", {"Name": acc["Name"], "Description": acc["Description"]},
function(result) {
acc["id"] = result["id"];
// Update account in SmartStore
navigator.smartstore.upsertSoupEntries("accounts", [ acc ]);
});
///// Later, there is an account (with id: someSfdcId) that you want to get locally
///// There might be an older version of that account in the SmartStore already
Removing a Soup
To remove a soup, call removeSoup(). Note that once a user signs out, the soups get deleted automatically.
navigator.smartstore.removeSoup(soupName,successCallback,errorCallback);
112
Securely Storing Data Offline Using the Mock SmartStore
Note: The MockSmartStore doesn’t encrypt data and is not meant to be used in production applications.
Inside the PhoneGap directory, there’s a local directory containing the following files:
• MockCordova.js—A minimal implementation of Cordova functions meant only for testing plugins outside the container.
• MockSmartStore.js—A JavaScript implementation of the SmartStore meant only for development and testing outside
the container.
• MockSmartStorePlugin.js—A JavaScript helper class that intercepts SmartStore Cordova plugin calls and handles
them using a MockSmartStore.
• CordovaInterceptor.js—A JavaScript helper class that intercepts Cordova plugin calls.
When writing an application using SmartStore, make the following changes to test your app outside the container:
Same-origin Policies
Same-origin policy permits scripts running on pages originating from the same site to access each other’s methods and properties
with no specific restrictions; it also blocks access to most methods and properties across pages on different sites. Same-origin
policy restrictions are not an issue when your code runs inside the container, because the container disables same-origin policy
in the webview. However, if you call a remote API, you need to worry about same-origin policy restrictions.
Fortunately, browsers offer ways to turn off same-origin policy, and you can research how to do that with your particular
browser. If you want to make XHR calls against Force.com from JavaScript files loaded from the local file system, you should
start your browser with same-origin policy disabled. The following article describes how to disable same-origin policy on
several popular browsers: Getting Around Same-Origin Policy in Web Browsers.
Authentication
For authentication with MockSmartStore, you will need to capture access tokens and refresh tokens from a real session and
hand code them in your JavaScript app. You’ll also need these tokens to initialize the forcetk.mobilesdk JavaScript toolkit.
Creating a Soup
The first step to storing a Salesforce object in SmartStore is to create a soup for the object. The function call to register a soup
takes two arguments - the name of the soup, and the index specs for the soup. Indexing supports three types of data: string,
113
Securely Storing Data Offline NativeSqlAggregator Sample App: Using SmartStore in Native
Apps
integer, and floating decimal. The following example illustrates how to initialize a soup for the Account object with indexing
on Name, Id, and OwnerId fields.
Android:
SalesforceSDKManagerWithSmartStore sdkManager =
SalesforceSDKManagerWithSmartStore.getInstance();
IndexSpec[] ACCOUNTS_INDEX_SPEC = {
new IndexSpec("Name", Type.string),
new IndexSpec("Id", Type.string),
new IndexSpec("OwnerId", Type.string)
};
smartStore.registerSoup("Account", ACCOUNTS_INDEX_SPEC);
iOS:
iOS:
114
Securely Storing Data Offline NativeSqlAggregator Sample App: Using SmartStore in Native
Apps
SELECT {Account:Name},
COUNT({Opportunity:Name}),
SUM({Opportunity:Amount}),
AVG({Opportunity:Amount}), {Account:Id}, {Opportunity:AccountId}
FROM {Account}, {Opportunity}
WHERE {Account:Id} = {Opportunity:AccountId}
GROUP BY {Account:Name}
This query represents an implicit join between two soups, Account and Opportunity. It returns:
• Name of the Account
• Number of opportunities associated with an Account
• Sum of all the amounts associated with each Opportunity of that Account
• Average amount associated with an Opportunity of that Account
• Grouping by Account name
The code snippet below demonstrates how to run such queries from within a native app. In this example, smartSql is the
query and pageSize is the requested page size. The pageIndex argument specifies which page of results you want returned.
Android:
iOS:
115
Chapter 8
Authentication, Security, and Identity in Mobile Apps
In this chapter ... Secure authentication is essential for enterprise applications running on mobile
devices. OAuth2 is the industry-standard protocol that allows secure
• OAuth Terminology authentication for access to a user's data, without handing out the username
• Creating a Connected App and password. It is often described as the valet key of software access: a valet
• Connected Apps key only allows access to certain features of your car: you cannot open the trunk
• OAuth2 Authentication Flow or glove compartment using a valet key.
• Portal Authentication Using OAuth
Mobile app developers can quickly and easily embed the Salesforce OAuth2
2.0 and Force.com Sites
implementation. The implementation uses an HTML view to collect the
username and password, which are then sent to the server. A session token is
returned and securely stored on the device for future interactions.
A Salesforce connected app is the primary means by which a mobile device
connects to Salesforce. A connected app gives both the developer and the
administrator control over how the app connects and who has access. For
example, a connected app can be restricted to certain users, can set or relax an
IP range, and so forth.
116
Authentication, Security, and Identity in Mobile Apps OAuth Terminology
OAuth Terminology
Access Token
A value used by the consumer to gain access to protected resources on behalf of the user, instead of using the user’s
Salesforce credentials. The access token is a session ID, and can be used directly.
Authorization Code
A short-lived token that represents the access granted by the end user. The authorization code is used to obtain an access
token and a refresh token.
Connected App
An application external to Salesforce that uses the OAuth protocol to verify both the Salesforce user and the external
application. Replaces remote access application.
Consumer Key
A value used by the consumer to identify itself to Salesforce. Referred to as client_id.
Refresh Token
A token used by the consumer to obtain a new access token, without having the end user approve the access again.
Note: The Callback URL does not have to be a valid URL; it only has to match what the app expects in this
field. You can use any custom prefix, such as sfdc://.
Note: After you create a new connected app, wait a few minutes for the token to propagate before running your app.
117
Authentication, Security, and Identity in Mobile Apps Connected Apps
Tip: The detail page for your connected app displays a consumer key. It’s a good idea to copy the key, as you’ll need
it later.
Connected Apps
A Connected App is an application that integrates with salesforce.com using APIs. Connected Apps use standard SAML and
OAuth protocols to authenticate, provide Single Sign-On, and provide tokens for use with Salesforce APIs. In addition to
standard OAuth capabilities, Connected Apps allow administrators to set various security policies and have explicit control
over who may use the applications.
Connected Apps begin with a developer defining OAuth metadata about the application, including:
In return, the developer is provided an OAuth client Id and client secret for the Connected App. The developer can then
package the app and provide it to a Salesforce administrator.
Administrators can install the Connected App into their organization and use profiles, permission sets, and IP range restrictions
to control which users can access the application. Management is done from a detail page for the Connected App. Administrators
can also uninstall the Connected App and install a newer version. When the app is updated, the developer can notify
administrators that there is a new version available for the app.
Note: Salesforce-managed packages like those for the Chatter mobile apps can’t be uninstalled. They are automatically
updated when the next user’s session refreshes.
Note: Because PIN security is implemented in the mobile device’s operating system, only native and hybrid mobile
apps can use PIN protection; HTML5 Web apps can’t use PIN protection.
In practice, PIN protection can be used so that the mobile app locks up if it’s isn’t used for a specified number of minutes.
When a mobile app is sent to the background, the clock continues to tick.
To illustrate how PIN protection works:
118
Authentication, Security, and Identity in Mobile Apps OAuth2 Authentication Flow
Ongoing Authentication
1. User opens a mobile application.
2. If the session ID is active, the app starts immediately. If the session ID is stale, the app uses the refresh token from its
initial authorization to get an updated session ID.
3. App starts.
119
Authentication, Security, and Identity in Mobile Apps OAuth 2.0 Refresh Token Flow
Warning: Because the access token is encoded into the redirection URI, it might be exposed to the end-user and
other applications residing on the computer or device.
If you are authenticating using JavaScript, call window.location.replace(); to remove the callback from the
browser’s history.
1. The client application directs the user to Salesforce to authenticate and authorize the application.
2. The user must always approve access for this authentication flow. After approving access, the application receives the
callback from Salesforce.
After obtaining an access token, the consumer can use the access token to access data on the end-user’s behalf and receive a
refresh token. Refresh tokens let the consumer get a new access token if the access token becomes invalid for any reason.
1. The consumer uses the existing refresh token to request a new access token.
2. After the request is verified, Salesforce sends a response to the client.
Note:
Mobile SDK apps can use the SmartStore feature to store data locally for offline use. SmartStore data is inherently
volatile. Its lifespan is tied to the authenticated user as well as to OAuth token states. When the user logs out of the
app, SmartStore deletes all soup data associated with that user. Similarly, when the OAuth refresh token is revoked
or expires, the user’s app state is reset, and all data in SmartStore is purged. Carefully consider the volatility of
120
Authentication, Security, and Identity in Mobile Apps Scope Parameter Values
SmartStore data when designing your app. This warning is especially important if your org sets a short lifetime for
the refresh token.
Value Description
api Allows access to the current, logged-in user’s account using APIs, such as REST API and
Bulk API. This value also includes chatter_api, which allows access to Chatter REST
API resources.
chatter_api Allows access to Chatter REST API resources only.
full Allows access to all data accessible by the logged-in user. full does not return a refresh
token. You must explicitly request the refresh_token scope to get a refresh token.
id Allows access only to the identity URL service.
refresh_token Allows a refresh token to be returned if you are eligible to receive one.
visualforce Allows access to Visualforce pages.
web Allows the ability to use the access_token on the Web. This also includes visualforce,
allowing access to Visualforce pages.
Parameter Description
Access token See “Using the Access Token” in the Salesforce Help.
121
Authentication, Security, and Identity in Mobile Apps Using Identity URLs
Parameter Description
Format This parameter is optional. Specify the format of the returned
output. Valid values are:
• urlencoded
• json
• xml
Instead of using the format parameter, the client can also
specify the returned format in an accept-request header using
one of the following:
• Accept: application/json
• Accept: application/xml
• Accept: application/x-www-form-urlencoded
122
Authentication, Security, and Identity in Mobile Apps Using Identity URLs
◊ created_date:xsd datetime value of the creation date of the last post by the user, for example,
2010-05-08T05:17:51.000Z
◊ body: the body of the post
Note: Accessing these URLs requires passing an access token. See “Using the Access Token” in the Salesforce
Help.
◊ picture
◊ thumbnail
• urls—A map containing various API endpoints that can be used with the specified user.
Note: Accessing the REST endpoints requires passing an access token. See “Using the Access Token” in the
Salesforce Help.
◊ enterprise (SOAP)
◊ metadata (SOAP)
◊ partner (SOAP)
◊ profile
◊ feeds (Chatter)
◊ feed-items (Chatter)
◊ groups (Chatter)
◊ users (Chatter)
◊ custom_domain—This value is omitted if the organization doesn’t have a custom domain configured and propagated
123
Authentication, Security, and Identity in Mobile Apps Using Identity URLs
<photos>
<picture>http://na1.salesforce.com/profilephoto/005/F</picture>
<thumbnail>http://na1.salesforce.com/profilephoto/005/T</thumbnail>
</photos>
<urls>
<enterprise>http://na1.salesforce.com/services/Soap/c/{version}/00Dx0000001T0zk
</enterprise>
<metadata>http://na1.salesforce.com/services/Soap/m/{version}/00Dx0000001T0zk
</metadata>
<partner>http://na1.salesforce.com/services/Soap/u/{version}/00Dx0000001T0zk
</partner>
<rest>http://na1.salesforce.com/services/data/v{version}/
</rest>
<sobjects>http://na1.salesforce.com/services/data/v{version}/sobjects/
</sobjects>
<search>http://na1.salesforce.com/services/data/v{version}/search/
</search>
<query>http://na1.salesforce.com/services/data/v{version}/query/
</query>
<profile>http://na1.salesforce.com/005x0000001S2b9
</profile>
</urls>
<active>true</active>
<user_type>STANDARD</user_type>
<language>en_US</language>
<locale>en_US</locale>
<utcOffset>-28800000</utcOffset>
<last_modified_date>2010-06-28T20:54:09.000Z</last_modified_date>
</user>
{"id":"http://na1.salesforce.com/id/00Dx0000001T0zk/005x0000001S2b9",
"asserted_user":true,
"user_id":"005x0000001S2b9",
"organization_id":"00Dx0000001T0zk",
"nick_name":"admin1.2777578168398293E12foofoofoofoo",
"display_name":"Alan Van",
"email":"admin@2060747062579699.com",
"status":{"created_date":null,"body":null},
"photos":{"picture":"http://na1.salesforce.com/profilephoto/005/F",
"thumbnail":"http://na1.salesforce.com/profilephoto/005/T"},
"urls":
{"enterprise":"http://na1.salesforce.com/services/Soap/c/{version}/00Dx0000001T0zk",
"metadata":"http://na1.salesforce.com/services/Soap/m/{version}/00Dx0000001T0zk",
"partner":"http://na1.salesforce.com/services/Soap/u/{version}/00Dx0000001T0zk",
"rest":"http://na1.salesforce.com/services/data/v{version}/",
"sobjects":"http://na1.salesforce.com/services/data/v{version}/sobjects/",
"search":"http://na1.salesforce.com/services/data/v{version}/search/",
"query":"http://na1.salesforce.com/services/data/v{version}/query/",
"profile":"http://na1.salesforce.com/005x0000001S2b9"},
"active":true,
"user_type":"STANDARD",
"language":"en_US",
"locale":"en_US",
"utcOffset":-28800000,
"last_modified_date":"2010-06-28T20:54:09.000+0000"}
After making an invalid request, the following are possible responses from Salesforce:
124
Authentication, Security, and Identity in Mobile Apps Setting a Custom Login Server
125
Authentication, Security, and Identity in Mobile Apps Handling Refresh Token Revocation in Android Native Apps
Revoking Tokens
To revoke OAuth 2.0 tokens, use the revocation endpoint:
https://login.salesforce.com/services/oauth2/revoke
Construct a POST request that includes the following parameters using the application/x-www-form-urlencoded
format in the HTTP request entity-body. For example:
token=currenttoken
If an access token is included, we invalidate it and revoke the token. If a refresh token is included, we revoke it as well as any
associated access tokens.
The authorization server indicates successful processing of the request by returning an HTTP status code 200. For all error
conditions, a status code 400 is used along with one of the following error responses.
• unsupported_token_type—token type not supported
• invalid_token—the token was invalid
• Any subsequent REST API calls your app makes will fail.
• The system discards your user’s account information and cached offline data.
• The system forces the user to navigate away from your page.
• The user must log into Salesforce again to continue using your app.
These side effects provide a secure response to the administrator’s action, but they might or might not be suitable for your
application. In your code you can choose whether to accept the default behavior or implement your own response. In either
case, continue reading to determine whether you need to adapt your code.
126
Authentication, Security, and Identity in Mobile Apps Token Revocation: Passive Handling
If your app fails to satisfy at least one of these conditions, implement the following code changes.
1. (For legacy apps written before the Mobile SDK 1.5 release) In the ClientManager constructor, set the
revokedTokenShouldLogout parameter to true.
Note: This step is not necessary for apps that are new in Mobile SDK 1.5 or later.
registerReceiver(tokenRevocationReceiver, new
IntentFilter(ClientManager.ACCESS_TOKEN_REVOKE_INTENT));
unregisterReceiver(tokenRevocationReceiver);
@Override
public boolean shouldLogoutWhenTokenRevoked() {
return false;
}
127
Authentication, Security, and Identity in Mobile Apps Portal Authentication Using OAuth 2.0 and Force.com Sites
2. The ClientManager constructor provides a boolean parameter, revokedTokenShouldLogout. Set this parameter to
false. You can do this by calling shouldLogoutWhenTokenRevoked() on your SalesforceSDKManager subclass.
3. Implement your handler by extending TokenRevocationReceiver and overriding the onReceive() method.
4. Regardless of whether your activity subclasses SalesforceActivity, perform step 2 in Token Revocation: Passive
Handling on page 127.
1. Associate a Force.com site with your portal. The site generates a unique URL for your portal. See Associating a Portal
with Force.com Sites.
2. Create a custom login page on the Force.com site. See Managing Force.com Site Login and Registration Settings.
3. Use the unique URL that the site generates as the redirect domain for your users' login requests.
The OAuth2 service recognizes your custom host name and redirects the user to your Site login page if the user is not yet
authenticated.
For example, rather than redirecting to https://login.salesforce.com:
https://login.salesforce.com/services/oauth2/authorize?response_type=
code&client_id=<your_client_id>&redirect_uri=<your_redirect_uri>
https://mysite.secure.force.com/services/oauth2/authorize?response_type=
code&client_id=<your_client_id>&redirect_uri=<your_redirect_uri>
For more information and a demonstration video, see OAuth for Portal Users on the Force.com Developer Relations Blogs
page.
128
Chapter 9
Migrating from the Previous Release
In this chapter ... If you developed code with Salesforce Mobile SDK 1.5, follow these instructions
to update your app to version 2.0.
• Migrating Android Applications
• Migrating iOS Applications
129
Migrating from the Previous Release Migrating Android Applications
• Change your class that extends ForceApp or ForceAppWithSmartStore to extend Application instead. We’ll call
this class SampleApp in the remaining steps.
• Create a new class that implements KeyInterface. Name it KeyImpl (or another name of your choice.) Move the
getKey() implementation from SampleApp into KeyImpl.
• We’ve renamed ForceApp to SalesforceSDKManager and ForceAppWithSmartStore to
SalesforceSDKManagerWithSmartStore.
where <mainActivityClass> is the class to be launched when the login flow completes.
Note:
◊ If your app supplies its own login activity, you can pass it as an additional argument to the initNative()
method call.
◊ If your app uses SmartStore, call initNative() on SalesforceSDKManagerWithSmartStore instead
of SalesforceSDKManager.
• Remove overridden methods of ForceApp from SampleApp, such as getKey(), getMainActivityClass(), and
any other overridden methods.
• You’re no longer required to create a LoginOptions object. The Salesforce Mobile SDK now automatically reads these
options from an XML file, bootconfig.xml, which resides in the res/values folder of your project.
◊ Create a file called bootconfig.xml under the res/values folder of your project. Move your app's login options
configuration from code to bootconfig.xml. See res/values/bootconfig.xml in the SalesforceSDK project
or in one of the sample native apps for an example.
130
Migrating from the Previous Release Migrating Android Applications
• NativeMainActivity has been renamed to SalesforceActivity and moved to a new package named
com.salesforce.androidsdk.ui.sfnative.
◊ If any of your app's classes extend NativeMainActivity, replace all references to NativeMainActivity with
SalesforceActivity.
◊ Update the app's class imports to reflect this change.
• We’ve moved SmartStore to a new package named com.salesforce.androidsdk.smartstore. If your app uses
SmartStore project, update the app's class imports and other code references to reflect this change.
• Change your class that extends ForceApp or ForceAppWithSmartStore to extend Application instead. We’ll call
this class SampleApp in the remaining steps.
• Create a new class that implements KeyInterface. Name it KeyImpl (or any other name of your choice.) Move the
getKey() implementation from SampleApp into KeyImpl.
• We’ve renamed ForceApp to SalesforceSDKManager and ForceAppWithSmartStore to
SalesforceSDKManagerWithSmartStore.
Note:
◊ If your app supplies its own login activity, you can pass it as an additional argument to the initHybrid()
method call.
◊ If your app uses SmartStore, call initHybrid() on SalesforceSDKManagerWithSmartStore instead
of SalesforceSDKManager.
• Remove overridden methods of ForceApp from SampleApp, such as getKey(), getMainActivityClass(), and
any other overridden methods.
• You’re no longer required to create a LoginOptions object. The Salesforce Mobile SDK now automatically reads these
options from an XML file, bootconfig.xml, which resides in the res/values folder of your project.
131
Migrating from the Previous Release Migrating iOS Applications
◊ Create a file called bootconfig.xml under the res/values folder of your project. Move your app's login options
configuration from code to bootconfig.xml. See res/values/bootconfig.xml in the SalesforceSDK project
or in one of the sample native apps for an example.
• NativeMainActivity has been renamed to SalesforceActivity and moved to a new package named
com.salesforce.androidsdk.ui.sfnative.
◊ If any of your app's classes extend NativeMainActivity, replace all references to NativeMainActivity with
SalesforceActivity.
◊ Update the app's class imports to reflect this change.
• We’ve moved SmartStore to a new package named com.salesforce.androidsdk.smartstore. If your app uses the
SmartStore project, update the app's class imports and other code references to reflect this change.
• We’ve replaced bootconfig.js with bootconfig.json. Convert your existing bootconfig.js to the new
bootconfig.json format. See the hybrid sample apps for examples.
• The SalesforceSDK Cordova plugins—SFHybridApp.js, cordova.force.js, and
SalesforceOAuthPlugin.js—have been combined into a single file named filecordova.force.js.
• forcetk.js has now been renamed to forcetk.mobilesdk.js. Replace the existing copy of forcetk.js with the
latest version of forcetk.mobilesdk.js. Update all references to forcetk.js to the new name.
• The bootstrap.html file is no longer required and can safely be removed.
• We’ve moved SalesforceDroidGapActivity and SalesforceGapViewClient to a new package named
com.salesforce.androidsdk.ui.sfhybrid. If your app references these classes, update those references and related
class imports.
132
Migrating from the Previous Release Migrating iOS Applications
• Update your AppDelegate class. Make your AppDelegate.h and AppDelegate.m files conform to the new design
patterns. Here are some key points:
◊ In AppDelegate.h, AppDelegate should no longer inherit from SFNativeRestAppDelegate.
◊ In AppDelegate.m, AppDelegate now has primary responsibility for navigating the auth flow and root view controller
staging. It also handles boundary events when the user logs out or switches login hosts.
Note: The design patterns in the new AppDelegate are just suggestions. Mobile SDK no longer requires a
specific flow. Use an authentication flow (with the updated SFAuthenticationManager singleton) that
suits your needs, relative to your app startup and boundary use cases.)
◊ The only prerequisites for using authentication are the SFAccountManager configuration settings at the top of
[AppDelegate init]. Make sure that those settings match the values specified in your connected app. Also, make
sure that this configuration is set before the first call to [SFAuthenticationManager
loginWithCompletion:failure:].
133
Migrating from the Previous Release Migrating iOS Applications
Note: If you’re updating a Visualforce app, only the bootconfig.js change is required. Your hybrid app does
not use the other files.
• Update your AppDelegate—Make your AppDelegate.h and AppDelegate.m files conform to the new design patterns.
If you’ve never changed your AppDelegate class, you can simply copy the new template app’s AppDelegate.h and
AppDelegate.m files over the old ones. Here are some key points:
◊ In AppDelegate.h:
- AppDelegate no longer inherits SFContainerAppDelegate.
- There's a new viewController property on SFHybridViewController.
◊ In AppDelegate.m, AppDelegate now assumes primary responsibility for navigating the bootstrapping and
authentication flow. This responsibility includes handling boundary events when the user logs out or switches login
hosts.
134
Chapter 10
Reference
In this chapter ... Reference documentation is hosted on GitHub
135
Reference REST API Resources
iOS Architecture
At a high level, the current facilities that the native SDK provides to consumers are:
136
Reference Native iOS Objects
Note: SmartStore is not currently exposed to native template apps, but is included in the binary distribution.
The Salesforce native SDK is essentially one library, with dependencies on (and providing exposure to) the following additional
libraries:
◊ RestKit in turn depends on libxml2.dylib, which is part of the standard iOS development environment
Note: This wrapper is not currently exposed to native template apps, but is included in the binary distribution.
• SFRestAPI
• SFRestAPI (Blocks)
• SFRestRequest
SFRestAPI
SFRestAPI is the entry point for making REST requests, and is generally accessed as a singleton, via [SFRestAPI
sharedInstance].
You can easily create many standard canned queries from this object, such as:
objectId:contactId
fields:updatedFields];
SFRestAPI (Blocks)
This is a category extension of the SFRestAPI class that allows you to specify blocks as your callback mechanism. For example:
137
Reference Android Architecture
completeBlock:^(NSDictionary *d) {
NSLog(@"ID value for object: %@", [d objectForKey:@"id"]);
}];
SFRestRequest
In addition to the canned REST requests provided by SFRestAPI, you can also create your own:
SFRestAPI (QueryBuilder)
This category extension provides utility methods for creating SOQL and SOSL query strings. Examples:
NSString *soqlQuery =
[SFRestAPI SOQLQueryWithFields:[NSArray arrayWithObjects:@"Id", @"Name", @"Company",
@"Status", nil]
sObject:@"Lead"
where:nil
limit:10];
NSString *soslQuery =
[SFRestAPI SOSLSearchWithSearchTerm:@"all of these will be escaped:~{]"
objectScope:[NSDictionary dictionaryWithObject:@"WHERE isactive=true ORDER BY
lastname
asc limit 5"
forKey:@"User"]];
Other Objects
Though you won’t likely leverage these objects directly, their purpose in the SDK is worth noting.
• RKRequestDelegateWrapper—The intermediary between SFRestAPI and the RestKit libraries.
RKRequestDelegateWrapper wraps the functionality of RestKit communications, providing convenience methods
for determining the type of HTTP post, handling data transformations, and interpreting responses.
• SFSessionRefresher—Tightly-coupled with SFRestAPI, providing an abstraction around functionality for automatically
refreshing a session if any REST requests fail due to session expiration.
Android Architecture
The SalesforceSDK is provided as a library project. You need to reference the SalesforceSDK project from your application
project. See the Android developer documentation.
Java Code
Java sources are under /src.
Java Code
138
Reference Java Code
com.salesforce.androidsdk.app
Class Description
SalesforceSDKManager Abstract subclass of application; you must supply a concrete
subclass in your project.
UpgradeManager Helper class for upgrades
UUIDManager Helper class for UUID generation
com.salesforce.androidsdk.auth
Class Description
AccountWatcher Watcher responsible for cleanup when account is removed
from settings application
AuthenticatorService Service taking care of authentication
HttpAccess Generic HTTP access layer
LoginServerManager Manages login hosts
OAuth2 Helper class for common OAuth2 requests
com.salesforce.androidsdk.phonegap
Class Description
ForcePlugin Abstract super class for all Salesforce plugins
JavaScriptPluginVersion Helper class to encapsulate the version reported by the
JavaScript code
SalesforceOAuthPlugin PhoneGap plugin for Salesforce OAuth
SDKInfoPlugin PhoneGap plugin to get information about the SDK container
TestRunnerPlugin PhoneGap plugin to run javascript tests in container
139
Reference Java Code
com.salesforce.androidsdk.rest
Class Description
ClientManager Factory of RestClient, kicks off login flow if needed
RestClient Authenticated client to talk to a Force.com server
RestRequest Force.com REST request wrapper
RestResponse REST response wrapper
com.salesforce.androidsdk.security
Class Description
Encryptor Helper class for encryption/decryption/hash computations
PasscodeManager Inactivity timeout manager, kicks off passcode screen if needed
com.salesforce.androidsdk.smartstore.app
This package is part of the SmartStore library project.
Class Description
SalesforceSDKManagerWithSmartStore Super class for all force applications that use the SmartStore
(lives in SmartStore library project)
UpgradeManagerWithSmartStore Upgrade manager for applications that use the SmartStore
(lives in SmartStore library project)
com.salesforce.androidsdk.smartstore.phonegap
This package is part of the SmartStore library project.
Class Description
SmartStorePlugin PhoneGap plugin for SmartStore
StoreCursor Represents a query cursor
com.salesforce.androidsdk.smartstore.store
This package is part of the SmartStore library project.
Class Description
DBHelper Helper class to access the database underlying SmartStore
DBOpenHelper Helper class to manage regular database creation and version
management
IndexSpec Represents an index specification
QuerySpec Represents a query specification
SmartSqlHelper Helper class for parsing and running SmartSql
SmartStore Searchable/secure store for JSON documents
140
Reference Java Code
com.salesforce.androidsdk.ui
Class Description
CustomServerUrlEditor Custom dialog allowing user to pick a different login host
LoginActivity Login screen
SalesforceActivity Main activity of native application should extend or duplicate
the functionality of this class
OAuthWebviewHelper Helper class to manage a WebView instance that is going
through the OAuth login process
PasscodeActivity Passcode (PIN) screen
SalesforceDroidGapActivity Main activity for hybrid applications
SalesforceGapViewClient WebView client used in hybrid applications
SalesforceR Class that allows references to resources defined outside the SDK
ServerPickerActivity Choose login host screen
com.salesforce.androidsdk.ui.sfhybrid
Class Description
SalesforceDroidGapActivity Defines the main activity for a Cordova-based application
SalesforceGapViewClient Defines the web view client for a Cordova-based application
com.salesforce.androidsdk.ui.sfnative
Class Description
SalesforceActivity Main activity of native applications. All native activities are
encouraged to extend one of the classes in this package, or else
duplicate the functionality of one of these classes.
SalesforceListActivity Main activity of native applications, based on the Android
ListActivity class. All native activities are encouraged to
extend one of the classes in this package, or else duplicate the
functionality of one of these classes.
SalesforceExpandableListActivity Main activity of native applications, based on the Android
ExpandableListActivity class. All native activities are
encouraged to extend one of the classes in this package, or else
duplicate the functionality of one of these classes.
com.salesforce.androidsdk.util
Class Description
BaseActivityInstrumentationTestCase Super class for activty test classes
EventsListenerQueue Class to track activity events using a queue, allowing for tests
to wait for certain events to turn up
141
Reference Libraries
Class Description
EventsObservable Used to register and receive events generated by the SDK
(used primarily in tests)
EventsObserver Observer of SDK events
SalesforceSDKManagerInstrumentationTestCase Super class for tests of an application using the Salesforce
Mobile SDK
HybridInstrumentationTestCase Super class for tests of hybrid application
JSTestCase Super class to run tests written in JavaScript
JUnitReportTestRunner Test runner that runs tests using a time run cap
LogUtil Helper methods for logging
NativeInstrumentationTestCase Super class for tests of native application
TimeLimitedTestRunner Test runner that limits the lifetime of the test run
Libraries
Libraries are under /libs.
Resources
Resources are under /res.
drawable-hdpi
File Use
sf__edit_icon.png Server picker screen
sf__highlight_glare.png Login screen
sf__icon.png Application icon
142
Reference Resources
drawable-ldpi
File Use
sf__icon.png Application icon
drawable-mdpi
File Use
sf__edit_icon.png Server picker screen
sf__highlight_glare.png Login screen
sf__ic_refresh_sync_anim0.png Application icon
sf__icon.png Application icon
drawable
File Use
sf__header_bg.png Login screen
sf__progress_spinner.xml Login screen
sf__toolbar_background.xml Login screen
drawable-xlarge
File Use
sf__header_bg.png Login screen (tablet)
sf__header_drop_shadow.xml Login screen (tablet)
sf__header_left_border.xml Login screen (tablet)
sf__header_refresh.png Login screen (tablet)
sf__header_refresh_press.png Login screen (tablet)
sf__header_refresh_states.xml Login screen (tablet)
sf__header_right_border.xml Login screen (tablet)
sf__login_content_header.xml Login screen (tablet)
sf__nav_shadow.png Login screen (tablet)
sf__oauth_background.png Login screen (tablet)
sf__oauth_container_dropshadow.9.png Login screen (tablet)
sf__progress_spinner.xml Login screen (tablet)
sf__refresh_loader.png Login screen (tablet)
sf__toolbar_background.xml Login screen (tablet)
143
Reference Resources
drawable-xlarge-port
File Use
sf__oauth_background.png Login screen (tablet)
layout
File Use
sf__custom_server_url.xml Server picker screen
sf__login.xml Login screen
sf__passcode.xml Pin screen
sf__server_picker.xml Server picker screen (deprecated)
sf__server_picker_list.xml Server picker screen
layout-land
File Use
sf__passcode.xml PIN screen
layout-xlarge
File Use
sf__header_bottom.xml Header (tablet)
sf__header_separator.xml Header (tablet)
sf__login.xml Login screen (tablet)
sf__login_header.xml Login screen (tablet)
sf__passcode.xml PIN screen (tablet)
sf__server_picker.xml Server picker screen (tablet)
sf__server_picker_header.xml Server picker screen (tablet)
menu
File Use
sf__clear_custom_url.xml Add connection dialog
sf__login.xml Login menu (phone)
values
File Use
bootconfig.xml Connected app configuration settings
sf__colors.xml Colors
144
Reference Resources
File Use
sf__dimens.xml Dimensions
sf__strings.xml SDK strings
sf__style.xml Styles
strings.xml Other strings (app name)
values-xlarge
File Use
sf__colors.xml Colors (tablet)
sf__dimens.xml Dimensions (tablet)
styles.xml Styles (tablet)
xml
File Use
authenticator.xml Preferences for account used by application
config.xml Plugin configuration file for PhoneGap. Required for hybrid.
servers.xml Server configuration.
145
Index
Index
A Developer Edition 6
Developer.force.com 6
Account Editor sample 95 Developing HTML apps 59
AccountWatcher class 38 Developing HTML5 apps 60
Android Development 5
native classes 36 Development requirements, Android 28
Android architecture 138, 142 Development, Android 27, 32–33
Android development 27, 32–33 Development, hybrid 51
Android hybrid development 52
Android hybrid sample apps 52
Android project 30 E
Android requirements 28 encoding, Base64 39
Android sample app 50 Encryptor class 39
Android template app 47 Events
Android template app, deep dive 47 Refresh token revocation 126–127
Android, native development 33
Apex controller 57
AppDelegate class 14
F
Application flow, iOS 13 Files
application structure, Android 34 JavaScript 53
Architecture, Android 138, 142 Flow 119–120
Audience 2 Force.com 5
authentication ForcePlugin class 41
Force.com Sites
128
and portal authentication 128 G
portal 128 GitHub 7
portal authentication 128 Glossary 117
Authentication 116
Authentication flow 119
Authorization 118 H
HTML5 59–60
B HTML5 development 2, 5
Hybrid Android development 52
Backbone framework 64 Hybrid applications
Base64 encoding 39 JavaScript files 53
BLOBs 104, 113 Javascript library compatibility 54
Versioning 54
C Hybrid development 2, 5, 51
Hybrid iOS sample 52
caching, offline 68 hybrid sample apps 52
Callback URL 117
ClientManager class 40, 44
com.salesforce.androidsdk.rest package 44
I
Connected apps 116, 118 Identity URLs 121
Consumer key 117 installation, Mobile SDK 6
Container 51 Installing the SDK 9, 28
create_native script 33 interface
KeyInterface 38
D iOS
SFRestDelegate protocol 20
Database.com 5–6 using SFRestRequest methods 22
Delete soups 105–106, 110–111 view controllers 15
Describe global 136 iOS application, creating 10
146
Index
J O
JavaScript 60 OAuth
Javascript library compatiblity 54 custom login host 125
Javascript library version 57 OAuth2 116, 119
JavaScript, files 53 offline caching 68, 70
Offline storage 103–104
K
P
KeyInterface interface 38
Packages 6
L Parameters, scope 121
PasscodeManager class 38
List objects 136 passcodes, using 42
List resources 136 Password 136
localStorage 104, 113 PIN protection 118
login and passcodes, iOS 13 Prerequisites 5
LoginActivity class 40 project template, Android 47
Project, Android 30
M
Q
MainActivity class 48
Manifest, TemplateApp 49 Queries, Smart SQL 108
memory management, iOS apps 13 Query 136
Metadata 136 Querying a soup 105–106, 110–111
Migrating querySpec 105–106, 110–111
1.5 to 2.0 129
migration
Android applications 130
R
iOS applications 132 Reference documentation 135
Mobile container 51 refresh token 55
Mobile Container 9 Refresh token
Mobile development 1 Revocation 126–127
Mobile Development 9 Refresh token flow 120
Mobile policies 118 Refresh token revocation 126
Mobile SDK installation 9, 28 Refresh token revocation events 126–127
Mobile SDK Packages 6 registerSoup 105–106, 110–111
Mobile SDK Repository 7 Releases 6–7
Remote access 117
N Remote access application 117
resource handling, Android native apps 42
native Android classes 36 REST 136
147
Index
148