Storing Data Offline with Salesforce Mobile SDK SmartStore
Say you’re writing a “hybrid” mobile app for iOS and Android using PhoneGap (“Callback” or “Cordova”) and you want to store data locally. Your choices are kind of limited. You could, for instance, use WebSQL, which has been implemented in WebKit for a while now and is available on both iOS and Android devices. If you clicked that link, though, you’ll probably notice the big scary warning that WebSQL has been deprecated…
It works right now, but maybe it’s not the best idea to use it if you don’t want to have to strip it out someday down the road. You also have to deal with all the data size limitations and requests to the user to allow the app to increase the database size.
So, what’s up next from the W3C if the WebSQL spec isn’t being maintained? The new spec is IndexedDB, which is a “database of records holding simple values and hierarchical objects”. Perfect! We’ll just check caniuse.com to make sure it’s supported on iOS and Android:
So… okay. It looks like it’s not supported yet on either. Does PhoneGap provide anything? In fact, it does, the Storage class. Which is based on WebSQL… I think you can see where I’m going here.
What do you do? Well, the Salesforce Mobile SDK for iOS and Android has implemented an ORM layer called SmartStore abstraction to SQLite, which uses FMDB on iOS and SQLCipher on Android. It’s easy to use and allows you to store data in JSON format locally and specify indexes for searching and sorting. Also, it uses native implementations of SQLite which are accessed via PhoneGap plugins so you don’t have any issues with HTML5 storage limitations or the W3C changing their mind about what web database technology everyone should standardize on.
To demonstrate SmartStore, I’ve put together a simple demo app, and posted it to Github:
The Sample App
Here it is running on the iPad simulator. One thing to know about SmartStore is that each database Table is called a Soup. It’s an Apple Newton thing. The Newton’s file system was based on a “Soup” store model. So here, I’ve asked the app to create a new Soup named “EggDrop” specified that I want to use just the “id” field as an index, and entered a Salesforce REST API query to be executed using the handy ForceTK library.
To start off, download the code from GitHub and open up the XCode project. If you want to start from scratch, you’ll want to install the Salesforce Mobile SDK for iOS and selected “Hybrid Force.com App” from X-Code’s New Project wizard.
Going from top to bottom:
Soup Name: A “Soup” named this will be created.
Indexes: I’ll show how this gets represented in the data model in a bit, but you can specify JSON fields for indexing and specify a type of either “string” or “integer”.
REST Query: Basically any query to the Salesforce REST API. Here we’re just defaulting to pulling down my Chatter feed items. I have 3 in my feed in a dev org that we’ll pull down. Buttons
Query SFDC: Run the REST Query, save the results to the specified Soup, and log the results to the console (on the page). Query SmartStore: Query the specified Soup and log the results to the console. They should be the same results as the remote Salesforce Query.
Reset: Delete all the Soups that we’ve created during this session.
Logout: Log out of your Salesforce org.
SmartStore Soup Functions
To see how all this works, the interesting bits are in the inline.js file.
To register a Soup, we call registerSoup:
To add some records to that Soup, we call upsertSoupEntries:
navigator.smartstore.upsertSoupEntries(soupName,records, success, error);
To query the Soup, first we have to define a querySpec (buildAllQuerySpec just pulls all records rather than searching for specific records):
var querySpec = navigator.smartstore.buildAllQuerySpec(“id”, null, 20);
This querySpec specifies “id’ as an index, null (allow default) for the sort order, and 20 for the page size.
We then use that querySpec to get records back from the Soup:
Additionally, if we want, we can delete Soups with removeSoup:
Running the App
When you run the app for the first time, you’ll be presented with an oAuth 2.0 User-Agent login screen for Salesforce. If you don’t already have a Developer Org, get one at http://developer.force.com/. It’s free.
Once you log in, hit Query SFDC, and you see the Soup get registered, JSON data gets downloaded from the REST API and added to the SOUP. Take a look at the JSON data, and then hit the “Query SmartStore” button. You’ll see it load that same data from the SmartStore Soup and display it in the console log.
Let’s take a look at the actual SQLite database file and see how things are stored behind the scenes. You’ll see we have 3 tables in the database: soupnames, soupindexmap, and TABLE1 (sqlitemaster and sqlitesequence are used internally by SQLite):
soup_names keeps a list of the Soups I’ve created
soup_index_map keeps track of the indexes for each Soup.
TABLE1 is a generically named table that contains my JSON data and Index values. The TABLE1_0 field here is the “id” index we specified when we created the Soup. If I had created more indexes, like a “name” index, another column would be shown here.
The Soup field contains the full JSON response for that record so you can reuse whatever code you wrote to deal with the JSON record from the REST API once you’ve pulled it from the Soup.
So that’s SmartStore. It’s a great way to put together an app for Salesforce, Database.com, or Chatter that stores JSON data on the device for offline access.