Skip to main content

Synchronize Offline Changes with Mobile Sync

Learning Objectives

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

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

Using Mobile Sync to Synchronize Offline Changes

Mobile Sync is a Mobile SDK library that works with SmartStore to guarantee the integrity and consistency of Salesforce records. Mobile Sync 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 Mobile Sync 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, update block, or promise, depending on the platform. Responses include a sync state object that describe the sync operation’s settings and status.

Mobile Sync methods automate common network tasks such as fetching sObject metadata, fetching a list of most recently used objects, and issuing SOQL and SOSL queries. Your app can call sync manager methods at any time, but they obviously can succeed only when the host device is connected to the internet. It's up to your app to determine the device's connection status.

Configuring Apps to Use Mobile Sync

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. Apps initialize the MobileSyncSDKManager object during bootstrap initialization.

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 Mobile Sync.

MobileSyncSDKManager

For apps that use both SmartStore and Mobile Sync.

When you create a native app with forcedroid or forceios, your new app is automatically configured to use MobileSyncSDKManager. This manager imports SmartStore, Mobile Sync, and all other Mobile SDK libraries. 

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 your app 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 additional 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, Mobile Sync assumes a default value.

After a sync operation is complete, your app receives asynchronous notifications that allow it to perform follow-up tasks. These tasks might include sending event notifications, logging success, or using the data in other ways that enhance 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:
    • For SOQL queries: {type:"soql", query:"<soql_query>"}
      Downloads the sObjects returned by the given SOQL query
    • For SOSL queries: {type:"sosl", query:"<sosl_query>"}
      Downloads the sObjects returned by the given SOSL query
    • For MRU queries: {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) Map with the following key:
    • mergeMode—To control how Mobile Sync merges data to local soups, set this key to one of the following modes:
      Overwrite
      (Default) Overwrites records that have been modified. If you don’t specify a merge mode, Mobile Sync overwrites local data.
      Leave if changed
      Preserves records that have been modified. This mode requires extra round-trips to the server. so use it with care.

Sync Up Target and Options

  • target—Map with one or both of the following keys:
    • 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 more of the following keys:
    • 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 Mobile Sync merges local data to the Salesforce org, set this key to one of the following modes:
      Overwrite
      (Default setting) Overwrites server 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 assigns the value to the isGlobalStore key.

If you omit the store parameter, Mobile Sync 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 these 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 use configuration files, we use them extensively in this trail.

To rerun a sync, you use the platform's reSync method. Resync methods use one of these identifiers to look up your configuration:

  • Sync ID—Every sync method returns a runtime ID as part of a state object. You can pass this ID to reSync to rerun a sync you’ve already used in the current session.
  • Sync name (Optional)—You can name a sync configuration in memory when you first run it, or you can import the name and its sync configuration from an external JSON file.

Sync Names

Sync operations can be complicated to configure, and they tend to be reused often. Sync names give you the convenience of rerunning your syncs without recoding them. For example, you can use sync names to

  • Configure sync operations in an external JSON file rather than in code
  • Rerun a previously used sync operation without reconfiguring it
  • 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 APIs or in sync configuration files. Mobile SDK supports named syncs on all platforms for all app types.

Sync Configuration Files

With sync configuration files, you can write static sync up and sync down definitions once in a JSON file, and then import them in all versions of your apps. Mobile SDK supports sync configuration files in native and hybrid apps. 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 and React Native apps: / in the Resources bundle
  • Hybrid apps: /www in the Resources bundle

Android:

  • Native and React 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

The following 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 Mobile Sync with Briefcase

A briefcase is a set of queries that together select a cohesive collection of related records, optimized for what you want. You can use briefcases to select records for data priming in advance of going offline, and for other general data loading purposes.

Sync Configuration

In the Mobile Sync Explorer Template App, you can update the user sync’s JSON file to configure your sync down and sync up operations. 

For the "target" property, specify the following values.

Target Properties

"type":"briefcase"

"infos": array of <BriefcaseObjectInfo> items

An array of objects that describe the specific sObjects and fields to retrieve, along with the soup to place them in.

BriefcaseObjectInfo Properties

"soupName": <string>

Name of the soup or table to store records of this object type into during sync.

"sobjectType": <string>

Name of a Salesforce object to sync.

"fieldlist": array of <string>

List of fields to sync for this object.

"idFieldName": <string>

(Optional) Name of a custom ID field. If you provide "idFieldName", Mobile Sync uses the field with the given name to get the ID of the record. For example, if you specify "idFieldName":"AcmeId", Mobile Sync obtains the record’s ID from the AcmeId field instead of the default Id field.

"modificationDateFieldName": <string>

(Optional) Name of the field containing the last modification date for the record. If you provide modificationDateFieldName, Mobile Sync uses the field with this name to compute the maxTimestamp value that startFetch uses to resync the records. Default field name is lastModifiedDate.

Required: “soupName”, “sobjectType”, “fieldlist”

Syncing Down

Note

If your org uses Briefcases for your mobile users, the Briefcase sync down target was introduced in Mobile SDK 10.1.

Let’s take a look at an example of a Briefcase file configured for syncing down. 

{
  "syncs": [
    {
      "syncName": "syncDownContacts", 
      "syncType": "syncDown",
      "soupName": "contacts",
      "target": {
        "type": "briefcase",      
        "infos": [
          {
            "sobjectType": "Account",
            "fieldlist": [
              "Name",
              "Description"
            ],
            "soupName": "accounts"
          },
          {
            "sobjectType": "Contact",
            "fieldlist": [
              “FistName”, "LastName", “Title”, “Email”
            ],
            "soupName": "contacts"
          }
        ]
      },
      "options": {
        "mergeMode": "OVERWRITE"
      }
    }
  ]
}

To sync down, define the “syncType” as “syncDown”. This activates the sync down operation, which syncs data from the server onto the client. You can name your sync down operation next to “syncName”. In this case, it’s been named “syncDownContacts”

Next to “soupName”, define the table or soup you want to update when the sync down operation takes place. In this case, the soup to be updated is called “contacts”.

Beneath “target”, define the target type as “briefcase”. Beneath the “infos” JSON object, you can define the entities you want to sync, in this case: “Account” and “Contact”. For each entity, denote the fields you want to be synced in the “fieldlist”

For each entity, denote the corresponding table you want to get updated on the client next to “soupName”.  

Under “options”, you can define the “mergeMode” option, which in this case is set to “OVERWRITE”. Should two users attempt to update the same record, this option gives overwrite priority to the last user who updated it.

Syncing Up

Now, let’s take a look at the configuration for syncing up.

{
“syncName: “syncUpContacts”,
“syncType”: “syncUp”,
“soupÑame”: “contacts”,
“target”: {
	“createFieldlist”: [
		“FirstName”, “LastName”, “Title”, “Email”
	]
},
“options”: {
	“fieldlist”: [
		“FirstName”, “LastName”, “Title”, “Email”
	],
	“mergeMode”: “LEAVE_IF_CHANGED”
}
}

To use the sync up operation, configure the table you want via “soupName”, which in this case called “contacts”. Use “target” and “options” to denote the fields you want to sync. The “mergeMode”, in this case, is set to “LEAVE_IF_CHANGED”. If any server record has changed since it was synced down to that client, this leaves both server and client records in their current state.

Using Mobile Sync in Native iOS Apps

Syncing Down

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

You can create a sync down configuration without running it:

var syncState = syncManager.createSyncDown(target: target, options: options, 
    soupName: CONTACTS_SOUP, syncName: syncState.syncName)

Or, you can create and run an unnamed sync down operation without options—but be advised that it overwrites any local changes:

var syncState = syncManager.syncDown(target: target, soupName: CONTACTS_SOUP, onUpdate:updateFunc)

For more control, you can create and run an unnamed sync down configuration that uses your options:

var syncState = syncManager.syncDown(target: target, options: options, 
    soupName: CONTACTS_SOUP, onUpdate:updateFunc)

Or you can create, name, and run a sync down configuration:

var syncState = try syncManager.syncDown(target: target, options: options, 
    soupName: CONTACTS_SOUP, syncName: syncState.syncName, onUpdate:updateFunc)

The SyncTarget 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.
Note

The syncDown(target:soupName:onUpdate:) method, which doesn’t take an options parameter, 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, Mobile Sync 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:

let syncState = syncManager.createSyncUp(target: target, options: options, 
    soupName: CONTACTS_SOUP, syncName: syncState.syncName)

You can use the default target with the following MobileSyncManager method:

let syncState = syncManager.syncUp(options: options, soupName: CONTACTS_SOUP, 
    onUpdate: updateFunc)

If you have a customized sync up target, you can run it by calling this method:

let syncState = syncManager.syncUp(target: target, options: options, 
    soupName: CONTACTS_SOUP, onUpdate: updateFunc)

Or, you can create, name, and run a sync operation:

let syncState = 
    try syncManager.syncUp(target: target, options: options, 
        soupName: CONTACTS_SOUP, syncName: syncState.syncName, 
        onUpdate: updateFunc)

Resync

Important yet sometimes overlooked, 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:

var syncState = 
    try syncManager.reSync(named: syncState.syncName, onUpdate: updateFunc)

If you cached the SFSyncState.syncId value returned from an earlier unnamed sync operation, you can use it to rerun that sync:

var syncState = 
    try syncManager.reSync(id: syncState.syncId, onUpdate: updateFunc)

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

Example

Native Swift apps created by forceios use a template that demonstrates basic SmartStore and Mobile Sync features. This template uses Combine publishers for SmartStore and Mobile Sync to simplify its code. These publishers are available in Mobile SDK as extensions to the SmartStore and MobileSync classes. Let's take a look at this implementation as realized in the iOSNativeSwiftTemplate project

First, create an app for exploration.

  1. In a Terminal window, create an app with forceios.
    • Use native_swift for the app type.
    • For convenience, name the app offlineSwift.
  2. When forceios finishes, open the new workspace in Xcode.

In Xcode, check out the setup and configuration for offline features.

  1. In Project Navigator (⌘1), expand offlineSwift > offlineSwift > Supporting Files. Here, you find the userstore.json and usersyncs.jsonfiles. If you open them in the Editor, you can see that they define basic configurations for accessing and syncing Account records.
  2. The template adds userstore.json and usersyncs.jsonto the app bundle in the Project Build Phases settings. 
    1. In the Xcode Project navigator, select the project node.
    2. In the Editor window, select Build Phases.
    3. Expand Copy Bundle Resources. Note that userstore.json and usersyncs.json are listed, which means that they are added to the app bundle when the app is built.

Now, let's examine the coding.

  1. In Project Navigator, expand offlineSwift > offlineSwift > Classes > SwiftUI.
  2. In SceneDelegate.swift, the template adds a setupRootViewController()function that loads the offline configurations into memory. This function is intentionally called only once per user session. It's important to remember that you can't call these setup methods more than once per session, or use them to load a different set of configurations. 
    // Set up store based on config userstore.json
    MobileSyncSDKManager.shared.setupUserStoreFromDefaultConfig()
    // Set up syncs based on config usersyncs.json
    MobileSyncSDKManager.shared.setupUserSyncsFromDefaultConfig()
  3. In Project Navigator, expand offlineSwift > offlineSwift > Classes > Models.
  4. In AccountsListModel.swift, notice the first line in the AccountsListModel class. This line defines an empty published array of Account objects. SwiftUI objects in the app  subscribe to AccountsListModelto receive updates when the published array changes.
    /**
    ViewModel for Account List
    */
    class AccountsListModel: ObservableObject {
      @Published var accounts: [Account] = []
      ...
  5. Find the fetchAccounts()function.
    func fetchAccounts(){
      syncTaskCancellable = syncManager?.publisher(for: "syncDownAccounts")
        .receive(on: RunLoop.main)
        .sink(receiveCompletion: { _ in }, receiveValue: { _ in
          self.loadFromSmartStore()
      })
      self.loadFromSmartStore()
    }
    Here, the Mobile Sync publisher takes a named sync operation--"syncDownAccounts", defined in usersyncs.json--and resyncs it. The publisher then copies the returned data into soups in the default SmartStore store. WhenfetchAccounts() receives the response from the publisher, it calls loadFromSmartStore() to asynchronously copy the refreshed data from SmartStore into the published accounts array. While waiting for the response to arrive, fetchAccounts() also calls loadFromSmartStore() to make sure that the accounts array is up-to-date with any local changes. Let's see how loadFromSmartStore() performs these updates.
  6. The loadFromSmartStore()function uses the SmartStore publisher to issue a Smart Sql query and receive the asynchronous response. If the response indicates success, the function publishes the resulting data set to the caller. Here, the caller copies the parsed response into the accounts array on the AccountsListModelobject.  
    import MobileSync
    ...
    private func loadFromSmartStore() {
      storeTaskCancellable = self.store?.publisher(for: "select {Account:Name}, {Account:Industry}, {Account:Id} from {Account}")
      .receive(on: RunLoop.main)
      .tryMap {
        $0.map { (row) -> Account in
          let r = row as! [String?]
          return Account(id: r[2] ?? "", name: r[0] ?? "", industry: r[1] ?? "Unknown Industry" )
        }
      }
      .catch { error -> Just<[Account]> in
        print(error)
        return Just([Account]())
      }
      .assign(to: \AccountsListModel.accounts, on:self)
    } 

When the accounts array is populated, the app can proceed with its primary logic--which, in this case, is to: 

  • Display the queried accounts and their details
  • For any account the customer selects, query and then display the contacts and their details

Using Mobile Sync in Native Android Apps

Syncing Down

To download sObjects from the server to your local Mobile Sync 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, 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 also use these methods to run a named sync for the first time. In subsequent runs, the resync methods sync only records that have been created or updated since the previous 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

For Android, the native MobileSyncExplorer sample app demonstrates how to use named syncs and sync configuration files with Contact records. 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.

This app stores the userstore.json and usersyncs.json files in the /res/raw project folder. Here's userstore.json:

{
  "soups": [
    {
      "soupName": "contacts",
      "indexes": [
        { "path": "Id", "type": "string"},
        { "path": "FirstName", "type": "string"},
        { "path": "LastName", "type": "string"},
        { "path": "__local__", "type": "string"},
        { "path": "__locally_created__", "type": "string"},
        { "path": "__locally_updated__", "type": "string"},
        { "path": "__locally_deleted__", "type": "string"},
        { "path": "__sync_id__", "type": "integer"}
      ]
    }
  ]
}

And usersyncs.json:

{
  "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 sample app loads these files into memory in its ContactListLoader constructor. 

public ContactListLoader(Context context, UserAccount account) {
  super(context);
  MobileSyncSDKManager sdkManager = MobileSyncSDKManager.getInstance();
  smartStore = sdkManager.getSmartStore(account);
  syncMgr = SyncManager.getInstance(account);
  // Setup schema if needed
  sdkManager.setupUserStoreFromDefaultConfig();
  // Setup syncs if needed
  sdkManager.setupUserSyncsFromDefaultConfig();
}

In the following method from the ContactListLoader class, the local syncDown() method runs reSync() with the syncDownContacts configuration:

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 (MobileSyncException e) {
        Log.e(TAG, "MobileSyncException 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 fires an intent that reloads the data in the Contact list.

Likewise, the local syncUp() method of ContactListLoader runs reSync() with the syncUpContacts configuration:

public synchronized void syncUp() {
       
    try {
        
        syncMgr.reSync(SYNC_UP_NAME /* see usersyncs.json */, new SyncUpdateCallback() { 
            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 (MobileSyncException e) {
           
        Log.e(TAG, "MobileSyncException occurred while attempting to sync up", e);
    
    }

 }

When the asynchronous response returns Status.DONE, the SyncUpdateCallback implementation here takes the extra step of calling syncDown(). This recommended step guarantees that the SmartStore soup remains up-to-date with any recent changes made to Contacts on the server.

Using Mobile Sync in Hybrid Apps

Mobile Sync provides two different approaches for hybrid apps.

  • com.salesforce.plugin.mobilesync — This Cordova plugin provides JavaScript access to the native “sync-down” and “sync-up” functionality of Mobile Sync. 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.
  • mobilesync.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 mobilesync.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 mobilesync.js. The mobilesync.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 mobilesync.js.
  • If you’ve changed objects in the soup, always call syncUp() before calling syncDown().

Hybrid apps created with forcehybrid automatically include the Mobile Sync plugin.

Syncing Down

Here's the Mobile Sync plugin’s syncDown() method.

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

If you’ve defined a sync name for this configuration, you can provide it. You define sync names in a usersyncs.json configuration file that you include at the top level of your web app.

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

  • {mergeMode:Force.MERGE_MODE_DOWNLOAD.OVERWRITE} (default)
  • {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:
    1. NEW
    2. RUNNING
    3. DONE
    4. 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 Mobile Sync plugin’s syncUp() method replicates created, deleted, or updated records from a SmartStore soup to the Salesforce server.

cordova.require("com.salesforce.plugin.mobilesync").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.
  • syncName—If you’ve defined a sync name for this configuration, you can provide it. You define sync names in a usersyncs.json configuration file that you include at the top level of your web app.
  • options—Provide a map with the following key:
    1. 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.

Resync

As in native apps, the reSync method offers superior flexibility and performance, and promotes ease of coding. If the sync name you provide hasn't previously been synced, reSync() does a complete sync. Otherwise, it performs an incremental sync of new, changed, or deleted records only.

cordova.require("com.salesforce.plugin.mobilesync").reSync([storeConfig], syncIdOrName, successCB, errorCB)

You provide configuration settings for the operation, including

  • syncIdOrName—The ID for this sync operation, or a sync name from your usersyncs.json file.
  • successCb, errorCb—Success and error callback functions.

Example

This example uses the named syncs defined in the following usersyncs.json file. You can find the full source code in the SalesforceMobileSDK-Shared GitHub repo.

{
  "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"}
    }
  ]
}

In the following examples, both sync up and sync down operations call reSync() on the Mobile Sync Cordova plug-in, passing in a sync name. All sync operations 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 reflect the latest changes on the server. 

In its current form, the success callback for syncDown() resets the UI and its content.

 handleSyncUpdate: function(sync) {
  // Called after sync operations succeed 
  if (sync.type === "syncDown") {
    // Reset UI buttons, then search (fetch) from SmartStore
    this.syncInFlight = false;
    $(".sync").disabled = false;
    this.search();
  }
  if (sync.type === "syncUp") {
      this.syncDown();
    }
  }
 },
 syncDown: function() {
   cordova.require("com.salesforce.plugin.mobilesync").
     reSync("syncDownContacts" /* see usersyncs.json */, 
     this.handleSyncUpdate.bind(this));
 },
 syncUp: function() {
   cordova.require("com.salesforce.plugin.mobilesync").
     reSync("syncUpContacts" /* see usersyncs.json */, 
     this.handleSyncUpdate.bind(this));
 }

Using Mobile Sync in React Native Apps

In React Native, Mobile Sync provides its functionality through a mobilesync module. In your JavaScript files, you can import this module and its dependencies from the react-native-force library as follows:

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

Apps created with forcereact include a similar import statement, but without smartstore or mobilesync. 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:mobilesync.MERGE_MODE.OVERWRITE}
    If you don’t define the mergeMode key, Mobile Sync uses this mode as the default.
  • To preserve, rather than overwrite, records that have been modified:
    {mergeMode:mobilesync.MERGE_MODE.LEAVE_IF_CHANGED}

Syncing Down

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

mobilesync.syncDown
    ([storeConfig,] target, soupName, options, [syncName,] success, error);

If existing records in the soup have the same ID as records returned by the query, Mobile Sync 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 actions for a success callback, it’s a good idea to implement it to store the sync ID. You can use this value later, if necessary, in a resync operation.Here’s a sync down example.

const syncDown = forceUtil.promiserNoRejection(mobilesync.syncDown);
...
const fieldlist = ["Id", "FirstName", "LastName", 
    "Title", "Email", "MobilePhone","Department",
    "HomePhone", "LastModifiedDate"];
const target = {type:"soql", 
    query:`SELECT ${fieldlist.join(",")} 
           FROM Contact 
           LIMIT 10000`};
syncDown(false,
    target,
    "contacts",
    {mergeMode:mobilesync.MERGE_MODE.OVERWRITE},
    syncName,
    (sync) => {/* Do something meaningful or omit this member */},
    (error) => {/* Do something meaningful or omit this member */}
 );

Syncing Up

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

mobilesync.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 syncUp = forceUtil.promiserNoRejection(mobilesync.syncUp);
...
const fieldlist = ["FirstName", "LastName", "Title", "Email", 
  "MobilePhone","Department"];
syncUp(false,
  {},
  "contacts",
  {mergeMode:mobilesync.MERGE_MODE.OVERWRITE, fieldlist},
  (sync) => {/* Do something meaningful or omit this callback */},
  (error) => {/* Do something meaningful or omit this callback */}
);

Resync

As in native apps, the reSync() method offers superior flexibility and performance, and promotes ease of coding. If the sync name you provide hasn't previously been synced, reSync() does a complete sync. Otherwise, it performs an incremental sync of created, updated, or deleted records only.

mobilesync.reSync
   ([storeConfig,] syncIdOrName, successCB, errorCB); 

You provide configuration settings for the operation, including

  • syncIdOrName—The ID for this sync operation, or a sync name from your usersyncs.json file.
  • successCb, errorCb—Success and error callback functions.

Resources

Share your Trailhead feedback over on Salesforce Help.

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

Learn More Continue to Share Feedback