Syncing Down
To download sObjects from the server to your local Mobile Sync soup, use the appropriate “sync down” method.
For sync down methods, you define a target that provides the list of sObjects to be downloaded. To provide an explicit list, use JSONObject on Android, or NSDictionary on iOS. However, you can also define the target with a query string. The sync target class provides factory methods for creating target objects from a SOQL, SOSL, or MRU query.
You also specify the name of the SmartStore soup that receives the downloaded data. This soup is required to have an indexed string field named __local__. Mobile SDK reports the progress of the sync operation through the callback method or update block that you provide.
Merge Modes
- Overwrite modified local records and lose all local changes. Set the options parameter to the following value:
- Preserve all local changes and locally modified records. Set the options parameter to the following value:
iOS Sync Manager Methods
- Swift Class Name
-
1SyncManager - Objective-C Class Name
-
1SFMobileSyncSyncManager
- To create a sync down operation without running it:
-
- Swift
-
1var syncState = syncManager.createSyncDown(target: target, options: options, 2 soupName: CONTACTS_SOUP, syncName: syncState.syncName) - Objective-C
-
1- (SFSyncState *)createSyncDown:(SFSyncDownTarget *)target 2 options:(SFSyncOptions *)options 3 soupName:(NSString *)soupName 4 syncName:(NSString *)syncName;
- To create and run a sync down operation that overwrites any local changes:
-
- Swift
-
1func syncDown(target: SFSyncDownTarget, soupName: String) -> SyncState - Objective-C
-
1- (SFSyncState*) syncDownWithTarget:(SFSyncDownTarget*)target 2 soupName:(NSString*)soupName 3 updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock;
- To create and run an unnamed sync down operation:
-
- Swift
-
1var syncState = syncManager.syncDown(target: target, soupName: CONTACTS_SOUP, onUpdate:updateFunc) - Objective-C
-
1- (SFSyncState*) syncDownWithTarget:(SFSyncDownTarget*)target 2 options:(SFSyncOptions*)options 3 soupName:(NSString*)soupName 4 updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock;
- To create and run a named sync down operation:
-
- Swift
-
1var syncState = try syncManager.syncDown(target: target, options: options, 2 soupName: CONTACTS_SOUP, syncName: syncState.syncName, onUpdate:updateFunc) - Objective-C
-
1- (SFSyncState*) syncDownWithTarget:(SFSyncDownTarget*)target 2 options:(SFSyncOptions*)options 3 soupName:(NSString*)soupName 4 syncName:(NSString* __nullable)syncName 5 updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock 6 error:(NSError**)error;
- To run a named sync down operation:
-
- Swift
-
1var syncState = 2 try syncManager.reSync(named: syncState.syncName, onUpdate: updateFunc) - Objective-C
-
1- (nullable SFSyncState*) reSyncByName:(NSString*)syncName 2 updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock 3 error:(NSError**)error;
- To rerun a previous sync operation using its sync ID:
-
- Swift
-
1var syncState = 2 try syncManager.reSync(id: syncState.syncId, onUpdate: updateFunc) - Objective-C
-
1- (nullable SFSyncState*) reSync:(NSNumber*)syncId 2 updateBlock:(SFSyncSyncManagerUpdateBlock)updateBlock 3 error:(NSError**)error;
Android SyncManager Methods
- To create a sync down operation without running it:
-
1public SyncState createSyncDown(SyncDownTarget target, 2 SyncOptions options, String soupName, String syncName) 3 throws JSONException; - To create and run a sync down operation that overwrites any local changes:
-
1public SyncState syncDown(SyncDownTarget target, String soupName, 2 SyncUpdateCallback callback) throws JSONException; - To create and run an unnamed sync down operation:
-
1public SyncState syncDown(SyncDownTarget target, SyncOptions options, 2 String soupName, SyncUpdateCallback callback) 3 throws JSONException; - To create and run a named sync down operation:
-
1public SyncState syncDown(SyncDownTarget target, SyncOptions options, 2 String soupName, String syncName, SyncUpdateCallback callback) 3 throws JSONException; - To run or rerun a named sync configuration:
-
1public SyncState reSync(String syncName, SyncUpdateCallback callback) 2 throws JSONException; - To rerun a previous sync operation using its sync ID:
-
1public SyncState reSync(long syncId, SyncUpdateCallback callback) 2 throws JSONException;
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}- Retrieves all raw data from the freshly updated soup.
- Transforms the soup’s data to ContactSObjectData objects and stores these objects in an internal array.
- Passes control to refreshRemoteData(_:onFailure:). The onSuccess argument passed to refreshRemoteData is the block passed in
from syncUpDown.
1func updateRemoteData(_ onSuccess: @escaping ([SObjectData]) -> Void, 2 onFailure:@escaping (NSError?, SyncState?) -> Void) -> Void { 3... 4 try strongSelf.refreshRemoteData({ (sobjs) in 5 onSuccess(sobjs) 6 }, onFailure: { (error,syncState) in 7 onFailure(error,syncState) 8 }) 9….
In refreshRemoteData, the app again calls reSync but with the syncDownContacts model—aliased as kSyncDownName—to update the soup. If sync down succeeds, refreshRemoteData “closes the circle” by executing the block that’s passed to it from syncUpDown via updateRemoteData.
1func refreshRemoteData(_ completion: @escaping ([SObjectData]) -> Void,onFailure: @escaping (NSError?, SyncState) -> Void ) throws -> Void {
2
3 try self.syncMgr.reSync(named: kSyncDownName) { [weak self] (syncState) in
4 switch (syncState.status) {
5 case .done:
6 do {
7 let objects = try self?.queryLocalData()
8 self?.populateDataRows(objects)
9 completion(self?.fullDataRowList ?? [])
10 } catch {
11 MobileSyncLogger.e(SObjectDataManager.self,
12 message: "Resync \(syncState.syncName) failed \(error)" )
13 }
14 break
15 case .failed:
16 MobileSyncLogger.e(SObjectDataManager.self,
17 message: "Resync \(syncState.syncName) failed" )
18 onFailure(nil,syncState)
19 default:
20 break
21 }
22 }
23}- 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:
The native MobileSyncExplorer sample app demonstrates how to use Mobile Sync 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.
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 "maxBatchSize":500},
10 "options": {"mergeMode":"OVERWRITE"}
11 },
12 {
13 "syncName": "syncUpContacts",
14 "syncType": "syncUp",
15 "soupName": "contacts",
16 "target": {"createFieldlist":["FirstName", "LastName", "Title", "MobilePhone",
17 "Email", "Department", "HomePhone"]},
18 "options": {"fieldlist":["Id", "FirstName", "LastName", "Title", "MobilePhone",
19 "Email", "Department", "HomePhone"], "mergeMode":"LEAVE_IF_CHANGED"}
20 }
21 ]
22}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.
1public synchronized void syncDown() {
2 try {
3 syncMgr.reSync(SYNC_DOWN_NAME /* see usersyncs.json */, new SyncUpdateCallback() {
4 @Override
5 public void onUpdate(SyncState sync) {
6 if (Status.DONE.equals(sync.getStatus())) {
7 fireLoadCompleteIntent();
8 }
9 }
10 });
11 } catch (JSONException e) {
12 Log.e(TAG, "JSONException occurred while parsing", e);
13 } catch (MobileSyncException e) {
14 Log.e(TAG, "MobileSyncException occurred while attempting to sync down", e);
15 }
16
}