Sync offline edits

Your users can edit offline using a services pattern and later sync their edits back to a feature service when connected. Syncing offline edits requires that you've created a geodatabase using a sync-enabled feature service from ArcGIS Online or ArcGIS Enterprise, as described in Create an offline map. After users have made edits and are ready to sync their local copy of the data with the service, use GeodatabaseSyncTask to sync with the feature service. Syncing can be performed even if no edits have been made locally, to pull changes from the feature service into the local copy of the data.

Note:

A Basic license is required for editing.

To synchronize edits, do the following:

  • Create a GeodatabaseSyncTask, passing in the URL of the feature service to be edited.
  • Set up a signal handler to report on the progress while the synchronization happens.
  • Set up the parameters for the synchronization task.
  • Call the syncGeodatabase method on GeodatabaseSyncTask.
For a complete example, see the sample Edit and sync features.

Note:
The sync operation overwrites previously synced edits to the same features on the service.

Errors that arise during a sync operation are returned in the callback when the job is done. For descriptions of errors that can arise when syncing offline edits, see Error handling with sync.

For services backed by non-versioned data, sync operations are performed per-layer, and are always bi-directional—that is, local edits are uploaded to the service, and then new edits are downloaded from the service. For services backed by versioned data, sync operations are per-geodatabase, and you can change the synchronization parameters to determine in which direction edits are synchronized—download only, upload only, or bi-directional. Use Geodatabase.syncModel() to find out if a geodatabase can be synchronized per-layer or per-geodatabase. Use SyncGeodatabaseParameters.setGeodatabaseSyncDirection() to set the synchronization direction for a sync operation. When using bi-directional sync, note that the 'last in wins'—that is, uploaded edits will overwrite changes present on the server.

Register a geodatabase in a pre-planned workflow

In a services pattern workflow sometimes known as a pre-planned workflow, you generate the geodatabase once and load copies of it onto each user's device. If you've generated the geodatabase on the user's device with GeodatabaseSyncTask, you don't need to register a geodatabase.

In the pre-planned workflow, you use the registerSyncEnabledGeodatabaseAsync() method to register each geodatabase copy (on each device) with the feature service you used to generate the original geodatabase. Registering in this way ensures each device receives the correct updates during sync operations.

Caution:

  • Registering a geodatabase with a feature service is not supported with a versioned feature service.
  • Once you call unregister on a geodatabase, you cannot re-register the same geodatabase.
  • If the original geodatabase is ever unregistered, no additional clients can use that copy to register.

For a list of benefits of this workflow, see create an offline layer.

Example code

The following example shows you how to sync your offline edits back to a feature service.

// create the generate geodatabase parameters
GenerateGeodatabaseParameters {
    id: generateParameters
    extent: generateExtent
    outSpatialReference: SpatialReference { wkid: 3857 }
    returnAttachments: false


    // only generate a geodatabase with 1 layer
    layerOptions: [
        GenerateLayerOption {
            layerId: featureLayerId
        }
    ]
}


// create the sync geodatabase parameters
SyncGeodatabaseParameters {
    id: syncParameters


    // only sync the 1 layer
    layerOptions: [
        SyncLayerOption {
            layerId: featureLayerId
        }
    ]
    // push up edits, and receive any new edits
    geodatabaseSyncDirection: Enums.SyncDirectionBidirectional
}
// create the GeodatabaseSyncTask to generate the local geodatabase
GeodatabaseSyncTask {
    id: geodatabaseSyncTask
    url: featureServiceUrl
    property var generateJob
    property var syncJob


    function executeGenerate() {
        // execute the asynchronous task and obtain the job
        generateJob = generateGeodatabase(generateParameters, outputGdb);


        // check if the job is valid
        if (generateJob) {


            // show the sync window
            syncWindow.visible = true;


            // connect to the job's status changed signal to know once it is done
            generateJob.jobStatusChanged.connect(updateGenerateJobStatus);


            // start the job
            generateJob.start();
        } else {
            // a valid job was not obtained, so show an error
            syncWindow.visible = true;
            statusText = "Generate failed";
            syncWindow.hideWindow(5000);
        }
    }


    function updateGenerateJobStatus() {
        switch(generateJob.jobStatus) {
        case Enums.JobStatusFailed:
            statusText = "Generate failed";
            syncWindow.hideWindow(5000);
            break;
        case Enums.JobStatusNotStarted:
            statusText = "Job not started";
            break;
        case Enums.JobStatusPaused:
            statusText = "Job paused";
            break;
        case Enums.JobStatusStarted:
            statusText = "In progress...";
            break;
        case Enums.JobStatusSucceeded:
            statusText = "Complete";
            syncWindow.hideWindow(1500);
            offlineGdb = generateJob.geodatabase;
            displayLayersFromGeodatabase();
            isOffline = true;
            break;
        default:
            break;
        }
    }


    function executeSync() {
        // execute the asynchronous task and obtain the job
        syncJob = syncGeodatabase(syncParameters, offlineGdb);


        // check if the job is valid
        if (syncJob) {


            // show the sync window
            syncWindow.visible = true;


            // connect to the job's status changed signal to know once it is done
            syncJob.jobStatusChanged.connect(updateSyncJobStatus);


            // start the job
            syncJob.start();
        } else {
            // a valid job was not obtained, so show an error
            syncWindow.visible = true;
            statusText = "Sync failed";
            syncWindow.hideWindow(5000);
        }
    }


    function updateSyncJobStatus() {
        switch(syncJob.jobStatus) {
        case Enums.JobStatusFailed:
            statusText = "Sync failed";
            syncWindow.hideWindow(5000);
            break;
        case Enums.JobStatusNotStarted:
            statusText = "Job not started";
            break;
        case Enums.JobStatusPaused:
            statusText = "Job paused";
            break;
        case Enums.JobStatusStarted:
            statusText = "In progress...";
            break;
        case Enums.JobStatusSucceeded:
            statusText = "Complete";
            syncWindow.hideWindow(1500);
            isOffline = true;
            break;
        default:
            break;
        }
    }


    // ...

Related topics