Syncing Up
To apply local changes on the server, use one of the “sync up” methods. These methods update the server with data from the given SmartStore soup. They look for created, updated, or deleted records in the soup, and then replicate those changes on the server. The options argument specifies a list of fields to be updated. In Mobile SDK5.1 and later, you can override this field list by initializing the sync manager object with separate field lists for create and update operations. See Handling Field Lists in Create and Update Operations .
Locally created objects must include an “attributes” field that contains a “type” field that specifies the sObject type. For example, for an account named Acme, use: {Id:”local_x”, Name: Acme, attributes: {type:”Account”}}.
iOS: SFMobileSyncSyncManager Methods
- You can create a named sync without running it.
- Swift
-
1var syncState = syncManager.createSyncUp(target: target, options: options, 2 soupName: CONTACTS_SOUP, syncName: syncState.syncName) - Objective-C
-
1- (SFSyncState *)createSyncUp:(SFSyncUpTarget *)target 2 options:(SFSyncOptions *)options 3 soupName:(NSString *)soupName 4 syncName:(NSString *)syncName;
- You can create and run a sync with just options that uses the default target.
- Swift
-
1var syncState = syncManager.syncUp(options: options, soupName: CONTACTS_SOUP, 2 onUpdate: updateFunc) - Objective-C
-
1- (SFSyncState*) syncUpWithOptions:(SFSyncOptions*)options 2 soupName:(NSString*)soupName 3 updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock;
- You can create and run a sync based on a target that you configure in code.
- Swift
-
1var syncState = syncManager.syncUp(options: options, soupName: CONTACTS_SOUP, 2 onUpdate: updateFunc) - Objective-C
-
1- (SFSyncState*) syncUpWithTarget:(SFSyncUpTarget*)target 2 options:(SFSyncOptions*)options 3 soupName:(NSString*)soupName 4 updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock;
- Or, you can create and run a named sync. If you load a sync with the same name
from a configuration file, this sync overrides it.
- Swift
-
1var syncState = 2 try syncManager.syncUp(target: target, options: options, 3 soupName: CONTACTS_SOUP, syncName: syncState.syncName, 4 onUpdate: updateFunc) - Objective-C
-
1- (SFSyncState*) syncUpWithTarget:(SFSyncUpTarget*)target 2 options:(SFSyncOptions*)options 3 soupName:(NSString*)soupName 4 syncName:(NSString*)syncName 5 updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock 6 error:(NSError**)error;
- To run or rerun an existing named sync configuration:
- Swift
-
1open func reSync(named syncName: String, 2 onUpdate updateBlock: @escaping SyncUpdateBlock) throws -> SyncState - Objective-C
-
1- (nullable SFSyncState*) reSyncByName:(NSString*)syncName 2 updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock 3 error:(NSError**)error;
- Or, to rerun a previous sync operation by sync ID:
- Swift
-
1open func reSync(id syncId: NSNumber, 2 onUpdate updateBlock: @escaping SyncUpdateBlock) throws -> SyncState - Objective-C
-
1- (nullable SFSyncState*) reSync:(NSNumber*)syncId 2 updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock 3 error:(NSError**)error;
- To rerun a sync without getting progress updates, use this function from the Mobile Sync
Swift extension:
- Swift
-
1public func reSyncWithoutUpdates(named syncName: String, 2 _ completionBlock: @escaping (Result<SyncState, MobileSyncError>) -> Void) - Objective-C
- (Not available.)
- To sync up by external ID, see Syncing Up by External ID.
Android: SyncManager Methods
-
You can create a named sync up configuration without running it.
1public SyncState createSyncUp(SyncUpTarget target, 2 SyncOptions options, 3 String soupName, 4 String syncName) 5 throws JSONException; -
You can create and run an unnamed sync up configuration:
1public SyncState syncUp(SyncUpTarget target, 2 SyncOptions options, 3 String soupName, 4 SyncUpdateCallback callback) 5 throws JSONException; -
You can create and run a named sync up configuration:
1public SyncState syncUp(SyncUpTarget target, 2 SyncOptions options, 3 String soupName, 4 String syncName, 5 SyncUpdateCallback callback) 6 throws JSONException; -
To run or rerun an existing named sync configuration:
1public SyncState reSync(String syncName, SyncUpdateCallback callback) 2 throws JSONException; -
Or, to rerun a previous sync operation by sync ID:
1public SyncState reSync(long syncId, SyncUpdateCallback callback) 2 throws JSONException; - To sync up by external ID, see Syncing Up by External ID.
Specifying Merge Modes
- Overwrite server records even if they've changed since they were synced down
to that client. When you call the syncUp method:
-
iOS: Set the options parameter to
- Swift
-
1SyncOptions.newSyncOptions(forSyncUp: ["Name"], mergeMode: SyncMergeMode.overwrite) - Objective-C
- [SFSyncOptions newSyncOptionsForSyncUp: ["Name"], mergeMode:SFSyncStateMergeModeOverwrite]
- Android: Set the options parameter to SSyncOptions.optionsForSyncUp(fieldlist, SyncState.MergeMode.OVERWRITE)
- Hybrid: Set the syncOptions parameter to {mergeMode:"OVERWRITE"}
-
iOS: Set the options parameter to
- If any server record has changed since it was synced down to that client,
leave it in its current state. The corresponding client record also remains
in its current state. When you call the syncUp() method:
-
iOS: Set the options parameter to
- Swift
-
1SyncOptions.newSyncOptions(forSyncUp: ["Name"], mergeMode: SyncMergeMode.leaveIfChanged) - Objective-C
- [SFSyncOptions newSyncOptionsForSyncUp:fieldlist mergeMode:SFSyncStateMergeModeLeaveIfChanged]
- Android: Set the options parameter to SyncOptions.optionsForSyncUp(fieldlist, SyncState.MergeMode.LEAVE_IF_CHANGED)
- Hybrid: Set the syncOptions parameter to {mergeMode:"LEAVE_IF_CHANGED"}
-
iOS: Set the options parameter to
If your local record includes the target’s modification date field, Mobile SDK detects changes by comparing it to the server record’s matching field. The default modification date field is lastModifiedDate. If your local records do not include the modification date field, the LEAVE_IF_CHANGED sync up operation reverts to an overwrite sync up.
Example
iOS:
- ContactSObjectDataSpec
- SObjectData
- SObjectDataSpec
- SObjectDataFieldSpec
- SObjectDataManager
1{
2 "syncs": [
3 {
4 "syncName": "syncDownContacts",
5 "syncType": "syncDown",
6 "soupName": "contacts",
7 "target": {"type":"soql", "query":"SELECT FirstName, LastName, Title,
8 MobilePhone, Email, Department, HomePhone FROM Contact LIMIT 10000"},
9 "options": {"mergeMode":"OVERWRITE"}
10 },
11 {
12 "syncName": "syncUpContacts",
13 "syncType": "syncUp",
14 "soupName": "contacts",
15 "target": {"createFieldlist":["FirstName", "LastName", "Title",
16 "MobilePhone", "Email", "Department", "HomePhone"]},
17 "options": {"fieldlist":["Id", "FirstName", "LastName", "Title",
18 "MobilePhone", "Email", "Department", "HomePhone"],
19 "mergeMode":"LEAVE_IF_CHANGED"}
20 }
21 ]
22}1func syncUpDown(){
2 let alert = self.showAlert("Syncing", message: "Syncing with Salesforce")
3 sObjectsDataManager.updateRemoteData({ [weak self] (sObjectsData) in
4 DispatchQueue.main.async {
5 alert.message = "Sync Complete!"
6 alert.dismiss(animated: true, completion: nil)
7 self?.refreshList()
8 }
9 }, onFailure: { [weak self] (error, syncState) in
10 alert.message = "Sync Failed!"
11 alert.dismiss(animated: true, completion: nil)
12 self?.refreshList()
13 })
14}1func updateRemoteData(_ onSuccess: @escaping ([SObjectData]) -> Void,
2 onFailure:@escaping (NSError?, SyncState) -> Void) -> Void {
3 do {
4 try self.syncMgr.reSync(named: kSyncUpName) { [weak self] (syncState) in
5 guard let strongSelf = self else {
6 return
7 }
8 switch (syncState.status) {
9 case .done:
10 do {
11 let objects = try strongSelf.queryLocalData()
12 strongSelf.populateDataRows(objects)
13 try strongSelf.refreshRemoteData({ (sobjs) in
14 onSuccess(sobjs)
15 }, onFailure: { (error,syncState) in
16 onFailure(error,syncState)
17 })
18 } catch let error as NSError {
19 MobileSyncLogger.e(SObjectDataManager.self,
20 message: "Error with Resync \(error)" )
21 onFailure(error,syncState)
22 }
23 break
24 case .failed:
25 MobileSyncLogger.e(SObjectDataManager.self,
26 message: "Resync \(syncState.syncName) failed" )
27 onFailure(nil,syncState)
28 break
29 default:
30 break
31 }
32 }
33 } catch {
34 onFailure(error as NSError, nil)
35 }
36}-
queryLocalData retrieves all raw data
from the freshly updated
soup.
1let objects = try strongSelf.queryLocalData() -
populateDataRows transforms the soup’s
data to ContactSObjectData objects and
stores these objects in an internal
array.
1strongSelf.populateDataRows(objects) - Control passes to refreshRemoteData(_:onFailure:). The refreshRemoteData method looks similar to
updateRemoteData with two exceptions:
- It performs a sync down instead of sync up.
- If sync down succeeds, it “closes the circle” by executing the block that’s been passed to it from syncUpDown via updateRemoteData.
- Sync up: It syncs soup changes up to the server by calling updateRemoteData on SObjectsDataManager. This step ensures that all allowable local and offline changes are merged into Salesforce.
- Sync down: After the soup records are merged with server data, it syncs server data down to the soup through a call to refreshRemoteData. This step ensures that the soup reflects changes originating on the server and also changes merged from sync up. Remember: The sync up merge mode determines which soup edits are allowed on the server.
- Finally, it updates its UI with the updated contact records from the soup.
Example
Android:
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 MobileSyncExplorer sample:
1public synchronized void syncUp() {
2 final SyncUpTarget target = new SyncUpTarget();
3 final SyncOptions options =
4 SyncOptions.optionsForSyncUp(Arrays.asList(ContactObject.CONTACT_FIELDS_SYNC_UP),
5 MergeMode.LEAVE_IF_CHANGED);
6 try {
7 syncMgr.syncUp(target, options, ContactListLoader.CONTACT_SOUP,
8 new SyncUpdateCallback() {
9 @Override
10 public void onUpdate(SyncState sync) {
11 if (Status.DONE.equals(sync.getStatus())) {
12 syncDown();
13 }
14 }
15 });
16 } catch (JSONException e) {
17 Log.e(TAG, "JSONException occurred while parsing", e);
18 } catch (MobileSyncException e) {
19 Log.e(TAG, "MobileSyncException occurred while attempting to sync up", e);
20 }
21}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.