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.
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}
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
andmatch
(full-text) queries. Use thestring
type 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 alike
ormatch
query 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.
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.
- Add configuration files to your project.
- In the Xcode Project navigator, select the project node.
- In the Editor window, select Build Phases.
- Expand Copy Bundle Resources.
- Click + (”Add items”).
- Select your soup configuration file. If your file is not already in an Xcode project folder:
- To select your file in Finder, click Add Other....
- When prompted to create groups, click Finish.
- Add a single line of code for each file you provide.
- To load a
userstore.json
file, use one of the following:SmartStoreSDKManager.shared().setupUserStoreFromDefaultConfig()
- To load a
globalstore.json
file, use one of the following:SalesforceManager.shared().setupGlobalStoreFromDefaultConfig()
- To load a
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.
- 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>
- Open your app's
.xcworkspace
file in Xcode. - In the project folder, expand
Supporting Files
. - Open the
userstore.json
file and inspect the SmartStore configuration. - To add the configuration file to the compiled app's bundle, the template references these files in Project Settings.
- In the Xcode Project navigator, select the project's top node.
- In the Editor window, select Build Phases.
- Expand Copy Bundle Resources.
- Notice userstore.json is included in the list.
- In your project’s source code folder, select
Classes/SceneDelegate.swift
. - In the
scene(_:willConnectTo:options:)
method, find the call tosetupRootViewController()
. - To see where the SmartStore configuration files are loaded, control + click
setupRootViewController()
and selectJump to Definition
.
To view the app's SmartStore configuration at runtime:
- Launch the app.
- After you've successfully logged in and authorized the app, bring up the Dev Tools menu.
- Type control + command + z if you’re using the iOS emulator, or shake your iOS device.
- Click Inspect SmartStore.
- 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.
- In your Android native project, open MainApplication.java.
- Add the following import statement if it’s not already present:
import com.salesforce.androidsdk.mobilesync.app.MobileSyncSDKManager;
- Find the line that calls
initNative()
. For example:SalesforceSDKManager.initNative(getApplicationContext(), new NativeKeyImpl(), MainActivity.class);
- If
initNative()
is called onSalesforceSDKManager
, changeSalesforceSDKManager
toMobileSyncSDKManager
:MobileSyncSDKManager.initNative(getApplicationContext(), new NativeKeyImpl(), MainActivity.class);
Registering Soups with a Configuration File
- Place your soup configuration files in the
/res/raw/
project folder. - Add a single line of code for each file you provide.
- To load a
userstore.json
file, useSmartStoreSDKManager.getInstance().setupUserStoreFromDefaultConfig();
- To load a globalstore.json file, use
SmartStoreSDKManager.getInstance().setupGlobalStoreFromDefaultConfig();
- To load a
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.
- Open your project directory in Android Studio.
- In the
app/res
folder, create a folder named “raw”. - Right-click
app/res/raw
and select New > File. Name the fileuserstore.json
. - 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"}, ] } ]}
- Open
MainActivity.java
and 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;
- At the top of the
MainActivity
class, 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;
- In the
onCreate(Bundle savedInstanceState)
method, import the soup definitions from youruserstore.json
file: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"); }
- In the
onFetchContactsClick(View v)
method, clear the Contact soup to avoid creating duplicate records:smartStore.clearSoup("Contact");
objectType = "Contact";
- In the
onFetchAccountsClick(View v)
method, clear the Account soup to avoid creating duplicate records:smartStore.clearSoup("Account");
objectType = "Account";
- In the
client.sendAsync()
method, callupsert()
in thefor
loop that inserts the JSON response into thelistAdapter
object: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); } }
- 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.
- 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:
- Copy the configuration file to your hybrid project’s top-level
www/
directory (for example,MyProject/www/
). - At a Terminal window or Windows command prompt, cd to your project directory (for example,
MyProject/
). - 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
- About SmartStore
- Smart SQL Queries
- The MobileSyncExplorer sample app includes SmartStore sample code for native and React Native platforms. For instructions on using these samples, see Installing the Sample Apps.
- SmartStoreExplorer (hybrid only)
- React Native website