đź“Ł Attention Salesforce Certified Trailblazers! Link your Trailhead and Webassessor accounts and maintain your credentials by December 14th. Learn more.
close
trailhead

Synchronize Offline Changes with SmartSync Data Framework

Learning Objectives

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

  • Understand basic SmartSync Data Framework concepts.
  • Use the SmartSync Data Framework library to synchronize data between the user’s local device and the Salesforce server.

Using SmartSync Data Framework to Synchronize Offline Changes

SmartSync Data Framework is a Mobile SDK library that works with SmartStore to guarantee the integrity and consistency of Salesforce records. SmartSync Data Framework makes it feasible for customers using Mobile SDK apps to continue editing Salesforce records locally when their mobile devices lose connectivity. When connectivity is restored, the Mobile SDK app uses the framework to synchronize local changes in SmartStore with the corresponding records on the Salesforce server.

About Network Requests

All data requests in SmartSync Data Framework apps are asynchronous. Sync methods format the request and send it to the Salesforce cloud. Your app receives the server’s response in a callback method or update block that you define. Responses include a sync state object that describe the sync operation’s settings and status.

SmartSync Data Framework lets your app dictate how changes in SmartStore data are merged with Salesforce data. Convenience methods automate common network tasks such as fetching sObject metadata, fetching a list of most recently used objects, and building SOQL and SOSL queries.

Configuring Apps to Use SmartSync Data Framework

Mobile SDK apps use a central SDK manager object to enforce the correct app startup and authentication protocols. The manager your app uses also determines the scope of SDK functionality included in the app. Native Android and iOS SDKs implement three manager objects.
SalesforceSDKManager
For the most basic apps. These apps don't use Mobile SDK offline features.
SmartStoreSDKManager
For apps that use SmartStore but not SmartSync Data Framework.
SmartSyncSDKManager
For apps that use both SmartStore and SmartSync Data Framework.

When you create a native app with forcedroid or forceios, your new app is configured to use SmartSyncSDKManager. This manager imports SmartStore, SmartSync Data Framework, and all other Mobile SDK libraries. Apps initialize the SmartSyncSDKManager object during bootstrap initialization.

Sync Manager Objects

SmartSyncSDKManager instantiates a central sync manager object for synchronizing your app's soups with the Salesforce server. For Android, this class is SyncManager. For iOS, it's SFSmartSyncSyncManager.

Synchronization doesn't happen automatically. After your app configures a sync operation, it calls sync manager methods for syncing down (Salesforce to soup) and up (soup to Salesforce). You can call these methods at your discretion, but only when the host device is connected to the internet. It's up to your app to determine whether the device is connected.

Sync Configuration

Whether you’re syncing up from SmartStore to Salesforce, or down from Salesforce to SmartStore, you provide sync configuration details. You can configure a sync operation either in code or in a JSON configuration file. In either case, at runtime Mobile SDK returns your configuration to you in a sync state object.

At the most basic level, you provide:
Store
Indicates whether the store you’re using is a global store or a user store. If you’re using a named store, you also specify its name.
Soup name
Name of the soup that’s the source for a sync up operation, or the receiver for a sync down operation. This soup is required to support an indexed string field named __local__.
Two other parameters define what the sync operation does:
Target
Describes which records you’re syncing. The target is the central configuration value for any sync operation. Think of a target as describing the scope of data your operation requires.
Options
Controls how the operation merges data. In certain cases, you can use options instead of a target to provide a list of field names for syncing.

Usage of the target and options parameters differs between sync up and sync down, as described in the following sections. In certain cases, one or the other parameter can be optional. If you omit the optional target or options, SmartSync Data Framework assumes a default value.

Two more parameters can save you some coding effort. Standard sync up and sync down methods require you to pass each sync parameter separately as an argument. Once you’ve run a sync configuration, however, you can reuse it by calling a resync method. Resync methods use one of these identifiers to look up your configuration:
  • Sync ID—Runtime ID of a sync operation that you’ve used previously in the current session. Every sync down or sync up method returns this ID as part of a larger state object.
  • Sync name—(Optional) Name of a saved sync configuration. You can name a configuration and save it in memory when you first run it, or you can import it from an external JSON configuration file.

When you perform a sync, you can provide logic that Mobile SDK runs after the sync operation ends. You can use the callback to send event notifications, log success, or do anything else that enhances your app.

Sync Down Target and Options

  • target—Specifies the source sObject type and the fields to download to the soup. You can use any one of the following strings:
    {type:"soql", query:"<soql_query>"}
    Downloads the sObjects returned by the given SOQL query
    {type:"sosl", query:"<sosl_query>"}
    Downloads the sObjects returned by the given SOSL query
    {type:"mru", sobjectType:"<sobject_type>", fieldlist:"<fields_to_fetch>"}
    Downloads the specified fields of the most recently used sObjects of the specified sObject type
    Mobile SDK sends the request to Salesforce, receives the response, and uses your configuration to populate your soup.
  • options—(Optional) Provide a map with one or both of the following keys and their described values:
    • mergeMode—To control how SmartSync Data Framework merges data, you specify a merge mode in the options parameter. SmartSync Data Framework support two basic modes:
      Overwrite
      (Default setting) Overwrites records that have been modified.
      Leave if changed
      Preserves records that have been modified. This mode requires extra round-trips to the server. so use it with care.
If you don’t specify a merge mode, SmartSync Data Framework overwrites local data.

Sync Up Target and Options

  • target—Map with one or both of the following keys and their described values:
    • createFieldlist—Comma-separated names of soup fields whose values are to be inserted into newly created Salesforce records. Can contain locked (read-only) fields.
    • updateFieldlist—Comma-separated names of soup fields to be used to update writable fields in existing Salesforce records. Cannot contain locked (read-only) fields.
    The target is not required in some sync up methods. In these cases, you use the fieldlist key in options to specify which fields to sync.
  • options—(Optional) An object that maps one or more of the following keys to the values described here:
    • fieldlist—Comma-separated list of soup field names that you’re sending to the server. You can use this key instead of the target field lists or with your target’s createFieldList. Make sure that your sync up operation doesn’t try to update read-only fields on the server.
    • mergeMode—To control how incoming server data merges with local changes in the soup, you specify a merge mode in the options parameter. SmartSync Data Framework support two basic modes:
      Overwrite
      (Default setting) Overwrites soup records that have been modified.
      Leave if changed
      Preserves server records that have been modified. This mode requires extra round-trips to the server, so use it with care.

Store Parameter

Hybrid and React Native apps indicate the store they're using through an optional storeConfig parameter. This parameter applies to sync up and sync down operations. You can specify it either of two ways.
As a map of the following key-value pairs:
  • isGlobalStore—(Optional) Boolean indicating whether you’re using a global store (true) or a user store (false). Default value is false.
  • storeName—(Optional) Name of the store. Default value is the name of the default store.
For example:
{"isGlobalStore" : false, "storeName" : "MyStore"}
As a Boolean:
You can instead pass a simple Boolean value. In this case, Mobile SDK interprets the argument to be the isGlobalStore value.

If you omit the store parameter, SmartSync Data Framework performs operations on the current user store.

Using Named Syncs and Sync Configuration Files

Standard sync up and sync down methods require you to pass each sync parameter as a separate argument. Mobile SDK provides two features that help organize and streamline your sync efforts. These features are sync names and sync configuration files, and they give you nice payback for minimal effort. Although you're not required to adopt either feature, we use them extensively in this trail.

To run a named sync, use the platform's reSync method that accepts a sync name. Resync methods use one of these identifiers to look up your configuration:
  • Sync ID—Runtime ID of a sync operation that you’ve used previously in the current session. Every sync method returns this ID as part of a state object.
  • Sync name—(Optional) Name of a saved sync configuration. You can name a configuration and save it in memory when you first run it, or you can import it from an external JSON configuration file.

Sync Names

Sync operations can be complicated to configure, and they tend to be reused often. To make it easier to define and reuse the right operation at the right time, Mobile SDK gives you the power to name your syncs. You can then use your sync names to rerun your sync operations. For example, you can use sync names to
  • Configure a sync operation and run it in a single pass
  • Rerun a sync operation
  • Get the status of an in-progress sync operation
  • Check for the existence of a sync configuration
  • Delete a sync configuration
You can create named syncs either with the API or in sync configuration files. Mobile SDK supports named syncs on all platforms for all app types.

Sync Configuration Files

Mobile SDK gives you two options for defining sync down and sync up operations: with configuration files, or with code. With configuration files, you can write your sync definitions once and then reuse them in all versions of your apps. Mobile SDK supports sync configuration files on all platforms for all app types.

Hybrid and React Native apps load sync configuration files automatically, while native apps load them with a single line of code. Let's look at the structure and rules for these files.

File Names
Mobile SDK supports configuration files only for the default global store and default user store.
  • For the default global store, provide a file named globalsyncs.json.
  • For the default user store, provide a file named usersyncs.json.
File Locations
Place your configuration files in the following locations.
iOS:
  • Native apps: / in the Resources bundle
  • Hybrid apps: /www in the Resources bundle
Android:
  • Native apps: /res/raw
  • Hybrid apps: /assets/www
File Format
The file format is the same regardless of app type or target platform. A sync definition requires five fields:
  • Sync name
  • Sync type
  • Soup name
  • Target
  • Options

Example

This configuration file defines sync down and sync up operations. Both configurations use the same fields: Id, Name, and LastModifiedDate. However, notice two significant rules for placement of the values:
  • The sync down configuration specifies the fields in the "target" member, using a SOQL query, while sync up specifies them under "options" as a list.
  • The sync down operation specifies its merge mode in "options", while sync up does the same in "merge_mode".
Opposing merge modes—such as "OVERWRITE" for sync down versus "LEAVE_IF_CHANGED" for sync up—guarantee a single source of truth. In this case, they allow server data to overwrite soup data, but not the other way around.
{
  "syncs": [
    {
      "syncName": "sync1",
      "syncType": "syncDown",
      "soupName": "accounts",
      "target": {"type":"soql", "query":"SELECT Id, Name, LastModifiedDate 
        FROM Account"},
      "options": {"mergeMode":"OVERWRITE"} 
    },
    {
      "syncName": "sync2",
      "syncType": "syncUp",
      "soupName": "accounts",
      "target": {"createFieldlist":["Name"]},
      "options": {"fieldlist":["Id", "Name", "LastModifiedDate"],
      "mergeMode":"LEAVE_IF_CHANGED"}
    }
  ]
}

Using SmartSync Data Framework in Native iOS Apps

Note

Note

This content presents Swift source code that uses promises. Promise implementations used in Mobile SDK 6.2.0 will no longer be officially supported in the Mobile SDK 7.0 release. To try out the upcoming changes, you can install the forceios 7.0.0 preview version as described in the Set Up Your Mobile SDK Developer Tools project. To browse the 7.0 pre-release source code, see https://github.com/forcedotcom/SalesforceMobileSDK-iOS/tree/dev70preview/libs.

Syncing Down

For downloading Salesforce records to your SmartStore soup, SFSmartSyncSyncManager gives you various options.

You can create a sync down configuration without running it:
Objective-C
- (SFSyncState *)createSyncDown:(SFSyncDownTarget *)target 
    options:(SFSyncOptions *)options 
    soupName:(NSString *)soupName 
    syncName:(NSString *)syncName;
Swift
syncManager.Promises.createSyncDown(target: target, options: options, soupName: soupName, syncName: syncName)
.then { 
..
}
Or, you can create and run an unnamed sync down operation without options—but be advised that it overwrites any local changes:
Objective-C
- (SFSyncState*) syncDownWithTarget:(SFSyncDownTarget*)target
                           soupName:(NSString*)soupName
                        updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock; 
Swift
syncManager.Promises.syncDown(target: target, soupName: soupName)
.then { 
..
}
.catch SFSmartSync.SyncDownFailed {
}
For more control, you can create and run an unnamed sync down configuration that uses your options:
Objective-C
- (SFSyncState*) syncDownWithTarget:(SFSyncDownTarget*)target
                            options:(SFSyncOptions*)options
                           soupName:(NSString*)soupName
                        updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock; 
Swift
syncManager.Promises.syncDown(target: target, options: options,soupName: soupName)
.then { 
..
}
.catch SFSmartSync.SyncDownFailed {
}
Or you can create, name, and run a sync down configuration:
Objective-C
- (SFSyncState*) syncDownWithTarget:(SFSyncDownTarget*)target
                            options:(SFSyncOptions*)options
                           soupName:(NSString*)soupName
                           syncName:(NSString* __nullable)syncName
                        updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock;
Swift
syncManager.Promises.syncDown(target: target, options: options,
    soupName: soupName,syncName: syncName)
.then { 
..
}
.catch SFSmartSync.SyncDownFailed {
}

The SFSyncTarget class provides factory methods for creating target objects from a SOQL, SOSL, or MRU query string. You specify the objects to download with a SOQL or SOSL query string. If you use an MRU query target, you specify only a list of field names. Mobile SDK reports the progress of the sync operation through the callback method or update block that you implement.

For iOS native, the mergeMode option can be one of the following values:
  • SFSyncStateMergeModeOverwrite—Overwrite modified local records and lose all local changes.
  • SFSyncStateMergeModeLeaveIfChanged—Preserve all local changes and locally modified records.
Important

Important

If you use the syncDownWithTarget:soupName:updateBlock: method, which doesn’t take an options parameter, SmartSync Data Framework can overwrite existing sObjects in the cache. To preserve local changes in such cases, always sync up before syncing down.

Syncing Up

During sync up, SmartSync Data Framework replicates created, updated, and deleted soup records on the server. Mobile SDK provides a default target that you can use instead of defining your own. The default target is simple and direct: using the Salesforce API, it merely syncs any changed records from the soup to the server.

If you have a target, you can create a sync up operation without running it:
Objective-C
- (SFSyncState *)createSyncUp:(SFSyncUpTarget *)target 
                      options:(SFSyncOptions *)options 
                     soupName:(NSString *)soupName 
                     syncName:(nullable NSString *)syncName;
Swift
syncManager.Promises.createSyncUp(target: target, options: options, soupName: soupName, syncName: syncName)
.then { 
..
}
You can use the default target with the following SFSmartSyncSyncManager method:
Objective-C
- (SFSyncState*) syncUpWithOptions:(SFSyncOptions*)options 
                          soupName:(NSString*)soupName 
                       updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock;
Swift
syncManager.Promises.syncUp(options: options, soupName: soupName)
.then { syncState in
..
}
.catch SFSmartSync.SyncUpFailed {
}
If you have a customized sync up target, you can run it by calling this SFSmartSyncSyncManager method:
Objective-C
- (SFSyncState*) syncUpWithTarget:(SFSyncUpTarget*)target
                          options:(SFSyncOptions*)options
                         soupName:(NSString*)soupName
                      updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock;
Swift
syncManager.Promises.syncUp(target:target, options: options, soupName: soupName)
.then { syncState in
..
}
.catch SFSmartSync.SyncUpFailed {
}
Or, you can create, name, and run a sync operation:
Objective-C
- (SFSyncState*) syncUpWithTarget:(SFSyncUpTarget*)target
                          options:(SFSyncOptions*)options
                         soupName:(NSString*)soupName
                         syncName:(NSString*)syncName
                      updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock;
Swift
syncManager.Promises.syncUp(target:target, options: options, 
    soupName: soupName,syncName: syncName)
.then { syncState in
..
}
.catch SFSmartSync.SyncUpFailed {
}

Resync

Important yet sometimes overlooked, the pair of resync methods are versatile and easy to code. These methods run sync configurations you’ve previously defined elsewhere. Despite the name, you can use these methods to run a named sync for the first time. The resync methods sync only records that have been created or updated since the last sync. If the sync has never been run, resync copies all new or changed records. Otherwise, it performs an incremental sync.

To use the resync methods, you provide either a sync ID from a previous sync or a predefined sync name. To run an existing named sync:
Objective-C
- (nullable SFSyncState*) reSyncByName:(NSString*)syncName 
                           updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock;
Swift
syncManager.Promises.reSync(syncId: syncName)
.then { 
..
}
.catch SFSmartSync.ReSyncFailed {
}
If you cached the returned SFSyncState.syncId value from an earlier sync operation, you can rerun that sync using reSync:updateBlock::
Objective-C
- (SFSyncState*) reSync:(NSNumber*)syncId 
            updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock;
Swift
syncManager.Promises.reSync(syncId: syncId)
.then { syncState in
..
}
.catch SFSmartSync.ReSyncFailed {
}

The reSync methods support both sync up and sync down configurations.

Example

You can easily replace the REST API calls in the forceios template with SmartSync Data Framework code. The following example demonstrates a forceios app written in Swift using Mobile SDK promises. For Objective-C examples, see Using SmartSync Data Framework in Native Apps in the Salesforce Mobile SDK Development Guide.

To prepare for the following steps, create a forceios app specifying native_swift for the application type.

  1. Create a userstore.json file with the following content.
    {
      "soups": [
        {
          "soupName": "User",
          "indexes": [
            { "path": "Id", "type": "string"},
            { "path": "Name", "type": "string"},
            { "path": "__local__", "type": "string"}
          ]
        }
      ]
    }
  2. Create a usersyncs.json file with the following content.
    {
      "syncs": [
        {
          "syncName": "syncDownUsers",
          "syncType": "syncDown",
          "soupName": "User",
          "target": {"type":"soql", "query":"SELECT Id, Name FROM User LIMIT 10"},
          "options": {"mergeMode":"OVERWRITE"}
        }
      ]
    }
  3. Add your userstore.json and usersyncs.json 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 sync configuration files. If your files are 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.
  4. In AppDelegate.swift, load your sync configuration into memory. Add the following code to the postLaunch block of the init() function:
    SalesforceSwiftSDKManager.shared().setupUserStoreFromDefaultConfig()
    SalesforceSwiftSDKManager.shared().setupUserSyncsFromDefaultConfig()
  5. In the imports section of RootViewController.swift, import SmartStore, SmartSync Data Framework, and PromiseKit.
    import SmartStore
    import SmartSync
  6. At the top of the RootViewController class, declare variables for SmartStore and the sync manager.
    var store: SFSmartStore?
    var syncManager: SFSmartSyncSyncManager?
  7. In the loadView function, delete the following lines.
    let restApi = SFRestAPI.sharedInstance()
    restApi.Promises
    .query(soql: "SELECT Name FROM User LIMIT 10")
    .then {  request  in
        restApi.Promises.send(request: request)
    }.done { [unowned self] response in
        self.dataRows = 
            response.asJsonDictionary()["records"] as! [NSDictionary]
        SalesforceSwiftLogger.log(type(of:self), 
                                    level:.debug, 
                                  message:"request:didLoadResponse: \ 
                                      #records: \(self.dataRows.count)")
            DispatchQueue.main.async(execute: {
                self.tableView.reloadData()
        })
    }.catch { error in
         SalesforceSwiftLogger.log(type(of:self), 
                                   level:.debug, 
                                 message:"Error: \(error)")
    }
    This code uses the REST API to statically populate the table rows in the view. With SmartSync Data Framework, you populate the table differently.
  8. In place of the lines you deleted, add the following code.
    1. Instantiate SmartStore.
      store = SFSmartStore.sharedStore(withName: kDefaultSmartStoreName) as? SFSmartStore
    2. Get the shared instance of the sync manager.
      syncManager = SFSmartSyncSyncManager.sharedInstance(for:store!) 
    3. Call the reSync method to populate SmartStore with the User records.
      _ = syncManager?.Promises 
      .reSync(syncName: "syncDownUsers") 
      .done { [unowned self] (_) in 
      
      }

      When this call runs, SmartSync Data Framework handles the REST API request and response for you. It also deposits User records from your Salesforce org into the SmartStore soup you specified in the config file.

    4. In the empty block, add a call to a function named loadFromStore() that you haven't yet defined.
      _ = syncManager?.Promises 
      .reSync(syncName: "syncDownUsers") 
      .done { [unowned self] (_) in 
          self.loadFromStore(); 
      }
      You can implement this function to populate the table view from SmartStore.
  9. Implement the loadFromStore() function to transfer User records from the soup to the table view.
    1. Create a function named loadFromStore().
      func loadFromStore() { 
      
      }
    2. Define a SmartStore query spec.
      let querySpec = SFQuerySpec.Builder(soupName:"User") 
      .queryType(value: "smart") 
      .smartSql(value: "select {User:Name} from {User}") 
      .pageSize(value: 100) .build(); 
      _ = self.store?.Promises
    3. Query SmartStore.
      .query(querySpec: querySpec, pageIndex: 0) 
      .done { records in 
    4. Use the query results to populate the dataRows member of the table view object.
      self.dataRows = (records as! [[NSString]]).map({ 
         row in return ["Name": row[0]]; 
      }) 
    5. Switch to the UI thread to update the table rows in the view.
              DispatchQueue.main.async(execute: { 
                  self.tableView.reloadData() 
              }) 
          } 
       
      }
    6. Add a catch block to handle any errors.
          .catch { error in 
              SalesforceSwiftLogger.log(type(of:self), 
                  level:.debug, 
                message:"Error: \(error)") 
          }
    Here's the finished loadFromStore() function.
    func loadFromStore()
    {
        let querySpec = SFQuerySpec.Builder(soupName:"User")
        .queryType(value: "smart")
        .smartSql(value: "select {User:Name} from {User}")
        .pageSize(value: 100)
        .build();
    _ = self.store?.Promises
        .query(querySpec: querySpec, pageIndex: 0)
        .done { records in
            self.dataRows = (records as! [[NSString]]).map({ row in
                return ["Name": row[0]];
            })
            DispatchQueue.main.async(execute: {
                self.tableView.reloadData()
            })
            
        }
        .catch { error in
            SalesforceSwiftLogger.log(type(of:self), level:.debug, message:"Error: \(error)")
        }
    }

Using SmartSync Data Framework in Native Android Apps

Syncing Down

To download sObjects from the server to your local SmartSync Data Framework soup, use one of the following SyncManager methods.

You can create a sync down configuration without running it:
public SyncState createSyncDown(SyncDownTarget target, 
    SyncOptions options, String soupName, String syncName) 
    throws JSONException;
Or, you can create and run an unnamed sync down operation without options—but be advised that it overwrites any local changes:
public SyncState syncDown(SyncDownTarget target, String soupName, 
    SyncUpdateCallback callback) throws JSONException;
For more control, you can create and run an unnamed sync down operation that uses your own merge options:
public SyncState syncDown(SyncDownTarget target, SyncOptions options, 
    String soupName, SyncUpdateCallback callback) 
    throws JSONException;
Or you can name that sync down configuration as you create it, and then run it:
public SyncState syncDown(SyncDownTarget target, SyncOptions options, 
    String soupName, String syncName, SyncUpdateCallback callback) 
    throws JSONException;

Syncing Up

To apply local changes on the server, use one of the following SyncManager methods.

  • You can create a named sync up configuration without running it:
    public SyncState createSyncUp(SyncUpTarget target, 
        SyncOptions options, 
        String soupName, 
        String syncName) 
        throws JSONException;
  • You can create and run an unnamed sync configuration in one call:
    public SyncState syncUp(SyncUpTarget target, 
        SyncOptions options, 
        String soupName, 
        SyncUpdateCallback callback) 
        throws JSONException;
  • Or, you can create and run a named sync up configuration in one call:
    public SyncState syncUp(SyncUpTarget target, 
        SyncOptions options, 
        String soupName, 
        String syncName, 
        SyncUpdateCallback callback) 
        throws JSONException;

The syncUp methods update the server with data from the given SmartStore soup. They look for created, updated, or deleted records in the soup and replicate those changes on the server. Either the target or the options parameter, or both, specifies a list of fields to be uploaded. A target lets you differentiate between a createFieldlist for creating records and an updateFieldlist for updating existing records. These settings let you sync new records while also avoiding attempts to update read-only fields in existing records. If you define target field lists and options field lists, the options setting is ignored.

Resync

Important yet sometimes overlooked, the pair of resync methods are versatile and easy to code. These methods run sync configurations you’ve previously defined elsewhere. Despite the name, you can use these methods to run a named sync for the first time. The resync methods sync only records that have been created or updated since the last sync. If the sync has never been run, resync copies all new or changed records. Otherwise, it performs an incremental sync.

To use the resync methods, you provide either a sync ID from a previous sync or a predefined sync name. To run a named sync:

public SyncState reSync(String syncName, SyncUpdateCallback callback) 
    throws JSONException;
If you cached the returned SFSyncState.syncId value from an earlier sync operation, you can rerun that sync using reSync:updateBlock::
public SyncState reSync(long syncId, SyncUpdateCallback callback) 
    throws JSONException;

The reSync methods support both sync up and sync down configurations.

Example

The native SmartSyncExplorer sample app demonstrates how to use SmartSync Data Framework named syncs and sync configuration files with Contact records. In Android, it defines a ContactObject class that represents a Salesforce Contact record as a Java object. To sync Contact data down to the SmartStore soup, the syncDown() method resyncs a named sync down configuration that defines a SOQL query.

In the following snippet, the reSync() method loads the following named sync operations from the res/raw/usersyncs.json file:
{
  "syncs": [
    {
      "syncName": "syncDownContacts",
      "syncType": "syncDown",
      "soupName": "contacts",
      "target": {"type":"soql", "query":"SELECT FirstName, LastName, Title, MobilePhone, Email, Department, HomePhone FROM Contact LIMIT 10000"},
      "options": {"mergeMode":"OVERWRITE"}
    },
    {
      "syncName": "syncUpContacts",
      "syncType": "syncUp",
      "soupName": "contacts",
      "target": {"createFieldlist":["FirstName", "LastName", "Title", "MobilePhone", "Email", "Department", "HomePhone"]},
      "options": {"fieldlist":["Id", "FirstName", "LastName", "Title", "MobilePhone", "Email", "Department", "HomePhone"], "mergeMode":"LEAVE_IF_CHANGED"}
    }
  ]
}
public synchronized void syncDown() {
	
    try {
	 syncMgr.reSync(SYNC_DOWN_NAME /* see usersyncs.json */, new SyncUpdateCallback() {
            @Override
            public void onUpdate(SyncState sync) {
                if (Status.DONE.equals(sync.getStatus())) {
                    fireLoadCompleteIntent();
                }
            }
        });
    } catch (JSONException e) {
        Log.e(TAG, "JSONException occurred while parsing", e);
    } catch (SmartSyncException e) {
        Log.e(TAG, "SmartSyncException occurred while attempting to sync down", e);
    }

}

If the sync down operation succeeds—that is, if sync.getStatus() equals Status.DONE—the received data goes into the specified soup. The callback method then only needs to fire an intent that reloads the data in the Contact list.

To sync up to the server, you call syncUp() with the same arguments as syncDown(): list of fields, name of source SmartStore soup, and an update callback. The only coding difference is that you can format the list of affected fields as an instance of SyncOptions instead of SyncTarget. Here’s the way it’s handled in the SmartSyncExplorer sample:

public synchronized void syncUp() {
       
    final SyncUpTarget target = new SyncUpTarget();
       
    final SyncOptions options = 
        SyncOptions.optionsForSyncUp(Arrays.asList(ContactObject.CONTACT_FIELDS_SYNC_UP),
               
            MergeMode.LEAVE_IF_CHANGED);

	
    try {
		
        syncMgr.syncUp(target, options, ContactListLoader.CONTACT_SOUP, 
            new SyncUpdateCallback() {

			
                @Override
			
                public void onUpdate(SyncState sync) {
		        
                    if (Status.DONE.equals(sync.getStatus())) {
					
                        syncDown();
				
                    }
			
                }
		
            });
	
    } catch (JSONException e) {
           
        Log.e(TAG, "JSONException occurred while parsing", e);
	
    } catch (SmartSyncException e) {
           
        Log.e(TAG, "SmartSyncException occurred while attempting to sync up", e);
	
    }

}

In the internal SyncUpdateCallback implementation, this example takes the extra step of calling syncDown() when sync up is done. This step guarantees that the SmartStore soup remains up-to-date with any recent changes made to Contacts on the server.

Using SmartSync Data Framework in Hybrid Apps

SmartSync Data Framework provides two different approaches for hybrid apps.
  • com.salesforce.plugin.smartsync — This Cordova plugin provides JavaScript access to the native “sync-down” and “sync-up” functionality of SmartSync Data Framework. As a result, performance-intensive operations — network negotiations, parsing, SmartStore management — run on native threads that do not impact web view operations. Use the plugin in simpler scenarios, such as syncing large numbers of records rapidly in a native thread rather than in the web view.
  • smartsync.js — This JavaScript library provides a Force SObject data framework for more complex syncing operations. The library is based on Backbone.js, an open-source JavaScript framework that defines an extensible data modeling mechanism. Using smartsync.js, you can create models of Salesforce objects and manipulate the underlying records just by changing the model data. If you perform a SOQL or SOSL query, you receive the resulting records in a model collection rather than as a JSON string.
You can combine these approaches in the same app to good effect. For example, the plugin exposes two methods: syncDown() and syncUp(). When you use these methods, the following guidelines can make your life easier.
  • To create, update, or delete records in SmartStore soups, use Force.SObject from smartsync.js. The smartsync.js library automatically creates special fields on soup records that the plug-in expects. You can then call syncUp() on the plug-in to update the server.
  • Similarly, to create the soup for your sync operations, use Force.StoreCache from smartsync.js.
  • If you’ve changed objects in the soup, always call syncUp() before calling syncDown().

Hybrid apps created in Mobile SDK 5.0 or later automatically include the SmartSync Data Framework plugin.

Syncing Down

The SmartSync Data Framework plugin’s syncDown() method downloads specified sObjects into a SmartSync Data Framework soup.

cordova.require("com.salesforce.plugin.smartsync").syncDown
    (storeConfig, target, soupName, options, syncName, success, error);

For the merge mode option, hybrid apps use these identifiers.

  • {mergeMode:Force.MERGE_MODE_DOWNLOAD.OVERWRITE}
    (default if merge mode is not specified)
  • {mergeMode:Force.MERGE_MODE_DOWNLOAD.LEAVE_IF_CHANGED}

The success parameter specifies a callback function that is called multiple times during the sync operation:

  • When the sync operation begins
  • When the internal REST request has completed
  • After each page of results is downloaded, until all results have been received

While the sync operation runs, status updates arrive via browser events. To listen for these updates, implement the following event listener:

document.addEventListener("sync",
   function(event)
      {
         // event.detail contains the status of the sync operation
      }
);
The event.detail member brings you sync metadata and—more importantly—information about the operation's current progress and status:
  • syncId—The ID for this sync operation
  • type—syncDown
  • target—The target you provided
  • soupName—The soup name you provided
  • options—Array of options you specified
  • status—The sync status, which can be one of the following:
    • NEW
    • RUNNING
    • DONE
    • FAILED
  • progress—The percentage of total records processed so far (an integer 0–100)
  • totalSize—The number of records processed so far
It’s a good idea to store the sync ID. You can use this value later to rerun the sync operation.

Syncing Up

The SmartSync Data Framework plugin’s syncUp() method replicates created, deleted, or updated records from a SmartStore soup to the Salesforce server.

cordova.require("com.salesforce.plugin.smartsync").syncUp
   ([storeConfig,] [target,] soupName, options, syncName, successCb, errorCb);
You provide configuration settings for the operation, including
  • target—If you’ve defined a custom native sync up target, use this parameter to identify its class.
  • options—Provide a map with the following key:
    • fieldlist—A list of fields sent to the server.
  • successCb, errorCb—Success and error callback functions.

Status updates on the sync operation arrive via the same event handler that you implemented for syncDown:

document.addEventListener("sync",
   function(event)
      {
         // event.detail contains the status of the sync operation
      }
);

As in sync down operations, the event.detail member brings you sync metadata and reports the operation's current progress and status.

Example

This example uses the named syncs defined in the following usersyncs.json file.
{
  "syncs": [
    {
      "syncName": "syncDownContacts",
      "syncType": "syncDown",
      "soupName": "contacts",
      "target": {"type":"soql", 
      "query":"SELECT FirstName, LastName, Title, MobilePhone, 
               Email, Department, HomePhone FROM Contact LIMIT 10000"},
      "options": {"mergeMode":"OVERWRITE"}
    },
    {
      "syncName": "syncUpContacts",
      "syncType": "syncUp",
      "soupName": "contacts",
      "target": {
         "createFieldlist":["FirstName", "LastName", "Title", "MobilePhone", 
                            "Email", "Department", "HomePhone"]},
      "options": {
         "fieldlist":["Id", "FirstName", "LastName", "Title", 
                      "MobilePhone", "Email", "Department", "HomePhone"], 
         "mergeMode":"LEAVE_IF_CHANGED"}
    }
  ]
}

Both sync operations call reSync() on the SmartSync Data Framework Cordova plug-in, passing in a sync name. Sync up and sync down operations both use the handleSyncUpdate() function for success callbacks. Notice that this function calls syncDown() after a successful syncUp run finishes. It's always a good idea to call syncDown() after syncUp() to make sure that your soups remain in sync with changes on the server. In its current form, the success callback takes no action after syncDown() finishes.

handleSyncUpdate: function(sync) {
    if (sync.type === "syncUp") {
        this.syncDown();
    }
},
syncDown: function() {
    cordova.require("com.salesforce.plugin.smartsync").
        reSync("syncDownContacts" /* see usersyncs.json */, 
        this.handleSyncUpdate.bind(this));
},
syncUp: function() {
    cordova.require("com.salesforce.plugin.smartsync").
        reSync("syncUpContacts" /* see usersyncs.json */, 
        this.handleSyncUpdate.bind(this));
},

Using SmartSync Data Framework in React Native Apps

In React Native, SmartSync Data Framework provides its functionality through a smartsync module. In your .js files, you can import this module and its dependencies from the react-native-force library as follows:
import {oauth, net, smartstore, smartsync} from 'react-native-force';
Apps created with forcereact include a similar import statement without smartstore or smartsync. Be sure to add these modules if you’re supporting Mobile SDK offline features.

Sync Configuration

As you probably expect, you provide the same configuration metadata for React Native apps as for hybrid apps. Here’s an example of a target declaration.
const fieldlist = ["Id", "FirstName", "LastName", 
    "Title", "Email", "MobilePhone","Department",
    "HomePhone", "LastModifiedDate"];
const target = {type:"soql", query:
    `SELECT ${fieldlist.join(",")} FROM Contact LIMIT 10000`};
For merge mode in options, you use one of the following values:
  • To overwrite records that have been modified:
    {mergeMode:smartsync.MERGE_MODE.OVERWRITE}
    If you don’t define the mergeMode key, SmartSync Data Framework uses this mode as the default.
  • To preserve, rather than overwrite, records that have been modified:
    {mergeMode:smartsync.MERGE_MODE.LEAVE_IF_CHANGED}

You can choose to provide success and error callback functions.

Syncing Down

The syncDown() function for React Native is identical to the hybrid function, except that it’s called on the smartsync module. It downloads specified Salesforce records into a SmartStore soup.

smartsync.syncDown
    ([storeConfig,] target, soupName, options, [syncName,] success, error);
If existing records in the soup have the same ID as records returned by the query, SmartSync Data Framework by default overwrites the duplicate soup records. To control how downloaded data merges with edited records in the soup, specify a merge mode in the options parameter.

The success parameter specifies a callback function that the internal sync manager calls when the sync operation ends in success. Unlike the hybrid implementation, the React Native library calls this function only once, when the operation has ended.

Mobile SDK passes a single argument to the success callback. This argument returns the following sync metadata which includes the sync ID:
  • syncId—The ID for this sync operation
  • type—syncDown
  • target—The target you provided
  • options—(Optional) An array specifying one or both of the following: merge mode, field list
  • soupName—The soup name you provided
  • syncName—The sync name you provided, if any
Even if you have no other use for a success callback, it’s a good idea to do so and store the sync ID. You can use this value later, if necessary, in a resync operation.
Here’s a sync down example.
const fieldlist = ["Id", "FirstName", "LastName", 
    "Title", "Email", "MobilePhone","Department",
    "HomePhone", "LastModifiedDate"];

const target = {type:"soql", 
    query:`SELECT ${fieldlist.join(",")} 
           FROM Contact 
           LIMIT 10000`};

smartsync.syncDown(false,
    target,
    "contacts",
    {mergeMode:smartsync.MERGE_MODE.OVERWRITE},
    syncName,
    (sync) => {/* Do something meaningful or omit this member */},
    (error) => {/* Do something meaningful or omit this member */}
);

Syncing Up

The SmartSync Data Framework plugin’s syncUp() function copies created, deleted, or updated records from a SmartStore soup to the Salesforce server.

smartsync.syncUp
   ([storeConfig,] target, soupName, options, [syncName,] successCb, errorCb);

As in sync down operations, the success callback function’s input argument brings you sync metadata including the sync ID.

Here’s a sync up example.
const fieldlist = ["FirstName", "LastName", "Title", "Email", 
    "MobilePhone","Department","HomePhone"];
smartsync.syncUp(false,
    {},
    "contacts",
    {mergeMode:smartsync.MERGE_MODE.OVERWRITE, fieldlist},
    (sync) => {/* Do something meaningful or omit this callback */},
    (error) => {/* Do something meaningful or omit this callback */}
);
retargeting