Skip to main content

Implement Secure Offline Storage with SmartStore

Learning Objectives

After completing this unit, you'll be able to:

  • Understand basic SmartStore terms and concepts.
  • Use SmartStore to issue SELECT queries.
  • Register, populate, and manage SmartStore data on your preferred target platform (iOS, Android, hybrid, React Native).
  • Use the SmartStore Inspector.

Using SmartStore for Offline Storage

Mobile devices can lose connection at any time, and environments such as hospitals and airplanes often prohibit connectivity. To handle these situations, it’s important that your mobile apps continue to function when they go offline. 

Mobile SDK provides SmartStore, a multithreaded, secure solution for offline storage on mobile devices. With SmartStore, your customers can continue working with data in a secure environment even when the device loses connectivity. When you couple SmartStore with Mobile Sync, you can easily keep local SmartStore data in sync with the Salesforce server when connectivity resumes.

SmartStore stores data as JSON documents in a simple, single-table database. You can retrieve SmartStore data either with Mobile SDK helper methods or with custom queries using SmartStore’s Smart SQL language.

Soups

SmartStore stores offline data in logical collections known as soups. A SmartStore soup represents a single table in the underlying SQLite database, or store, and typically maps to a standard or custom Salesforce object. Soups contain soup elements. Each element is a JSON object that mirrors a single database row. To streamline data access, you define indexes for each soup. You use these indexes to query the soup with either SmartStore helper methods or SmartStore’s Smart SQL query language. SmartStore indexes also make your life easier by supporting full-text search queries.

It’s helpful to think of soups as tables, and stores as databases. You can define as many soups as you like in an application. As self-contained data sets, soups don’t have predefined relationships to each other, but you can use Smart SQL joins to query across them. Also, in native apps you can write to multiple soups within a transaction.

Note

SmartStore data is volatile. In most cases, its lifespan is tied to the authenticated user and 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. When designing your app, consider the volatility of SmartStore data, especially if your organization sets a short lifetime for the refresh token.

Smart SQL

SmartStore supports the Smart SQL query language for free-form SELECT statements. Only SELECT statements are supported. Smart SQL combines all standard SQL SELECT grammar with special syntax for referencing soups and soup fields. This approach gives you maximum control and flexibility, including the ability to use joins.

Syntax

Syntax is identical to the standard SQL SELECT specification but with the following adaptations:

Usage Syntax
To specify a column {<soupName>:<path>}
To specify a table {<soupName>}
To refer to the entire soup entry JSON string {<soupName>:_soup}
To refer to the internal soup entry ID {<soupName>:_soupEntryId}
To refer to the last modified date {<soupName>:_soupLastModifiedDate}

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 {employees:firstName}, {employees:lastName} 
 from {employees} order by {employees:lastName}
select {departments:name} 
 from {departments} 
 order by {departments:deptCode}

Joins

Smart SQL also allows you to use joins. For example:

select {departments:name}, {employees:firstName} || ' ' || {employees:lastName}  
 from {employees}, {departments}  
 where {departments:deptCode} = {employees:deptCode}  
 order by {departments:name}, {employees:lastName}

You can even do self-joins:

select mgr.{employees:lastName}, e.{employees:lastName}  
 from {employees} as mgr, {employees} as e  
 where mgr.{employees:employeeId} = e.{employees:managerId}
Note

Doing a join on a JSON1 index requires a slightly extended syntax. For example, instead of

select {soup1:path1} from {soup1}, {soup2}

use

select {soup1}.{soup1:path1} from {soup1}, {soup2}

Aggregate Functions

Smart SQL supports 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}

Registering a Soup

Before using a soup, you register it. If the soup doesn’t exist, registering creates it. If it does exist, registering gives you access to it. To register soups, you create a JSON configuration file and load it at runtime. A single configuration file can define all your app's soups. Mobile SDK supports configuration files in native and hybrid apps.

Configuration files register soups only in the default user store or the default global store. To use other named stores or external stores, you must register soups with SmartStore APIs. Mobile SDK provides methods for defining a soup’s name and indexes, and then registering it.

When you register a soup, you create an empty named structure in memory that’s waiting for data. You typically initialize the soup with data from a Salesforce organization. To obtain the Salesforce data, you use Mobile SDK’s standard REST request mechanism. When a successful REST response arrives, you extract the data from the response object and then upsert it into your soup.

During soup creation, errors can happen for various 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

Soup Structure

To define a soup, you provide a soup name and a list of one or more index specifications. Indexes are based on soup fields. You're not required to provide an index spec for every field you store in the soup. For example, if you're using the soup as a simple key-value store, use a single index specification of type string. Once the soups are created, SmartStore uses the indexes to track any insert, update, or delete operations.

SmartStore supports the following index data types.

  • string
  • integer
  • floating
  • full_text
  • json1

Defining Indexes

In native and hybrid apps, you can use configuration files to register soups and define their indexes. In React Native apps, you use JavaScript code. A few rules apply in every case.

  • Index paths are case-sensitive and can include compound paths, such as Owner.Name.
  • Index entries that are missing any fields described in an index spec array are not tracked in that index.
  • The type of the index applies only to the index. When you query an indexed field (for example, “select {soup:path} from {soup}”), the query returns data in the type that you specified in the index specification.
  • Index columns can contain null fields.
  • As of Mobile SDK 9.1, index paths are no longer required for fields referenced in SELECT or WHERE clauses.
  • You can specify index paths that point to internal (non-leaf) nodes. You can use internal paths with like and match (full-text) queries. Use the stringtype when you define internal node paths.For example, consider this element in a soup named “spies”:
    {  
       "first_name":"James",
       "last_name":"Bond",
       "address":{  
          "street_number":10,
          "street_name":"downing",
          "city":"london"
       }
     }
    In this case, “address” is an internal node because it has children. Through the index on the path “address”, you can use a like or matchquery to find the “city” value—“london”—in “address”. For example:
    SELECT {spies:first_name, spies:last_name} FROM spies WHERE {spies:address} LIKE 'london'

Configuration File Format

Here's a theoretical example of a JSON configuration file that defines two soups—soup1 and soup2—and demonstrates the full gamut of index data types.

{  "soups": [
    {
      "soupName": "soup1",
      "indexes": [
        { "path": "stringField1", "type": "string"},
        { "path": "integerField1", "type": "integer"},
        { "path": "floatingField1", "type": "floating"},
        { "path": "json1Field1", "type": "json1"},
        { "path": "ftsField1", "type": "full_text"}
      ]
    },
    {
      "soupName": "soup2",
      "indexes": [
        { "path": "stringField2", "type": "string"},
        { "path": "integerField2", "type": "integer"},
        { "path": "floatingField2", "type": "floating"},
        { "path": "json1Field2", "type": "json1"},
        { "path": "ftsField2", "type": "full_text"}
      ]
    }
  ]
 }

For more information on registering your soups with code instead of configuration files, see the Salesforce Mobile SDK Development Guide.

Note

If your code and your configuration file both register a soup with the same name, Mobile SDK ignores the definition in the configuration file.

The following configuration file registers a single soup based on account records. This soup indexes the name, ID, and owner (or parent) ID fields. Soup names are not required to match the name of a source Salesforce object, but an obvious allusion is usually a good choice.

{  "soups": [
    {
      "soupName": "account",
      "indexes": [
        { "path": "Name", "type": "string"},
        { "path": "Id", "type": "string"},
        { "path": "OwnerId", "type": "string"}
      ]
    }
  ]
 }

Inserting or Updating Soup Entries

To insert or update soup entries—letting SmartStore determine which action is appropriate—you use an upsert method. For example, a hybrid app can use one of these JavaScript versions:

navigator.smartStore.upsertSoupEntries(isGlobalStore, soupName, 
    entries[], successCallback, errorCallback)
navigator.smartStore.upsertSoupEntries(storeConfig, soupName, 
    entries[], successCallback, errorCallback)

You provide the soup's name, an array of entries formatted as JSON strings, and optional success and error callback functions. The only difference between the two methods is the first parameter. If your soup lives in a named user or global store, you use the storeConfig parameter to provide the type of store and its name. For example:

{isGlobalStore:true, name:"AcmeSales"}

If you're using the default store global or user store, you can simply pass true to indicate a global store. Otherwise, you can omit the argument. This parameter is optional and defaults to false. It is not present in native methods, which read the same information from related objects.

Querying and Managing SmartStore

Mobile SDK for iOS native apps provides factory methods that build "query spec" objects based on your input. The factory methods build queries based on the following WHERE operators:

  • = ("exact" operator)
  • LIKE
  • MATCH (full text search extension)
  • BETWEEN, <=, >= (range queries)
  • ALL

These query objects handle simple, straightforward queries and spare you the trouble of writing them yourself. To turn up the nuance in more complex situations, you can write your own Smart SQL query and pass it to the Smart SQL factory method. After you obtain a query object, you pass it to an execution method to retrieve the specified soup data.

Managing a Soup

Sometimes you want to clean up unused data, or improve the performance of a soup by changing its indexes. For such tasks, SmartStore provides a suite of methods that perform diagnostic, maintenance, and management tasks. Use these methods to get information about a soup or store, edit a soup’s structure, or delete soups and stores.

More specifically, with these methods you can:

  • Get the size of the underlying database
  • Get a list of all stores (user or global)
  • Check whether a soup with the given name exists
  • Retrieve a soup’s spec and its index specs
  • Alter a soup’s configuration
  • Reindex a soup
  • Clear all records from a soup
  • Remove a soup from the store
  • Remove a store
  • Remove all stores (user or global)

These methods are available on all platforms for all app types.

Using the SmartStore Inspector

During testing, it’s helpful to see if your code is handling SmartStore data as you intended. The SmartStore Inspector provides a UI tool for that purpose. With it, you can:

  • Examine soup metadata, such as soup names and index specs for any soup
  • Clear a soup’s contents
  • Perform Smart SQL queries

For easiest access, launch the SmartStore Inspector through the Dev Support dialog box. 

How you access the Dev Support menu depends on your development environment. To launch the dialog box, use one of the following options.

Android

Do one of the following:

  • When your app is running in the Android emulator, use the Command+m (Mac) or Ctrl+m (Windows) keyboard shortcut.
  • In a system command shell, run: adb shell input keyevent 82

iOS

  • On a physical device, use the shake gesture to bring up the Dev Support menu, and then choose SmartStore Inspector.
  • In the iOS simulator, select the Hardware > Shake Gesture menu item, or use the ^+Command+z keyboard shortcut.

Using SmartStore in Native iOS Apps

Adding the SmartStore module to new native iOS apps requires no extra effort. Any native forceios app you create automatically includes the SmartStore library.

Registering Soups with a Configuration File

For iOS native apps, Mobile SDK looks for configuration files under / (top level) in the Resources bundle.

  1. Add configuration files to your project.
    1. In the Xcode Project navigator, select the project node.
    2. In the Editor window, select Build Phases.
    3. Expand Copy Bundle Resources.
    4. Click + (”Add items”).
    5. Select your soup configuration file. If your file is not already in an Xcode project folder:
      1. To select your file in Finder, click Add Other....
      2. When prompted to create groups, click Finish.
  2. Add a single line of code for each file you provide.
    • To load a userstore.jsonfile, use one of the following:
      SmartStoreSDKManager.shared().setupUserStoreFromDefaultConfig()
    • To load a globalstore.jsonfile, use one of the following:
      SalesforceManager.shared().setupGlobalStoreFromDefaultConfig()

Populating a Soup

When you register a soup, you create an empty named structure in memory that’s waiting for data. To populate the soup with data from Salesforce, use the standard REST request mechanism to obtain the data. When a successful REST response arrives, extract the data from the response object and then upsert it into your soup. For coding details, see the example at the end of this iOS section.

Querying Soup Data

In iOS, you create query spec objects by calling class methods on the SFQuerySpec class. For example, in Objective-C, the newSmartQuerySpec:withPageSize: method returns an SFQuerySpec object that encapsulates a given Smart SQL query string:

var querySpec = store.buildSmartQuerySpec(
    smartSql: "select {account:Name} from {account}",
    pageSize: 10)

The page size parameter determines how many records are sent in each page of results. These methods allow greater flexibility than other query factory functions because you provide your own Smart SQL SELECT statement. For example, the following code issues a query that calls the SQL COUNT function. Since COUNT returns a single value, the only possible page size is one.

The following code issues a query that calls the SQL COUNT function. Since COUNT returns a single value, the only possible page size is one.

To run a query, pass your SFQuerySpec object to the query() method on the SFSmartStore object.

var querySpec = store.buildSmartQuerySpec(
    smartSql: "select count(*) from {employees}",
    pageSize: 1)

Managing a Soup

To use Objective-C soup management APIs in a native iOS app, import SmartStore/SFSmartStore.h. You call soup management methods on a SFSmartStore shared instance. Obtain the shared instance by using one of the following SFSmartStore class methods.

To obtain the SmartStore instance for the current user:

var store = SmartStore.shared(withName: storeName)

To obtain the SmartStore instance for a specified user:

var store = SmartStore.shared(withName: storeName, forUserAccount: user)

For example, to call the removeSoup: management method:

self.store = [SFSmartStore sharedStoreWithName:kDefaultSmartStoreName];
 if ([self.store soupExists:@"Accounts"]) {
    [self.store removeSoup:@"Accounts"];
 }

Example

In this example, you create a Swift app. The native Swift template for forceios already includes a SmartStore implementation, so we'll just review its setup. 

  1. Using forceios, create a native Swift project similar to the following example:
    $ forceios create Enter your application type (native_swift or native, leave empty for native_swift): <Press RETURN>
    Enter your application name: <Enter any name you like>
    Enter your package name: com.myapps.ios
    Enter your organization name (Acme, Inc.): MyApps.com
    Enter output directory for your app (leave empty for the current directory): <Press RETURN or enter a directory name>
  2. Open your app's .xcworkspace file in Xcode.
  3. In the project folder, expand Supporting Files.
  4. Open the userstore.json file and inspect the SmartStore configuration.
  5. To add the configuration file to the compiled app's bundle, the template references these files in Project Settings.
    1. In the Xcode Project navigator, select the project's top node.
    2. In the Editor window, select Build Phases.
    3. Expand Copy Bundle Resources.
    4. Notice userstore.json is included in the list.
  6. In your project’s source code folder, select Classes/SceneDelegate.swift.
  7. In the scene(_:willConnectTo:options:) method, find the call to setupRootViewController().
  8. To see where the SmartStore configuration files are loaded, control + click setupRootViewController() and select Jump to Definition.

To view the app's SmartStore configuration at runtime: 

  1. Launch the app.
  2. After you've successfully logged in and authorized the app, bring up the Dev Tools menu.
    1. Type control + command + z if you’re using the iOS emulator, or shake your iOS device.
    2. Click Inspect SmartStore.
    3. To list your soups and their quantities of records, click Soups.

Using SmartStore in Native Android Apps

By default, all forcedroid native apps include SmartStore and Mobile Sync libraries. However, older Android apps may require a few custom setup steps.

  1. In your Android native project, open MainApplication.java.
  2. Add the following import statement if it’s not already present:
    import com.salesforce.androidsdk.mobilesync.app.MobileSyncSDKManager;
  3. Find the line that calls initNative(). For example:
    SalesforceSDKManager.initNative(getApplicationContext(), new NativeKeyImpl(), MainActivity.class);
  4. If initNative() is called on SalesforceSDKManager, change SalesforceSDKManager to MobileSyncSDKManager:
    MobileSyncSDKManager.initNative(getApplicationContext(), new NativeKeyImpl(), MainActivity.class);

Registering Soups with a Configuration File

  1. Place your soup configuration files in the /res/raw/ project folder.
  2. Add a single line of code for each file you provide.
    • To load a userstore.jsonfile, use
      SmartStoreSDKManager.getInstance().setupUserStoreFromDefaultConfig();
    • To load a globalstore.json file, use
      SmartStoreSDKManager.getInstance().setupGlobalStoreFromDefaultConfig();

Populating a Soup

When you register a soup, you create an empty named structure in memory that’s waiting for data. To populate the soup with data from Salesforce, use the standard REST request mechanism to obtain the data. When a successful REST response arrives, extract the data from the response object and then upsert it into your soup:

public void populateAccounts() throws UnsupportedEncodingException {
    final RestRequest restRequest =
        RestRequest.getRequestForQuery(
            ApiVersionStrings.getVersionNumber(SalesforceSDKManager.getInstance().getAppContext()), 
            "SELECT Name, Id, OwnerId FROM Account");
    client.sendAsync(restRequest, new RestClient.AsyncRequestCallback() {
        @Override
        public void onSuccess(RestRequest request, RestResponse result) {
            result.consumeQuietly(); // always call before switching to main thread (unlike here)
            try {
                JSONArray records = result.asJSONObject().getJSONArray("records");
                insertAccounts(records);
            } catch (Exception e) {
                onError(e);
            } finally {
                Log.println(Log.INFO, "REST Success!", "\nSmartStore insertion successful");
            }
        }
        @Override
        public void onError(Exception e)
        {
            Log.e(TAG, e.getLocalizedMessage());
        }
    });
 }
/**
 * Inserts accounts into the accounts soup.
 *
 * @param accounts Accounts.
 */
 public void insertAccounts(JSONArray accounts)
 {
    try {
        if (accounts != null) {
            for (int i = 0; i < accounts.length(); i++) {
                if (accounts.get(i) != null) {
                    try {
                        smartStore.upsert("Accounts", accounts.getJSONObject(i));
                    }
                    catch (JSONException exc) {
                        Log.e(TAG, "Error occurred while attempting to insert account. "
                                +  "Please verify validity of JSON data set.");
                    }
                }
            }
        }
    }
    catch (JSONException e) {
        Log.e(TAG, "Error occurred while attempting to insert accounts. "
                + "Please verify validity of JSON data set.");
    }
 }

Querying Soup Data with Smart SQL

In Android, you create query spec objects by calling static factory methods on the QuerySpec class. For example, the buildSmartQuerySpec method creates a Smart SQL object that encapsulates a given query string:

public static QuerySpec buildSmartQuerySpec(String smartSql, int pageSize)

To execute the query, you pass the returned QuerySpec object to the SmartStore.query() method. This function allows greater flexibility than other query factory functions because you provide your own Smart SQL SELECT statement. The pageSize parameter determines how many records are sent in each page of results.

To run a query through a QuerySpec object, pass it to the query() method on the SmartStore object. The following code issues a query that calls the SQL COUNT function. Since COUNT returns a single value, the only possible page size is one.

try {
    JSONArray result =
        store.query(QuerySpec.buildSmartQuerySpec(
            "select count(*) from {Accounts}", 1), 0);
    // result should be [[ n ]] if there are n employees
    Log.println(Log.INFO, "REST Success!", "\nFound " + 
        result.getString(0) + " accounts.");
 } catch (JSONException e) {
    Log.e(TAG, "Error occurred while counting the number of account records. "
        +  "Please verify validity of JSON data set.");
 }

Managing a Soup

To use soup management APIs in a native Android app, you call methods on the shared SmartStore instance:

SmartStore smartStore = 
    SmartStoreSDKManager.getInstance().getSmartStore();
 smartStore.clearSoup("user1Soup");

You can easily add SmartStore support to a forcedroid native app. Let's reconfigure the JSON import file to create two soups, one for each sObject query. We can then populate the soups at the same time that we populate the list view.

  1. Open your project directory in Android Studio.
  2. In the app/res folder, create a folder named “raw”.
  3. Right-click app/res/raw and select New > File. Name the file userstore.json.
  4. Add the following text to the new file:
    { "soups": [
        {
        "soupName": "Account",
        "indexes": [
            { "path": "Name", "type": "string"},
            { "path": "Id", "type": "string"},
            { "path": "OwnerId", "type": "string"},
            ]
        },
        {
        "soupName": "Contact",
        "indexes": [ 
            { "path": "Name", "type": "string"},
            { "path": "Id", "type": "string"},
            { "path": "OwnerId", "type": "string"},
            ]
        }
     ]}
  5. Open MainActivity.javaand import these files:
    import com.salesforce.androidsdk.smartstore.app.SmartStoreSDKManager;
     import com.salesforce.androidsdk.smartstore.store.IndexSpec;
     import com.salesforce.androidsdk.smartstore.store.QuerySpec;
     import com.salesforce.androidsdk.smartstore.store.SmartStore;
     import com.salesforce.androidsdk.smartstore.ui.SmartStoreInspectorActivity;
  6. At the top of the MainActivityclass, declare a private variable to point to the SmartStore shared instance, and another to track which sObject we're handling:
    private SmartStore smartStore;private String objectType;
  7. In the onCreate(Bundle savedInstanceState)method, import the soup definitions from your userstore.jsonfile:
    smartStore = SmartStoreSDKManager.getInstance().getSmartStore();
     if (!smartStore.hasSoup("Account") && !smartStore.hasSoup("Contact")) {
        SmartStoreSDKManager.getInstance().setupUserStoreFromDefaultConfig();
     } else {
        // Delete existing records in preparation for new server data
        smartStore.clearSoup("Account");
        smartStore.clearSoup("Contact");
     }
  8. In the onFetchContactsClick(View v)method, clear the Contact soup to avoid creating duplicate records:
    smartStore.clearSoup("Contact");
    objectType = "Contact";
  9. In the onFetchAccountsClick(View v)method, clear the Account soup to avoid creating duplicate records:
    smartStore.clearSoup("Account");
    objectType = "Account";
  10. In the client.sendAsync() method, call upsert() in the for loop that inserts the JSON response into the listAdapterobject:
    for (int i = 0; i < records.length(); i++) {
         listAdapter.add(records.getJSONObject(i).getString("Name"));
        try {
             smartStore.upsert((objectType, records.getJSONObject(i));
         } catch (Exception e) {
             onError(e);
         }
    }
  11. Launch the app, then check your work using the Dev Tools menu.
    • To bring up the menu, type Command + m (Mac) or Ctrl + m (Windows).
    • Click Inspect SmartStore.
    • To see a list of your soups and number of records in each, click Soups.
  12. Note: If you get "Query: No soups found", chances are you have an error in your userstore.json file.

You’ve now created and populated two SmartStore soups, but at this point they don’t serve a useful purpose. In the real world, you'd create an editing interface for the Account and Contact lists, and then upsert the customer’s changes to SmartStore. When the customer’s device regained connectivity, you could then merge changes to the server with Mobile Sync.

Using SmartStore in Hybrid Apps

Registering Soups with a Configuration File

In hybrid apps, Mobile SDK automatically loads SmartStore configuration files. You’re responsible for putting configuration files in the required location, as follows:

  1. Copy the configuration file to your hybrid project’s top-level www/ directory (for example, MyProject/www/).
  2. At a Terminal window or Windows command prompt, cd to your project directory (for example, MyProject/).
  3. Run: cordova prepare

Populating a Soup

To obtain Salesforce records, hybrid apps use the standard force.query() function from the JavaScript library. You use the success callback to upsert the data from the record set into the soup.

force.query("SELECT Name,Id FROM Contact", 
    onSuccessSfdcContacts, onErrorSfdc); 
var sfSmartstore = function() {
    return cordova.require("com.salesforce.plugin.smartstore");};
function onSuccessSfdcContacts(response) {
    logToConsole()("onSuccessSfdcContacts: received " + 
        response.totalSize + “ contacts");
    var entries = [];
    response.records.forEach(function(contact, i) {
           entries.push(contact);
    });
    if (entries.length > 0) {
        sfSmartstore().upsertSoupEntries(CONTACTS_SOUP_NAME,
            entries,
            function(items) {
                var statusTxt = "upserted: " + items.length + 
                    " contacts";
                logToConsole()(statusTxt);
            }, 
         onErrorUpsert);
    }
 }
function onErrorSfdc(param) {
    logToConsole()("onErrorSfdc: " + param);
 }
function onErrorUpsert(param) {
    logToConsole()("onErrorUpsert: " + param);
 }

Querying Soup Data with Smart SQL

In hybrid apps, you create query spec objects by calling functions on the com.salesforce.plugin.smartstore plugin's SmartStore object. For example, the buildSmartQuerySpec() function executes a Smart SQL query:

smartstore.buildSmartQuerySpec(smartSql, [pageSize])

where smartSql is the query to be executed. This function allows greater flexibility than other query factory functions because you provide your own SELECT statement. pageSize is optional and defaults to 10.

The following code issues a query that calls the Smart SQL COUNT function on a soup named “employees”.

var querySpec = 
    navigator.smartstore.buildSmartQuerySpec(
        "select count(*) from {employees}", 1);
navigator.smartstore.runSmartQuery(querySpec, function(cursor) { 
    // result should be [[ n ]] if there are n employees
 });

Managing a Soup

Each soup management function in JavaScript takes two callback functions: a success callback that returns the requested data, and an error callback. Success callbacks vary according to the soup management functions that use them. Error callbacks take a single argument, which contains an error description string. For example, you can define an error callback function as follows:

function(e) { alert(“ERROR: “ + e);}

To call a soup management function in JavaScript, first invoke the Cordova plug-in to initialize the SmartStore object. You then use the SmartStore object to call the soup management function. The following example defines named callback functions discretely, but you can also define them inline and anonymously.

var sfSmartstore = function() {
    return cordova.require("com.salesforce.plugin.smartstore");};
function onSuccessRemoveSoup(param) {
    logToConsole()("onSuccessRemoveSoup: " + param);
    $("#div_soup_status_line").html("Soup removed: " 
        + SAMPLE_SOUP_NAME);
 }
function onErrorRemoveSoup(param) {
    logToConsole()("onErrorRemoveSoup: " + param);
    $("#div_soup_status_line").html("removeSoup ERROR");
 }
sfSmartstore().removeSoup(SAMPLE_SOUP_NAME,
     onSuccessRemoveSoup, 
     onErrorRemoveSoup);

Using SmartStore in React Native Apps

React Native apps have much in common with hybrid apps. Usually, SmartStore functions for the two platforms share identical signatures. However, several significant differences apply.

  • React Native apps use ES2015 JavaScript code. While you can use the same JavaScript syntax as in hybrid apps, you can also take advantage of new streamlined coding conventions.
  • On top of JavaScript, React Native apps also support TypeScript for static typing. You can choose how much or how little to use, or whether to use TypeScript at all.
  • In React Native, SmartStore doesn’t use backbone.js.
  • You can’t use Cordova hybrid libraries or plug-ins in React Native. Instead, you import React Native SmartStore modules.

To use the SmartStore API, you import the smartstore module. To use the Salesforce API—most importantly for making queries to retrieve Salesforce records—you import the net module. You can import both modules in a single statement:

import {net, smartstore} from 'react-native-force';

Registering a Soup

For React Native, you use JavaScript code instead of configuration files to set up SmartStore soups. For example:

smartstore.registerSoup(false,
    "contacts", 
    [ {path:"Id", type:"string"}, 
    {path:"FirstName", type:"full_text"}, 
    {path:"LastName", type:"full_text"},    
    {path:"__local__", type:"string"} ],
    () => syncDown()
 );

Populating a Soup

To fill your soup with Salesforce data, begin by querying for records using the standard Salesforce API. The net module provides a set of wrapper functions that simplify the network calls. You pass a query string and success and error callbacks. In the success callback, you use the smartstore module to upsert records from the query response into your soup. (If this strategy sounds familiar, you must have read the hybrid section!)

net.query("SELECT Name,Id FROM Contact", 
        onSuccessSfdcContacts, onErrorSfdc);

Here’s a success callback example.

function onSuccessSfdcContacts(response) {
    logToConsole()("onSuccessSfdcContacts: received " + 
        response.totalSize + “ contacts");
    var entries = [];
    $.each(response.records, function(i, contact) {
           entries.push(contact);
           logToConsole()("name: " + contact.Name);
    });
    if (entries.length > 0) {
        smartstore().upsertSoupEntries(CONTACTS_SOUP_NAME,
            entries,
            function(items) {
                var statusTxt = "upserted: " + items.length + 
                    " contacts";
                logToConsole()(statusTxt);
            }, 
            onErrorUpsert);
    }
 }

Querying Soup Data with Smart SQL

In React Native, you create query spec objects by calling functions on the smartstore module. For example, the buildSmartQuerySpec() function constructs a Smart SQL query object:

buildSmartQuerySpec(smartSql, [pageSize])

In this function, smartSql is the query to be executed. This function allows greater flexibility than other query factory functions because you provide your own SELECT statement. pageSize is optional and defaults to 10.

Once you’ve built the smart query object, you pass it to the runSmartQuery() function, providing a success callback to handle the response. The following code builds and runs a query that calls the SQL COUNT function.

var querySpec = 
    smartstore.buildSmartQuerySpec(
        "select count(*) from {employees}", 1);
// Note that the first parameter--a storeConfig object, or a Boolean indicating whether to use 
// the default global store or the default user store--is required in React Native apps
// but is optional in hybrid apps
smartstore.runSmartQuery(false, querySpec, function(cursor) { 
    // result should be [[ n ]] if there are n employees
 });

Using SmartStore Management Functions

Soup management functions follow the same pattern as other React Native functions. You define success and error callbacks, and you call the function on the smartstore module. The number of parameters passed to the success callback can vary depending on the function. Error callbacks always take only an error description string argument.

Resources

Share your Trailhead feedback over on Salesforce Help.

We'd love to hear about your experience with Trailhead - you can now access the new feedback form anytime from the Salesforce Help site.

Learn More Continue to Share Feedback