Skip to content

Overview

An offline application use an offline store (an on-device copy of the back-end data) to ensure that data is always accessible, even when the device is offline.

You can make changes to the offline store and then synchronize with the back end. By synchronizing you can also retrieve new data from the back end. The offline store needs to synchronize with the back end from time to time to ensure correctness, completeness, and accuracy of data.

Download (Updating the Offline Store)

The offline store is updated with the latest data from the back end when the device is online by performing a download. The timing of performing a download is at the discretion of the application developer. For example, all of these scenarios can be considered, depending on the application:

  • Give the user control over the timing of performing download
  • Download at the beginning and end of a day
  • Download when the application opens
  • Download every 15 minutes
  • Download after every upload

    Because the download process might run for a long time, you can only invoke it asynchronously, and it will usually run in the background.

// Download can only be called asynchronously.
// The first parameter is the success handler and the second
// parameter is the failure handler with exception encountered
// as the parameter
// It returns a cancelToken object that can be used to cancel
// this download later. Please also see "Cancelling Synchronization".
CancelToken cancelToken = offlineODataProvider.download(
    () -> {
        // Proceed to use the offline store after download is successfully completed,
        // for example notifying the UI to take appropriate action
        ...
    },
    (error) -> {
        // Handle the error, for example logging the error or retrying download
        ...
    }
);
/// Download can only be called asynchronously
offlineODataProvider.download(completionHandler: {(_ error: OfflineODataError?) -> Void in
    if let error = error {
        /// Handle the error
        ...
    } else {
        /// Proceed to use the offline store after download is successfully completed
        ...
    }
})

Upload (Updating the Back End)

An application can make changes to the local offline store with OData requests, which are stored in a request queue. Changes made by requests that are queued but have not yet been sent to the back end are reflected in the offline store.

An upload can be performed when a device is online, which sends requests executed against the offline store to the back end.

// Upload can only be called asynchronously.
// The first parameter is the success handler and the second
// parameter is the failure handler with exception encountered
// as the parameter
offlineODataProvider.upload(
    () -> {
        // Proceed to use the offline store after upload is successfully completed,
        // for example notifying the UI to take appropriate action
        ...
    },
    (error) -> {
        // Handle the error, for example, examining ErrorArchive for requests that failed.
        ...
    }
);
/// Upload can only be called asynchronously
offlineODataProvider.upload(completionHandler: {(_ error: OfflineODataError?) -> Void in
    if let error = error {
        /// Handle the error
        ...
    } else {
        /// Proceed to use the offline store after upload is successfully completed
        ...
    }
})

Upload is treated as failed only if there are communication errors during upload. The upload is treated as successfully completed if there aren't any communication errors, although some requests may fail due to the back end. See error categories for more information. To determine if any requests have failed, you can examine the ErrorArchive. In addition, implement the OfflineODataDelegate protocol with a callback delegate. The requestDidFail delegate will be invoked once for every request that failed. This callback delegate can be passed in as a parameter when you construct the OfflineODataProvider instance.

Note

Local entities are not updated with an upload, and you can perform multiple upload operations without performing a download. It is good practice (although not required) to perform an upload before you perform a download to make sure that local changes are pushed to the back end.

Canceling Synchronization

You can use cancelUpload() and cancelDownload() to cancel a synchronization (upload/download) operation and retry at a later time. For example, there may be network bandwidth issues or other reasons that the current synchronization might fail and need to be retried.

You can also cancel individual download operations using a cancel token. When a download operation is canceled (by calling cancelDownload() or CancelToken.cancel()), the server will also be notified to cancel the ongoing download operation.

// Download can only be called asynchronously.
// The returned cancelToken can be used to cancel this download
CancelToken cancelToken = offlineODataProvider.download(
    () -> {
        // Proceed to use the offline store after download is successfully completed,
        // for example notifying the UI to take appropriate action
        ...
    },
    (error) -> {
        // Handle the error, for example, logging the error or retrying download
        ...
    }
);

// Cancel the download
cancelToken.cancel();
/// Download can only be called asynchronously
/// It returns a `cancelToken` object that can be used to cancel this download later. Please also see "Cancelling Synchronization".
offlineODataProvider.download(completionHandler: {(_ error: OfflineODataError?) -> Void in
    if let error = error {
        /// Handle the error
        ...
    } else {
        /// Proceed to use the offline store after download is successfully completed
        ...
    }
})

/// Cancel the download 
cancelToken.cancel();

Schema Upgrades

During download, a schema upgrade of the offline store is performed if a change in the back-end OData service metadata is detected.

Metadata changes are expected to be additive, meaning the OData model is altered by adding entity types or properties, but not by removing entity types or properties. This lets you successfully replay the operations in the request queue on the new schema. If the changes to the OData model are not additive, then you need to deploy a new application compatible with the new model.

Background Updates

Background updates are possible but require the application developer to ensure that upload and download operations are called in a manner that enables them to continue in the background.

Removing Entities After Upload

Offline applications sometimes require the ability to create entities or media streams that should be removed from the device once they have been successfully uploaded to the back end. A common scenario here would be if the user has the permission to create entities that won't necessarily be assigned to him or her. This can be achieved by passing in an OfflineODataRequestOptions instance with removeCreatedEntityAfterUpload set to true when creating an entity or media stream.

Note

Before the create request is uploaded, subsequent operations can be performed on the entity or media stream created with the removeCreatedEntityAfterUpload option, including requests to directly modify the entity or media stream or requests to reference it (create a relationship to it).

After the entity or media stream has been successfully uploaded, subsequent requests on the entity or media stream are not permitted (since the entity or media stream has been removed). In this case, a 404 Not Found error is returned, since the entity or media stream is not in the offline store.

Note

  • Relationships to entity or media stream created with the removeCreatedEntityAfterUpload option – Any relationships created to or from entities or media streams created with the removeCreatedEntityAfterUpload option are automatically removed from the offline store when the entity or media stream is removed.
  • Errors During Upload – The entity or media stream created with the removeCreatedEntityAfterUpload option remains on the device if the POST request itself fails, or any request that references the entity or media stream fails.

This example illustrates how to create a new entity that should be removed after upload:

// Declare a new event
Event event = new Event(false);

// Set properties for the event
...

// Set offline specific request options
OfflineODataRequestOptions options = new OfflineODataRequestOptions();
options.setRemoveCreatedEntityAfterUpload(true);

// Create the event with the given options
eventService.createEntity(event, HttpHeaders.empty, options);
/// Declare a new event
let newEvent = ...

/// Set properties for the event
...

/// Set offline specific request options
let options = OfflineODataRequestOptions()
options.removeCreatedEntityAfterUpload = true

/// Create the event with the given options
try eventService.createEntity(newEvent, options: options)

Background Data Transfer Support

iOS provides a very limited amount of CPU time to applications in a background state. Synchronization, however, may take a significant amount of time, depending on the data size, especially for initial downloads. In such cases, the network connection may break and the synchronization might fail.

To assist in these cases, the enableBackgroundDataTransfer option has been added to OfflineODataProvider. If enableBackgroundDataTransfer is true, it will create a synchronization task in the background and finish the data transfer while the application is still in a background state.

When the application returns to foreground state, the application can continue to work with downloaded data and complete the previously started synchronization. This feature can improve the user experience because users don't then need to keep the application in the foreground to wait for a long synchronization to finish.

This example illustrates how to enable background data transfer:

let storeParams = OfflineODataParameters()
// Set option enableBackgroundDataTransfer to enable
storeParams.enableBackgroundDataTransfer = true
enableBackgroundDataTransfer Meaning
true enable background data transfer
false disable background data transfer

Note

Default value of the option is true.

Limitation

Offline synchronization (upload, download) contains preprocessing, data transfer, and post-processing. Both preprocessing and post-processing require CPU time, and usually preprocessing is completed very quickly. However, if preprocessing takes more time than usual, due to data complexity for example, and the app switches to background state, preprocessing may run for quite a while and then, even when the app returns from the background state, the data transfer may still not have been started while in the background state. In such an extreme case, the data transfer will continue to run when the application is brought to the foreground again.


Last update: August 12, 2022