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 enables mobile customers whose devices have lost connectivity to continue working on Salesforce data. When connectivity is restored, SmartSync Data Framework synchronizes any changes made to local SmartStore records with the corresponding records on the Salesforce server.

About Network Requests

All data requests in SmartSync Data Framework apps are asynchronous. Sync methods return the server’s response in a callback method or update block that you define. These responses include a sync state object containing the following information:

  • Sync operation ID. You can check the progress of the operation at any time by passing this ID to the sync manager’s getSyncStatus method.
  • Your sync parameters (soup name, target for sync down operations, options for sync up operations).
  • Type of operation (up or down).
  • Progress percentage (integer, 0–100).
  • Total number of records in the transaction.
SmartSync Data Framework predefines cache policies for fine-tuning interactions between cached data and server data in offline and online scenarios. Mobile SDK convenience methods automate common network activities—fetching sObject metadata, fetching a list of most recently used objects, and building SOQL and SOSL queries.

SDK Manager Objects and Template Apps

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 the following specialized manager objects.
SalesforceSDKManager
For the most basic apps. These apps don't use any 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 any native app with forcedroid or forceios, the default template instantiates SmartSyncSDKManager. This manager includes SmartStore, SmartSync Data Framework, and all other Mobile SDK libraries. In this snippet from MainApplication.java, you can see where the Android template initiates its SDK manager:
public class MainApplication extends Application {
    @Override public void onCreate() { 
        super.onCreate(); 
        SmartSyncSDKManager.initNative(getApplicationContext(), 
            new NativeKeyImpl(), MainActivity.class);
        ...

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. Your app configures a sync operation and then 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 predetermine when the device has connectivity.

You can define a sync configuration either in code or in a JSON configuration file. In either case, at runtime Mobile SDK returns your configuration to you as a sync state object. You can later use the ID of this object to rerun the sync operation. If you name a sync operation, you can also rerun that sync by specifying its name.

Sync Configuration Settings

Whether you’re syncing up from SmartStore to Salesforce, or down to SmartStore from Salesforce, you provide sync configuration metadata. At the most basic level, you provide
Store
The global or user store that contains the soup you’re using. 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 uses a default value—which may or may not be to your liking.

A couple of other parameters can save you coding time. 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 callback logic that Mobile SDK runs after the sync operation ends. You might 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:
    • fieldlist—Comma-separated list of soup field names that you’re sending to the server. For syncing up, you can use this key instead of the target field list. However, be careful that you don’t try to update read-only fields on the server.
    • 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) Map with one or both of the following keys and their described values:
    • 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 in combination with your target’s createFieldList. Be 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

Native Android and iOS apps specify the store parameter with a reference to the intended store object. Hybrid and React Native apps, on the other hand, support an optional storeConfig parameter. This parameter applies to sync up and sync down operations. You can set it either of two ways.
As a map:
storeConfig (optional)—A map that defines two keys:
  • isGlobalStore—(Optional) Boolean indicating whether you’re using a global or user store. Default value is false.
  • storeName—(Optional) Name of the store. Default value is the name of the default store.
As a Boolean:
You can also pass a simple Boolean value instead of the storeConfig structure. In that case, Mobile SDK interprets the argument as the isGlobalStore value.

If you omit the store parameter, SmartSync Data Framework assumes that you’re using 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 6.0 introduced two new 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 exclusively 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 6.0 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 one fell swoop
  • Rerun a sync operation
  • Get the status of an in-progress sync operation
  • Check 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

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:
    - (SFSyncState *)createSyncDown:(SFSyncDownTarget *)target 
        options:(SFSyncOptions *)options 
        soupName:(NSString *)soupName 
        syncName:(NSString *)syncName;
    
    Or, you can create and run an unnamed sync down operation without options—but be advised that it overwrites any local changes:
    - (SFSyncState*) syncDownWithTarget:(SFSyncDownTarget*)target
                               soupName:(NSString*)soupName
                            updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock; 
    For more control, you can create and run an unnamed sync down configuration that uses your options:
    - (SFSyncState*) syncDownWithTarget:(SFSyncDownTarget*)target
                                options:(SFSyncOptions*)options
                               soupName:(NSString*)soupName
                            updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock; 
    Or you can create, name, and run a sync down configuration:
    - (SFSyncState*) syncDownWithTarget:(SFSyncDownTarget*)target
                                options:(SFSyncOptions*)options
                               soupName:(NSString*)soupName
                               syncName:(NSString* __nullable)syncName
                            updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock;

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. You can take advantage of this target with the following SFSmartSyncSyncManager method:

- (SFSyncState*) syncUpWithOptions:(SFSyncOptions*)options 
                          soupName:(NSString*)soupName 
                       updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock;
If you have a customized sync up target, you can use it by calling this SFSmartSyncSyncManager method:
- (SFSyncState*) syncUpWithTarget:(SFSyncUpTarget*)target
                          options:(SFSyncOptions*)options
                         soupName:(NSString*)soupName
                      updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock;
Or, you can create, name, and run a sync operation:
- (SFSyncState*) syncUpWithTarget:(SFSyncUpTarget*)target
                          options:(SFSyncOptions*)options
                         soupName:(NSString*)soupName
                         syncName:(NSString*)syncName
                      updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock;

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:

- (nullable SFSyncState*) reSyncByName:(NSString*)syncName 
                           updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock;
If you cached the returned SFSyncState.syncId value from an earlier sync operation, you can rerun that sync using reSync:updateBlock::
- (SFSyncState*) reSync:(NSNumber*)syncId 
            updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock;

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 iOS, this sample defines a ContactSObjectData class that represents a Salesforce Contact record as an Objective-C object. The sample also defines several classes that support the ContactSObjectData class:
  • ContactSObjectDataSpec
  • SObjectData
  • SObjectDataSpec
  • SObjectDataFieldSpec
  • SObjectDataManager
To sync Contact data down to the SmartStore soup, the refreshRemoteData method of SObjectDataManager loads the following named sync operation from the /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"}
    }
  ]
}
This syncDownContacts configuration builds a SOQL string that queries information from the Contact object. The refreshRemoteData: method of SFSmartSyncSyncManager calls the reSyncByName:updateBlock: method with the name of the sync down configuration (kSyncDownName). This method also requires an update block that is called when the sync operation has either succeeded or failed. Here, the update block calls the refreshLocalData: method after the first sync down operation, passing in the completionBlock argument from its own caller.
- (void)refreshRemoteData:(void (^)(void))completionBlock {
    __weak SObjectDataManager *weakSelf = self;
    // See usersyncs.json
    [self.syncMgr reSyncByName:kSyncDownName updateBlock:^(SFSyncState *sync) {
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if ([sync isDone] || [sync hasFailed]) {
            [strongSelf refreshLocalData:completionBlock];
        }
    }];
}

If the sync down operation succeeds—that is, if the isDone method of SFSyncState returns YES—the specified soup receives the server data. The update block then calls the refreshLocalData: method. This method retrieves the data from the soup, updates the UI to reflect any changes, and then runs the completion block passed in from refreshRemoteData:.

- (void)refreshLocalData:(void (^)(void))completionBlock {
    SFQuerySpec *sobjectsQuerySpec = 
        [SFQuerySpec newAllQuerySpec:self.dataSpec.soupName 
                       withOrderPath:self.dataSpec.orderByFieldName 
                           withOrder:kSFSoupQuerySortOrderAscending 
                        withPageSize:kMaxQueryPageSize];
    NSError *queryError = nil;
    NSArray *queryResults = 
        [self.store queryWithQuerySpec:sobjectsQuerySpec 
                             pageIndex:0 
                                 error:&queryError];
    [SFSDKLogger log:[self class] 
               level:DDLogLevelDebug 
              format:@"Got local query results.  Populating data rows."];
    if (queryError) {
        [SFSDKLogger log:[self class] 
                   level:DDLogLevelError 
                  format:@"Error retrieving '%@' data from SmartStore: %@", 
                         self.dataSpec.objectType, 
                         [queryError localizedDescription]];
        return;
    }
    
    self.fullDataRowList = [self populateDataRows:queryResults];
    [SFSDKLogger log:[self class] 
               level:DDLogLevelDebug 
              format:@"Finished generating data rows.  Number of rows: %d.  Refreshing view.", 
                     [self.fullDataRowList count]];
    self.dataRows = [self.fullDataRowList copy];
    if (completionBlock) completionBlock();
}

At sync up time, you send the syncUp:withOptions:soupName:updateBlock: message to SFSmartSyncSyncManager with the same arguments used for syncing down: list of fields, name of source SmartStore soup, and an update block. The only coding difference is that you can format the list of affected fields as an instance of SFSyncOptions instead of SFSyncTarget. Here’s how the SmartSyncExplorer sample sends the sync up message:

- (void)updateRemoteData:
    (SFSyncSyncManagerUpdateBlock)completionBlock {
    
    SFSyncOptions *syncOptions = 
        [SFSyncOptions 
            newSyncOptionsForSyncUp:self.dataSpec.fieldNames  
                          mergeMode:SFSyncStateMergeModeLeaveIfChanged]];
    [self.syncMgr syncUpWithOptions:syncOptions 
        soupName:self.dataSpec.soupName 
        updateBlock:^(SFSyncState* sync) {
            if ([sync isDone] || [sync hasFailed]) {
                completionBlock(sync);
            }
        }
    ];
}

If the update block provided here determines that the sync operation has finished, it calls the completion block that’s passed into updateRemoteData. A user initiates a syncing operation by tapping a button. Therefore, to see the definition of the completion block, look at the syncUpDown button handler in ContactListViewController.m. The handler calls updateRemoteData with the following block.

[self.dataMgr updateRemoteData:^(SFSyncState *syncProgressDetails) 
{
    dispatch_async(dispatch_get_main_queue(), ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        strongSelf.navigationItem.rightBarButtonItem.enabled = YES;

        // When the sync failed it means not everything could be synced up
	 // it doesn't necessarily mean nothing could be synced up
	 // Therefore we refresh regardless of success status
	 [strongSelf.dataMgr refreshLocalData:completionBlock];
	 [strongSelf.dataMgr refreshRemoteData:completionBlock]; 

        // We’ll again call refreshLocalData when completing

	 // Letting the user know whether the sync succeeded
	 if ([syncProgressDetails isDone]) {
	     [strongSelf showToast:@"Sync complete!"];
	 } else if ([syncProgressDetails hasFailed]) {
	     [strongSelf showToast:@"Sync failed."];
	 }
    });
}];

If the sync up operation succeeded, this block first refreshes the display on the device, along with a “Sync complete!” confirmation toast. Regardless of the status of the sync operation, this method refreshes local and remote data. This step covers partial successes and completions.

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