11 Data Offline and Sync

Mobile app developers can use the Data Offline and Sync features to build a client app that enables the users to perform critical tasks when offline.

You can use the following APIs to build applications that cache REST resources for offline use and then synchronize all offline changes with the server when the device goes online again.

API Platforms Features
Sync Express
  • Cordova

  • JavaScript

  • Basic synchronization.

  • Easy to use.

  • Works with any REST API where the resource name alternates between plural nouns and singular resource identifiers (rid), such as /items/{rid}/subitems/{rid}.

  • Requires minimal changes to existing code.

  • Works with any JavaScript framework.

  • When device reconnects, sends change requests one resource object at a time.

  • Always overwrites the server version of the object.

Synchronization
  • Android

  • iOS

  • Robust synchronization.

  • Works with synchronization-compliant custom APIs.

  • When device reconnects, sends all changes in one request.

  • Provides choices for what to do if the server version of an object changes while edits were made offline (server wins, client wins, preserve conflict).

  • Provides choices for how long to store resource objects on the device,  when to refresh data from the server, and which resources can be edited when offline.

  • Automatically synchronizes with the Storage platform.

Building Apps that Work Offline Using Sync Express

The Javascript and Cordova client SDKs feature Sync Express, which enables you to easily and quickly make your application work offline using your existing REST requests. You can use this library for REST APIs where the resource name alternates between plural nouns and singular resource identifiers (rid), such as /items/{rid}/subitems/{rid}.

Add Sync Express to Your App

To use Sync Express in your app, you must complete the following tasks.
  • Copy both mcs.sync.min.js and mcs.min.js from the SDK into the directory where you keep your JavaScript libraries.
  • Use a script element to load mcs.sync.min.js. This must be the first script that the app fetches and loads unless you add loki-cordova-fs-adapters.js, which is explained next.
  • Use either RequireJS or a script element to load mcs.min.js.
  • From the command line, enter the following to add the cordova-plugin-network-information plugin. This plugin enables Sync Express to detect if the device is online or offline.
    cordova plugin add cordova-plugin-network-information

Install the cordova-plugin-file

When an application attempts to store more REST resources than the device’s cache size allows, Sync Express throws a QUOTA_EXCEEDED_ERR exception. With Cordova apps, you can install the cordova-plugin-file to increase the device’s cache size. This plugin isn’t available for JavaScript web apps.

To install and use the cordova-plugin-file:
  1. Enter this command to install the file:
    cordova plugin add cordova-plugin-file
  2. Copy loki-cordova-fs-adapters.js from the SDK into the directory where you keep your JavaScript libraries.
  3. Add a script element to load loki-cordova-fs-adapter.js. This must be the first script that the app fetches and loads. Then the app can load mcs.sync.min.js and mcs.min.js as described above.

Configure Your App to Use Sync Express

To enable Sync Express, add a syncExpress entry to oracle_mobile_cloud_config.js, and use path elements in the policies array to identify the endpoints that you want to activate Sync Express for. The name that you use for a path parameter must exactly match the name of the property that uniquely identifies a returned object. Use a colon to identify the path parameter, such as :deptId. Note that a configuration file can have a syncExpress entry for Sync Express or a sync entry for the Synchronization library, but it can’t have both.

Let’s say, for example, that you want to activate Sync Express for all calls to these endpoints:

  • /departments

  • /departments/{deptId}

The department database object has these properties:

deptId: number
name: string

The response object for a department collection looks like this:

[
  {
    "deptId": 1,
    "name": "Department 1"
  },
  {
    "deptId": 2,
    "name": "Department 2"
  }
]

The corresponding syncExpress entry would look like this. Notice that you need only one entry in the configuration file to activate Sync Express for both endpoints.

var mcs_config = {
  "logLevel": mcs.LOG_LEVEL.INFO,
  "mobileBackend": {
    "name": "myBackend",
    ...
  }
  "syncExpress": {
    "policies": [
      {
        "path": '/mobile/custom/myApi/departments/:deptId(\\d+)?'
      }
    ]
  }
};

Now let’s say, for example, that you want to include calls to endpoints with subcollections (nested entities), such as an employees within a department:

  • /departments

  • /departments/{deptId}

  • /departments/{deptId}/employees

  • /departments/{deptId}/employees/{empId}

The employee database object has these properties:

deptId: number
empId: number
name: string

The response object for an employee collection looks like this:

[
  {
    "empId": 1,
    "name": "John Doe"
  },
  {
    "empId": 2,
    "name": "Jane Doe"
  }
]

The corresponding syncExpress entry would look like this. Notice that you need only one entry in the configuration file to activate Sync Express for all the endpoints.

var mcs_config = {
  "logLevel": mcs.LOG_LEVEL.INFO,
  "mobileBackend": {
    "name": "myBackend",
    ...
  }
  "syncExpress": {
    "policies": [
      {
        "path": '/mobile/custom/myApi/departments/:deptId(\\d+)/:_employees?/:empId(\\d+)?'
      }
    ]
  }
};

Sync Express provides some regular expressions for formulating the path specification:

  • Use a colon (:) plus the property name to indicate either a path parameter or the name of the property that uniquely identifies each returned object (or both). For example, for the /departments endpoint, you must include :deptId(\\d+) in the path specification to indicate the unique identifier for a department resource, even if the API didn’t have a /mobile/custom/myAPI/departments/{deptId} endpoint.

  • Use a question mark (?) to indicate that the path parameter is optional.

  • When a path segment represents a collection of children resources (a subcollection), then you must precede the parameter name with a colon and an underscore (:_) so that Sync Express stores the response objects in the client cache as children objects that are associated with the parent object.

  • By default, Sync Express assumes that the path parameter is a string. Use (\\d+) to indicate that the path parameter must be a numeric value.

For example, given the /mobile/custom/myApi/departments/:deptId(\\d+)/:_employees?/:empId(\\d+)? path specification:

  • :deptId specifies a path parameter and also provides the name of the property in the department object that uniquely identifies a department.

  • The ? after :deptId(\\d+) indicates that this and subsequent parameters are not required. Thus, the path specification applies to these endpoints:

    • /mobile/custom/myApi/departments

    • /mobile/custom/myApi/departments/{deptId}

    • /mobile/custom/myApi/departments/{deptId}/employees

    • /mobile/custom/myApi/departments/{deptId}/employees/{empId}

  • (\\d+) indicates that the path parameter value must be numeric. If the object’s deptId property is a string, then you’d use /mobile/custom/myApi/departments/:deptId? instead.

  • (:_employees) identifies a subcollection and indicates that all response objects must be stored in the client cache as children of the specified deptId.

Configure Your App to Handle items Arrays

If any response bodies wrap a collection in an items property, such as "items":[{"id:":33},{"id:":34}], then you must add the Oracle REST handler to the syncExpress entry in the configuration file, as shown in the following example:

var mcs_config = {
  "logLevel": mcs.LOG_LEVEL.INFO,
  "mobileBackend": {
    "name": "myBackend",
    ...
  }
  "syncExpress": {
    "handler": "OracleRestHandler",
    "policies": [
      {
        "path": '/mobile/custom/myApi/departments/:deptId(\\d+)?'
      }
    ]
  }
};

Make Your App Synchronize Offline Changes Automatically

To make an app synchronize offline changes with the server automatically, add code to refresh the user interface when the device re-connects (goes online) by making explicit REST calls, which then flush pending changes automatically.

Building Apps that Work Offline Using the Synchronization Library

Use the Synchronization library from Android and iOS mobile apps to enable the app users to continue to use the app when offline.

What Can I Do with the Synchronization Library?

When developing Android and iOS client apps, you, as a mobile app developer, might often take these goals into consideration:

  • Enable updates to app data on mobile devices when connectivity is intermittent or non-existent.

  • Improve performance by minimizing the amount of calls and data transported over the wire.

The client SDK’s Synchronization library, with its data caching, support for offline operations, and automated synchronization, enables you to achieve these goals when you access custom API resources. In addition, through declarative policies, you can design caching and synchronization policies for your custom APIs that you can apply across your apps, and adjust without having to modify code.

Enable Edits to App Data When the Mobile Device Is Offline

As an example of how you can use the Synchronization library to enable app users to read, create, update, and delete data when the mobile device is offline, consider some apps that are designed for the Fix it Fast (FiF) company, which maintains in-house appliances. The mobile app developer wants to ensure that the apps continue to work even when there is no internet connection. For example:

  • A customer uses an FiF mobile app to fill out the details for an incident report regarding a basement furnace. She then goes to the basement to take a picture of the furnace's barcode, attaches it to the report, and taps Send. Even though there’s no internet connection in the basement, the app should enable the customer to access, change, and send the incident report. As soon as the device reconnects to the internet, the app should transmit the report and the attached photo to the server.

  • During the day, a technician reviews her job list, sorts the jobs by priority, driving distance, and issue type, and adjusts the priorities as needed. As she completes a job, she attaches notes to the incident report, and she updates the job status. She expects to be able to do all these tasks even when she doesn't have access to the internet. When her device is connected, she expects the app to synchronize her offline modifications with the server, first synchronizing the essential information, such as job status, and then synchronizing the less essential information, such as her notes.

  • After an unexpectedly long repair, the technician lowers the priority for customer that is the furthest away, John Doe. Because she is offline, her modifications are stored in the offline edits in the local cache. During the time she was offline, John Doe called the office to report that his water heater was now leaking, and the office changed his priority to high. When the technician goes back on line, the app synchronizes the updates, and sees that there is a conflict. The app pops up a notice about the conflict and asks the technician if she still wants to lower the priority.

To implement these data offline requirements, the mobile app developer uses the Synchronization library to fetch and update data, and sets the appropriate fetch, update, and conflict resolution policies in the configuration file.

  • To ensure that incident reports from the /incidents resource are always available, that they can be modified while offline, and that the server is updated with queued offline modifications as soon as the device resumes access, the mobile app developer sets the following policies for the resource:

    • Fetch policy: Fetch resources from the server when the client application is online, and fetch them from the local cache when the app is offline (FETCH_FROM_SERVICE_IF_ONLINE).

    • Update policy: Queue updates if offline and synchronize automatically when the client app is back online (QUEUE_IF_OFFLINE)).

  • To ensure that two technicians don't inadvertently update the same status or priority for an /incidentstatus resource due to queued offline updates, the mobile app developer sets the following policy:

    • Conflict resolution policy: Don’t overwrite the server’s version with the local version if there’s a conflict. The edited local version is kept in the offline edits in the local cache, and the mobile app handles the conflict (PRESERVE_CONFLICT).

    This assumes that the code for this custom API returns the ETag that is used to detect conflicts, and uses the sync.setItem and sync.addItem custom code SDK methods to build the response.

Improve Performance

As an example of how you can use the Synchronization library to improve performance, consider the FiF apps that we discussed previously.

  • Before leaving the office every morning, the technicians start an FiF app on their tablets, and pull a list of their jobs for the day. Because the customer information such as name, phone, and address is static, the app can cache that data upon startup and not re-retrieve it during the day to improve performance. Other information, such as incident status and priority, must be kept current.

  • Expired data needs to be cleared whenever the app is restarted.

  • The finance department designed an API that supplies a customer's default credit card information. Because the information is fairly static, mobile apps might consider caching that information to improve performance. However, the finance department wants to ensure that mobile apps never cache that information.

To implement these performance requirements, the mobile app developer uses the Synchronization library to fetch and update data, and sets the appropriate fetch, expiration, and eviction policies in the configuration file.

  • To cache the information from the /customer resource so that it's retrieved from the server on startup, and, after that from the local cache only, the mobile app developer sets the following policies:

    • Expiration policy: Mark resources as expired when the client application restarts (EXPIRE_ON_RESTART).

    • Eviction policy: Delete expired resources from the local cache when the client application restarts (EVICT_ON_EXPIRY_AT_STARTUP).

    • Fetch policy: Fetch resource from the server only if it isn’t in the local cache or is expired (FETCH_FROM_SERVICE_ON_CACHE_MISS_OR_EXPIRY).

  • To ensure that the priority and status from the /incidentstatus resource is always available, but stays as current as possible:

    • Fetch policy: Fetch resources from the server when the client application is online, and fetch them from the local cache when the app is offline (FETCH_FROM_SERVICE_IF_ONLINE).

    • Eviction policy: Delete expired resources from the local cache when the client application restarts (EVICT_ON_EXPIRY_AT_STARTUP).

    • Expiration policy: Mark a resource as expired when the client application restarts. Update the local cache with the latest version from the server the next time the client application calls the resource (EXPIRE_ON_RESTART).

  • To ensure that none of the information from the /creditcards resource is cached, the custom code that implements this API makes sure that all HTTP responses include the Oracle-Mobile-Sync-No-Store header set to true.

Synchronization Library Process Flow

To help you understand how the parts fit together, here’s an explanation of how the Synchronization library does the following:

  • Manages objects in the local cache

  • Uses synchronization policies to retrieve resources from either the local cache or the server

  • Handles object updates

When the mobile app makes a request through the Synchronization library to get data from a custom API, the Synchronization library looks at the fetch policy setting to determine whether to get the objects from the server or the local cache. Whenever the Synchronization library fetches objects from the server, it refreshes the local cache with the newly fetched objects.

Depending on the policy settings, the Synchronization library might also periodically refresh expired items in the local cache using a background process.

When the user edits an object, the following occurs depending on whether the mobile device is online or offline:

  • Online edit: An update request is sent to the server.

  • Offline edit: The edited object is stored in the offline edits in the local cache. When the app goes online, a background process sends a request to update the resource on the server.

If the conflict resolution policy is CLIENT_WINS, the update request includes an If-Match header of * so that the server updates the resource without conflict. Otherwise the request includes an If-Match header that is set to the ETag that was last returned by the server.

Video: Overview of the Data Offline & Synchronization API

To learn more about how the Synchronization library uses caching to enable a client app to work offline as well as improve performance, take a look at this video:

Android Synchronization Library

This section shows how to use the Synchronization library to implement several of the common data offline tasks for working with a custom API’s resources.

For detailed information about the library, see Oracle Mobile Hub's Android SDK Reference.

Tip:

The client SDK download page contains an examples zip, which contains the source code for the SalesPlus app. This app illustrates many of the synchronization features that are described in this section.
Set Up Your Mobile App for the Android Synchronization Library
  1. Ensure that the AndroidManifest.xml file contains the following entries. WRITE_EXTERNAL_STORAGE lets the Synchronization library maintain the local cache. ACCESS_NETWORK_STATE lets the Synchronization library determine the connection status.
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  2. Define the synchronization policies for the mobile backend and API endpoints in the configuration file.
  3. As with all mobile apps, instantiate MobileManager, and then instantiate MobileBackend to manage connectivity, authentication, and other transactions between your application and its associated mobile backend, including calls to platform and custom APIs.
  4. To access the custom APIs from the Synchronization library, get the mobile backend's synchronization service.
    try {
        Synchronization synchronization =
                MobileManager.getManager().
                getMobileBackend(this).
                getServiceProxy(Synchronization.class);
    } catch (ServiceProxyException e) {
        e.printStackTrace();
    }
Fetch Resources

After you set up your app to work with data offline, you use the mobile endpoint class to open endpoints to custom code API resources, and you use fetch builders to synchronize data retrieval and modifications with the local cache automatically. A fetch builder enables you to specify how to fetch the data, and then enables you to execute the fetch.

  1. To access an endpoint, instantiate MobileEndpoint for that endpoint. This example instantiates an endpoint for /mobile/custom/incidentreport/incidents.
    // open Endpoint
    MobileEndpoint endpoint = 
      synchronization.openMobileEndpoint(
        "incidentreport", 
        "incidents", 
        MobileObject.class);
  2. (Optional) Add objects or files to the collection. This example adds an object.
    MobileObject newObject = endpoint.createObject();
    JSONObject payload = new JSONObject();
    // Set properties
    try {
        payload.put("title", "incident 213");
        ...
    } catch (JSONException e) {
        ...
    }
    newObject.initialize(null, endpoint, payload);
    // Add incident
    newObject.saveResource(new MobileEndpointCallback() {
        @Override
        public void onComplete(Exception exception, MobileResource mobileResource) {
            //This function is called when the request completes
            ...
        }
    });
  3. Use a fetch builder to specify how to fetch the objects from the endpoint. The fetch builder method that you use depends on whether you want to retrieve an object, a collection, or a file:
    • FetchObjectBuilder

    • FetchCollectionBuilder

    • FetchFileBuilder

    Here’s an example of creating a fetch builder for a collection.

    FetchCollectionBuilder fetchCollectionBuilder = endpoint.fetchObjects();

    In this example, we want to filter all the incidents for the signed-in technician (which is the same as the user name). The API provides a query parameter for technician, so we can tell the builder to add that query parameter to the request:

    fetchCollectionBuilder = fetchCollectionBuilder.withQueryParameter("technician", username);

    Tip:

    You can call withQueryParameter as many times as you need to specify all the query parameters.
  4. Add necessary headers.

    In this example, to enable easy searching for all diagnostic log entries associated with this fetch builder, the request includes the Oracle-Mobile-Diagnostic-Session-ID header. The mDiagLogFilterTag string variable has been set to a value that uniquely identifies requests that are made using this fetch builder.

    fetchCollectionBuilder.withHeader("Oracle-Mobile-Diagnostic-Session-ID", mDiagLogFilterTag);
  5. Use the builder to execute the fetch.
    fetchCollectionBuilder.execute(new MobileEndpointCallback(){
        @Override
        public void onComplete(Exception exception, MobileResource mobileResource) {
            //This function is called when the request completes
            ...   
            MobileObjectCollection collection = (MobileObjectCollection) mobileResource;
        }
    });
    If the fetch policy is to fetch the data from the local cache, such as FETCH_FROM_SERVICE_ON_CACHE_MISS, then it’s fetched from the local cache if available. In all other cases, the collection is fetched from the server if the policy allows. If the noCache setting is false, then the results are saved to a local cache.
  6. The raw downloaded JSON object is exposed through the JsonObject property. Use this property to set the appropriate values.
    List objectsList = collection.getObjectsList();
    MobileObject incidentMobileObject = (MobileObject) objectsList.get(index);
    JSONObject json = incidentMobileObject.getJsonObject();
    // This updates incidentMobileObject
    json.put("status", "completed");
  7. Use one of the MobileObject save methods to save the changes on the server.
    incident.saveResource(new MobileEndpointCallback(){
        @Override
        public void onComplete(Exception exception, MobileResource mobileResource) {
            ...
        }
    });
    If the device isn’t connected to the internet, and the update policy is QUEUE_IF_OFFLINE, then the library saves the changes to the local cache. The Synchronization library sends the changes to the server automatically when the device reconnects with the internet.
  8. Use one of the MobileObject delete methods to delete an object.
    incident.deleteResource(new MobileEndpointCallback(){
        @Override
        public void onComplete(Exception exception, MobileResource mobileResource) {
            ...
        }
    });
    If the device is offline, and the update policy is QUEUE_IF_OFFLINE, then the library deletes the object in the local cache. It deletes the object on the server when the client is online again.
Fetch Filtered Resources

You might have an app that filters which items it displays. For example, an FiF app might want to display all incidents with a status of new. When the device is online, your code can fetch the items as mobileResource objects, convert the objects to JSON objects, and then filter the items. However, when the device is offline, your app can’t filter the mobileResource objects in the local cache because the objects are just blobs of data. The solution is to use a custom MobileObject. When you do this, the local cache stores the data in a table with a column for each of the custom object’s fields, which enables your mobile app to query data in the local cache based on field values. We’ll use the incident list in the FiF example to illustrate how to do this. In this example, the users must be able to filter the incident list by status.

When you open a mobile endpoint on a custom MobileObject class, you can use the fetch builder’s queryFor method to specify the filter to use in the local cache. Note that this method is for filtering JSON objects from the local cache. It doesn’t affect the way that the Synchronization library retrieves results from the server. Whenever you execute the fetch builder, the library first looks at the fetch policy setting to determine whether to refresh the local cache. If the policy specifies that it must refresh the local cache from the server, then it retrieves all the objects, regardless of the filter that you specify using the queryFor method. Regardless of the fetch policy and whether it refreshed the local cache, the library then uses the queryFor method to filter the data in the local cache, and return the filtered results. That is, regardless of whether the device is online or offline, and regardless of whether the library fetches data from the server or uses the local cache, the queryFor method filters the results based on the query property and value.

  1. Create a class that extends MobileObject. Add a property for every field that you’ll use in the app. Then override onDataLoad() and getPropertyNames() and create getters and setters for the fields. Here’s an example of creating an IncidentCustomMobileObject class.
    public class IncidentCustomMobileObject extends MobileObject {
        private int id;
        private String title;
        private String technician;
        private String customer;
        private String status;
        private String priority;
        private String createdBy;
        private String createdOn;
        private String modifiedBy;
        private String modifiedOn;
    
        // This method tells the Synchronization library how to get the values from the JSON object.
        @Override
        protected void onDataLoad(){
            try{
                if(jsonObject != null){
                    title = jsonObject.has("title") ? jsonObject.getString("title") : "";
                    technician = jsonObject.has("technician") ? jsonObject.getString("technician") : "";
                    customer = jsonObject.has("customer") ? jsonObject.getString("customer") : "";
                    status = jsonObject.has("status") ? jsonObject.getString("status") : "";
                    createdBy = jsonObject.has("createdBy") ? jsonObject.getString("createdBy") : "";
                    createdOn = jsonObject.has("createdOn") ? jsonObject.getString("createdOn") : "";
                    modifiedBy = jsonObject.has("modifiedBy") ? jsonObject.getString("modifiedBy") : "";
                    modifiedOn = jsonObject.has("modifiedOn") ? jsonObject.getString("modifiedOn") : "";
                    priority = jsonObject.has("priority") ? jsonObject.getString("priority") : "";
                }
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    
        // The Synchronization library uses this method to determine the column names and data
        // types for the database table for the local cache.
        @Override
        public void getPropertyNames(Map<String,PropertyType> properties, List<List<String>> indexes){
            properties.put("title", PropertyType.String);
            properties.put("technician", PropertyType.String);
            properties.put("customer", PropertyType.String);
            properties.put("status", PropertyType.String);
            properties.put("createdBy", PropertyType.String);
            properties.put("createdOn", PropertyType.String);
            properties.put("modifiedBy", PropertyType.String);
            properties.put("modifiedOn", PropertyType.String);
            properties.put("priority", PropertyType.String);
        }
    
        //Getters and Setters
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public String getTechnician() {
            return technician;
        }
    
        public void setTechnician(String technician) {
            this.technician = technician;
        }
    
        public String getCustomer() {
            return customer;
        }
    
        public void setCustomer(String customer) {
            this.customer = customer;
        }
    
        public String getStatus() {
            return status;
        }
    
        public void setStatus(String status) {
            this.status = status;
        }
    
        public String getPriority() {
            return priority;
        }
    
        public void setPriority(String priority) {
            this.priority = priority;
        }
    
        public String getCreatedBy() {
            return createdBy;
        }
    
        public void setCreatedBy(String createdBy) {
            this.createdBy = createdBy;
        }
    
        public String getCreatedOn() {
            return createdOn;
        }
    
        public void setCreatedOn(String createdOn) {
            this.createdOn = createdOn;
        }
    
        public String getModifiedBy() {
            return modifiedBy;
        }
    
        public void setModifiedBy(String modifiedBy) {
            this.modifiedBy = modifiedBy;
        }
    
        public String getModifiedOn() {
            return modifiedOn;
        }
    
        public void setModifiedOn(String modifiedOn) {
            this.modifiedOn = modifiedOn;
        }
    
    
    }
    
  2. Open the endpoint for the custom class.
    MobileEndpoint endpoint = 
      synchronization.openMobileEndpoint(
        "incidentreport", 
        "incidents", 
        IncidentCustomMobileObject.class);
  3. When you create the fetch builder, use the queryFor method to add a query to filter the results by status.
    FetchCollectionBuilder fetchCollectionBuilder = endpoint.fetchObjects();
    fetchCollectionBuilder = fetchCollectionBuilder.queryFor(
      "status", 
      Comparison.Equals, 
      "pending");
  4. Fetch the data.
    fetchCollectionBuilder.execute(new MobileEndpointCallback(){
      @Override
       public void onComplete(Exception exception, MobileResource mobileResource){
          MobileObjectCollection collection = (MobileObjectCollection) mobileResource
       }
    })
  5. The raw downloaded JSON object is exposed through the JsonObject property. Use this property to access the appropriate values.
    Incident incident = (Incident) collection.getObjectsList().get(index);
    JSONObject json = incident.getJsonObject();
    json.put("status", "completed");
  6. Save and delete objects the same way you save and delete OMCMobileObject objects.
    //Save the object
    incident.saveResource (new MobileEndpointCallback(){
    });
    ...
    // Delete the object
    incident.deleteResource (new MobileEndpointCallback(){
    });
    
Specify Which Resources to Synchronize First

When a mobile app reconnects with the internet, the library synchronizes the local cache with the server. If you want the library to synchronize some resources before others, such as statuses before images, then pin the resources with the applicable priorities.

When you fetch the resource, you use the MobileResource class’ pinResource method to set a resource’s priority (MobileFile, MobileObject, and MobileObjectCollection inherit from this class).
builder.execute(new MobileEndpointCallback(){
  @Override
  public void onComplete(Exception exception, MobileResource mobileResource) {
     mobileResource.pinResource(PinPriority.High);
  }
});
Change a Resource’s Synchronization Policies

When you fetch a resource, the Synchronization library saves with the resource object the synchronization policies that are specified in the configuration file. These saved policies are associated with that resource object for its lifetime. You can change these saved policies when you fetch the data and before you add, update, or delete a resource.

Change a Fetch Builder’s Synchronization Policy

You can use the fetch builder’s synchronization policy to override an endpoint’s configured policies. When the library fetches the resource from the server, it saves the fetch builder’s policy settings with the resource.

  1. Create the fetch builder.
    FetchCollectionBuilder fetchCollectionBuilder = endpoint.fetchObjects();
  2. Create a SyncPolicy object and set the policies to override. This example overrides all the policies:
    SyncPolicy policy = new SyncPolicy();
    policy.setFetchPolicy(SyncPolicy.FETCH_POLICY_FETCH_FROM_SERVICE_IF_ONLINE);
    policy.setExpirationPolicy(SyncPolicy.EXPIRATION_POLICY_EXPIRE_ON_RESTART);
    policy.setEvictionPolicy(SyncPolicy.EVICTION_POLICY_EVICT_ON_EXPIRY_AT_STARTUP);
    policy.setUpdatePolicy(SyncPolicy.UPDATE_POLICY_QUEUE_IF_OFFLINE);
    policy.setConflictResolutionPolicy(SyncPolicy.CONFLICT_RESOLUTION_POLICY_CLIENT_WINS);
    policy.setNoCache(false);
  3. Set the builder’s synchronization policy.
    fetchCollectionBuilder = fetchCollectionBuilder.withPolicy(policy);
Change a Resource Object’s Synchronization Policy

Sometimes, you’ll need to change the synchronization policy for a mobile resource object (such as a mobile object, mobile collection, or mobile file) before you send an add, update, or delete to the server. This example sets the mobile resource object’s conflict resolution policy to CONFLICT_RESOLUTION_POLICY_CLIENT_WINS.

  1. Get the synchronization policy for the mobile resource object.
    SyncPolicy policy = mIncidentMobileObject.getCurrentSyncPolicy();
  2. Set the conflict resolution policy to CONFLICT_RESOLUTION_POLICY_CLIENT_WINS. All other policies remain as is.
    policy.setConflictResolutionPolicy(SyncPolicy.CONFLICT_RESOLUTION_POLICY_CLIENT_WINS);
  3. Set the mobile resource object’s synchronization policy. This change doesn't take affect until you call saveResource (to perform an add or update). For a delete, you must call reloadResource for the policy change to take affect before you call deleteResource.
    mIncidentMobileObject.setSyncPolicy(policy);
Detect and Handle Conflicts

When the conflict resolution policy that is in affect for a resource is PRESERVE_CONFLICT, the Synchronization library doesn’t overwrite the server’s version with the local version if there’s a conflict. Instead, an edited version is kept in the offline edits in the local cache, and the mobile app is responsible for handling the conflict, such as programmatically merging the two versions.

A conflict occurs when the object on the server was updated after you retrieved it, and thus is no longer the version that you tried to update. For example, Mary uses her app to change an incident status at 4:00 p.m. However, her device is offline, so the change is stored in the offline edits in the local cache. At 4:30, Tom updates the same incident. At 5:00, Mary’s device reconnects with the internet, and the Synchronization library automatically sends Mary’s offline edit to the server. The server responds with a 412 Precondition Failed status to indicate the conflict.

When a conflict happens, the library marks the modified object as having conflicts, and it makes available both the modified object (from the offline edits in the local cache), and the current server version to enable you to handle the conflict in your code.

If the device is online when the library sends an update or delete to the server, then the mobile app can handle the conflict as soon as it receives the response. However, when the user makes edits when the device is offline, there’s no way to know if there are conflicts. You can't check for conflicts until the device reconnects and the library synchronizes the offline edits with the server. There are two ways to detect and handle conflicts that occur when a device reconnects:

  • Detect and handle conflicts after the library finishes synchronizing offline edits with the server.
  • Detect and handle conflicts when the library sends the offline edit to the server (when the device is online).
Detect Conflicts After the Library Completes Synchronization

You can detect and handle conflicts after the library finishes synchronizing offline edits with the server. After the library finishes synchronizing all offline edits, it calls this method for each offline edit that it synchronized.

  • Use the offlineResourceSynchronized method, as shown here. In this example, the only mobile endpoint that the mobile app accesses is the incidents endpoint. The example shows how to handle both custom and generic MobileObject objects.

    synchronization.offlineResourceSynchronized(new SyncResourceUpdatedCallback() {
        @Override
        public void onResourceUpdated(String uri, MobileResource mobileResource) {
            if (mobileResource == null) {
                Log.i("offlineResourceSync", "Resource for " + uri +
                        "deleted from cache after offline synchronization");
                return;
            }
    
            String result = null;
            if (mobileResource.hasConflict()) {
                result = "with conflicts";
            } else if (mobileResource.hasOfflineUpdates()) {
                result = "with offline update";
            } else if (mobileResource.hasOfflineCommitError()) {
                result = "with error";
            } else {
                result = "successfully";
            }
    
            // If you created a custom MobileObject class, you can access properties directly
            if (mobileResource instanceof IncidentCustomMobileObject) {
    
                IncidentCustomMobileObject anIncident = (IncidentCustomMobileObject) mobileResource;
    
                Log.i("offlineResourceSync", "Offline edits for " + anIncident.getTitle()
                        + " finished with result :" + result);
    
                // Incident has been synchronized with the service object.
                // You can show a pop up or reload the resources in the UI,
                // such as in the main thread.
    
            } else {
    
                // Process has finished.
                // MobileObject/MobileFile has been synchronized with the service object.
                // You can show a pop up or reload the resources in the UI,
                // such as in the main thread.
    
            }
        }
    });
Detect Conflicts When the Library Updates the Cache

You can detect and handle conflicts when the library sends the offline edit to the server (when the device is online).

  • Use the Synchronization cachedResourceChanged method to listen for online updates and deletes, as shown here. The callback for this method is called for each resource that the library updates or deletes. Typically, you use this method to detect any resource change during a background cache refresh so that you can refresh the UI with the change. However, you also can use this method to detect and handle conflicts when the library synchronizes the offline edits. Note that the callback is not called when the library adds a new resource to the local cache.

    Don’t initialize CachedResourceChanged more than once during the lifetime of the application.

    In this example, the only mobile endpoint that the mobile app accesses is the incidents endpoint. The example shows how to handle both custom and generic MobileObject objects.

    synchronization.cachedResourceChanged(new SyncResourceUpdatedCallback() {
        @Override
        public void onResourceUpdated(String uri, MobileResource mobileResource) {
            if (mobileResource == null) {
                Log.i("cachedResourceChanged", "Resource for " + uri + "deleted from cache");
                return;
            }
    
            String result = null;
            if (mobileResource.hasConflict()) {
                result = "with conflicts";
            } else if (mobileResource.hasOfflineUpdates()) {
                result = "with offline update";
            } else if (mobileResource.hasOfflineCommitError()) {
                result = "with error";
            } else {
                result = "successfully";
            }
    
            // If you created a custom MobileObject class, you can access properties directly
            if (mobileResource instanceof IncidentCustomMobileObject) {
    
                IncidentCustomMobileObject anIncident = (IncidentCustomMobileObject) mobileResource;
    
                Log.i("cachedResourceChanged", "Cache changes for " + anIncident.getTitle()
                        + " finished with result :" + result);
    
                // Custom object changed in local cache. You can show a pop up
                // or reload the resources in the UI, such as in the main thread.
            } else {
    
                Log.i("cachedResourceChanged", "Cache changes finished with result :" + result);
    
                // OMCMobileObject, OMCMobileFile, or OMCMobileObjectCollection
                // object changed in local cache.
                // You can show a pop up or reload the resources in the UI,
                // such as in the main thread.
    
            }
        }
    });
Review and Discard Offline Edits

You might want to enable a mobile user to work offline while they make their changes, and then switch back to working online when the user has completed making changes, is satisfied with the end result, and is ready for the Synchronization library to synchronize with the server. The code examples in this section show how to:

  • Switch the app to work-offline mode and switch back to work-online mode.

  • List the resources that have been changed while offline.

  • Discard all offline edits.

  • Discard a resource’s offline edits.

The Synchronization class provides the methods for reviewing and discarding offline edits. As shown in the following steps, you use its getNetworkStatus and setOfflineMode methods, along with the SyncNetworkStatus enumeration to switch the work-offline mode on and off. You use its loadOfflineResources method to get all the offline edits that haven’t been synchronized with the server, and its discardOfflineUpdates method to discard all offline edits.

  1. At application start-up, instantiate Synchronization and open the mobile endpoint.
    try {
        synchronization = 
            MobileManager.getManager().getMobileBackend(this).getServiceProxy(Synchronization.class);
        } catch(ServiceProxyException e) {
            e.printStackTrace();
        }
    incidentsEndpoint = synchronization.openMobileEndpoint(
        "incidentreport",
        "incidents",
        MobileObject.class);
  2. Add a Switch component to the layout.
    <Switch
        android:id="@+id/workOfflineSwitch"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        ...
        android:onClick="changeWorkOfflineMode"
        android:text="Work Offline" />
  3. Add the changeWorkOfflineMode function, which is called when workOfflineSwitch is clicked. This method uses the Synchronization getNetworkStatus method to determine the current network status, and the setOfflineMode method to switch the work-offline mode on and off. When it calls setOfflineMode, the library synchronizes all offline edits with the server automatically. Note that calling setOfflineMode(true) when the device isn’t connected to the internet has no effect.
    public void changeWorkOfflineMode(View view) {
        SyncNetworkStatus syncNetworkStatus = synchronization.getNetworkStatus();
        try {
            if (syncNetworkStatus == SyncNetworkStatus.SyncOffline) {
                // Because setOfflineMode() is a no-op when the device
                // is offline, don't allow user to switch modes when offline.
                Toast.makeText(MainActivity.this,
                        "No internet connection. " +
                                "You can't switch the Work Offline mode on or off when " +
                                "there isn't an internet connection.",
                        Toast.LENGTH_SHORT).show();
            } else {
                // Device is not in "real" offline mode.
                // Switch from work online to work offline, or switch from work offline to work online
                // setOfflineMode(true) sets SyncNetworkStatus to SyncOfflineTest
                // setOfflineMode(false) sets SyncNetworkStatus to SyncOnline
                // (if the device is actually online)
                synchronization.setOfflineMode(syncNetworkStatus == SyncNetworkStatus.SyncOnline);
            }
        } catch (Exception e) {
            // Handle error
        }
    }
  4. Add code to the onCreate method to set the switch according to the current mode.
    Switch workOfflineSwitch = (Switch) findViewById(R.id.workOfflineSwitch);
    
    workOfflineSwitch.setChecked(
            synchronization.getNetworkStatus() == SyncNetworkStatus.SyncOfflineTest);
  5. Add code to display a list of the offline edits. You use the Synchronization loadOfflineResources method to get the list. In this example, the mobile app accesses only the incidents endpoint, and all the items in the offline edits list are of type MobileObject.
    //Display a list of offline edits
    synchronization.loadOfflineResources(new SyncLocalLoadingCallback() {
        @Override
        public void onSuccess(List<MobileResource> resources) {
            // This list contains all the MobileResource objects in the local edit cache
            // In this app, the only mobile endpoint is for incidents
            // So, only MobileObjects are in the edit cache
            for (MobileResource resource : resources) {
                // Put your code to add the incident to the display list here
            }
        }
    
        @Override
        public void onError(String errorMessage) {
            //Handle the error
        }
    });
  6. Add a button to discard all offline edits. Use code like the following to discard the edits.
    final Button mDiscardEdits = (Button) findViewById(R.id.buttonDiscardOfflineEdits);
    
    mDiscardEdits.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //Discard all offline edits:
            //Deletes all resources in the edit cache,
            //but keeps all resources in the local cache as is
            synchronization.discardOfflineUpdates(new SyncDiscardOfflineResourceCallback() {
                @Override
                public void onError(String errorMessage) {
                    //Handle the error
                }
            });
        }
    
    });
  7. The previous step shows how to discard all offline updates. You also can discard offline updates for a specific resource. You call the resource's reloadResource method with the discardOfflineUpdates parameter set to true and the reloadFromService parameter set to false.

    In the following code example, arraySelectedResourcesToDiscardOfflineEdits is a list of resources that were edited while offline and were selected for discarding the edits.

    try {
        for (int index = 0; index < arraySelectedResourcesToDiscardOfflineEdits.length; index++) {
    
            MobileResource mobileResource = arraySelectedResourcesToDiscardOfflineEdits[index];
            mobileResource.reloadResource(true, false, new MobileEndpointCallback() {
                @Override
                public void onComplete(Exception exception, MobileResource mobileResource) {
                    if (exception != null) {
                        // handle exception here
                    } else {
                        // handle success here
                    }
                }
            });
    
        }
    } catch (Exception ex) {
        // handle exception here
    }

iOS Synchronization Library

This section shows how to use the Synchronization library to implement several of the common data offline tasks for working with a custom API’s resources.

For detailed information about the library, see Oracle Mobile Hub's iOS SDK Reference.

Tip:

The client SDK download page contains an examples zip, which contains the source code for the SalesPlus app. This app illustrates many of the synchronization features that are described in this section.
Set Up Your Mobile App for the iOS Synchronization Library
  1. Ensure that the correct policies for the mobile backend and API are defined in the configuration file.
  2. As with all mobile apps, instantiate OMCMobileManager, and then instantiate OMCMobileBackend to manage connectivity, authentication, and other transactions between your application and its associated mobile backend, including calls to platform and custom APIs.
  3. To access the custom APIs from the Synchronization library, get the mobile backend's synchronization service.
    OMCSynchronization* synchronization = [mbe synchronization];
    [synchronization initialize];
Fetch Resources

After you set up your app to work with data offline, you use the mobile endpoint class to open endpoints to custom code API resources, and you use fetch builders to synchronize data retrieval and modifications with the local cache automatically. A fetch builder enables you to specify how to fetch the data, and then enables you to execute the fetch.

  1. To access an endpoint, instantiate OMCMobileEndpoint for that endpoint. This example instantiates an endpoint for /mobile/custom/incidentreport/incidents.
    // open Endpoint
    OMCMobileEndpoint* endpoint = [
      synchronization openEndpoint:OMCMobileObject.class       
      apiName:@"incidentreport"
      endpointPath:@"incidents"
    ];
  2. (Optional) Add objects or files to the collection. This example adds an object.
    OMCMobileObject* newObject = [mobileEndpoint createObject];
        // Set properties
        [newObject addOrUpdateJsonProperty:@"title" propertyValue:@"incident 213"]; 
        ....  
        [newObject saveResourceOnSuccess:^(id mobileObject) {
    
           } OnError:^(NSError *error) {
    
           }];
  3. Use a fetch builder to specify how to fetch the objects from the endpoint. The fetch builder method that you use depends on whether you want to retrieve an object, a collection, or a file:
    • OMCFetchObjectBuilder

    • OMCFetchObjectCollectionBuilder

    • OMCFetchFileBuilder

    Here’s an example of creating a fetch builder for a collection.

    OMCFetchObjectCollectionBuilder* builder = [endpoint fetchObjectCollectionBuilder];

    In this example, we want to get all the incidents for the signed-in technician (which is the same as the user name). The API provides a query parameter for technician, so we can tell the builder to add that query parameter to the request:

    [builder withParamName:@"technician" paramValue:username];
    You can call withParamName as many times as you need to specify all the query parameters.
  4. Add necessary headers.

    In this example, to enable easy searching for all diagnostic log entries associated with this fetch builder, the request includes the Oracle-Mobile-Diagnostic-Session-ID header. The diagLogFilterTag string variable has been set to a value that uniquely identifies requests that are made using this fetch builder.

    [builder setRequestHeaders:[NSDictionary dictionaryWithObjectsAndKeys: diagLogFilterTag, @"Oracle-Mobile-Diagnostic-Session-ID", nil]];
  5. Use the builder to execute the fetch.
    [builder executeFetchOnSuccess:^(OMCMobileObjectCollection *mobileObjectCollection) {
      // This function is called when the request finishes successfully.
      // Get all the objects from the collection.         
      NSArray* collection = [mobileObjectCollection getMobileObjects];      
    } OnError:^(NSError *error) {                  
      // This function is called when the request finishes with an error
    }];
    If the fetch policy is to fetch the data from the local cache, such as FETCH_FROM_SERVICE_ON_CACHE_MISS, then it’s fetched from the local cache if available. In all other cases, the collection is fetched from the server if the policy allows. If the noCache setting is false, then the results are saved to a local cache.
  6. The raw downloaded JSON object is exposed through the jsonObject property. You can use this property to set the appropriate values, or use addOrUpdateJsonProperty.
    OMCMobileObject* incident = [collection objectAtIndex:index];
    // You can access raw JSON              
    NSDictionary* json = [incident jsonObject];
    // Or use the addOrUpdateJsonProperty method             
    [incident addOrUpdateJsonProperty:@"status" propertyValue:@"completed"];
  7. Use one of the OMCMobileObject save methods to save the changes on the server.
    [incident saveResourceOnSuccess:^(id object){
      // Block that is called after the request finishes successfully
      ...                              
    }OnError:^(NSError *error){
      // Block that is called after the request finishes with an error
      ...                          
    }];
    If the device isn’t connected to the internet, and the update policy is QUEUE_IF_OFFLINE, then the library saves the changes to the local cache. The changes are sent to the server automatically when the device reconnects with the internet.
  8. Use one of the OMCMobileObject delete methods to delete an object.
    [incident deleteResourceOnError:^(NSError *error) {                              
    }];
    If the device is offline, and the update policy is QUEUE_IF_OFFLINE, then the library deletes the object in the local cache. It deletes the object on the server when the client is online again.
Fetch Filtered Resources

You might have an app that filters which items it displays. For example, an FiF app might want to display all incidents with a status of new. When the device is online, your code can fetch the items as mobileResource objects, convert the objects to JSON objects, and then filter the items. However, when the device is offline, your app can’t filter the mobileResource objects in the local cache because the objects are just blobs of data. The solution is to use a custom MobileObject. When you do this, the local cache stores the data in a table with a column for each of the custom object’s fields, which enables your mobile app to query data in the local cache based on field values. We’ll use the incident list in the FiF example to illustrate how to do this. In this example, the users must be able to filter the incident list by status.

When you open a mobile endpoint on a custom MobileObject class, you can use the fetch builder’s queryForProperty method to specify the filter to use in the local cache. Note that this method is for filtering JSON objects from the local cache. It doesn’t affect the way that the Synchronization library retrieves results from the server. Whenever you execute the fetch builder, the library first looks at the fetch policy setting to determine whether to refresh the local cache. If the policy specifies that it must refresh the local cache from the server, then it retrieves all the objects, regardless of the filter that you specify using the queryForProperty method. Regardless of the fetch policy and whether it refreshed the local cache, the library then uses the queryForProperty method to filter the data in the local cache, and return the filtered results. That is, regardless of whether the device is online or offline, and regardless of whether the library fetches data from the server or uses the local cache, the queryForProperty method filters the results based on the query property and value.

  1. Create a custom mobile object class that extends OMCMobileObject, define all the properties that you need for your custom mobile object, and synthesize those properties. Here’s an example of the incident.h header file for an Incident class.
    #import <Foundation/Foundation.h>
    #import "OMCMobileObject.h"
    
    @interface Incident : OMCMobileObject {
        
    }
    
    // Properties
    @property (nonatomic, retain) NSNumber* id
    @property (nonatomic, retain) NSString* title;
    @property (nonatomic, retain) NSString* customer;
    @property (nonatomic, retain) NSString* status;
    @property (nonatomic, retain) NSString* priority;
    @end
  2. When you initialize the mobile backend's synchronization service, use the initializeWithMobileObjectEntities method to create database entities for the Incident custom class.
    NSArray* entities = [NSArray arrayWithObjects:[Incident class], nil];
    [synchronization initializeWithMobileObjectEntities:entities];

    You can include more than one custom object in the initialization.

  3. Open the endpoint for the custom class.
    OMMobileEndpoint* endpoint = [
      synchronization openEndpoint:Incident.class
      apiName:@"incidentreport"                  
      endpointPath:@"incidents"
    ];
  4. When you create the fetch builder, use the queryForProperty method to add a query to filter the results by status.
    OMCFetchObjectCollectionBuilder* builder = [endpoint fetchObjectCollectionBuilder];
    
    [builder queryForProperty:@"status"
      comparision:Equals
      compareWith:@"pending"];
  5. Fetch the data.
    [builder executeFetchOnSuccess:^(OMCMobileObjectCollection *mobileObjectCollection) {
      // This function is called when the request finishes successfully.
      // Get all the objects from the collection.         
      NSArray* collection = [mobileObjectCollection getMobileObjects];      
    } OnError:^(NSError *error) {                  
      // This function is called when the request finishes with an error
    }];
  6. The raw downloaded JSON object is exposed through the jsonObject property. You can use this property to set the appropriate values, or you can access the properties directly.
    Incident* incident = [collection objectAtIndex:index];
    // You can access raw JSON
    NSDictionary* json = [incident jsonObject];
    // Or you can access the property directly
    incident.status = @"completed";
    
  7. Save and delete objects the same way you save and delete OMCMobileObject objects.
    //Save the object
    [incident saveResourceOnSuccess:^(id object){
    
    }OnError:^(NSError *error) {
    
    }];
    ...
    // Delete the object
    [incident deleteResourceOnError:^(NSError *error) {
    
    }];
Specify Which Resources To Synchronize First

When a mobile app reconnects with the internet, the library synchronizes the local cache with the server. If you want the library to synchronize some resources before others, such as statuses before images, then pin the resources with the applicable priorities.

When you fetch the resource, you use the OMCMobileResource class’ pinResource method to set a resource’s priority (OMCMobileFile, OMCMobileObject, and OMCMobileObjectCollection inherit from this class).
[builder executeFetchOnSuccess:^(OMCMobileObjectCollection *mobileObjectCollection) { 
   [mobileObjectCollection pinResource:High];
  // Get all the objects from the collection         
  NSArray* objects = [mobileObjectCollection getMobileObjects];      
} OnError:^(NSError *error) {                  
  // This function is called when the request finishes with an error      
}];
Change a Resource’s Synchronization Policies

When you fetch a resource, the Synchronization library saves with the resource object the synchronization policies that are specified in the configuration file. These saved policies are associated with that resource object for its lifetime. You can change these saved policies when you fetch the data and before you add, update, or delete a resource.

Change a Fetch Builder’s Synchronization Policy

You can use the fetch builder’s synchronization policy to override an endpoint’s configured policies. When the library fetches the resource from the server, it saves the fetch builder’s policy settings with the resource.

  1. Create the fetch builder.
    OMCFetchObjectCollectionBuilder* builder = [endpoint fetchObjectCollectionBuilder];
  2. Create an OMCSyncPolicy object, and then set the policies that you want to override. This example overrides all the policies:
    OMCSyncPolicy* policy = [[OMCSyncPolicy alloc] init];
    policy.fetch_Policy = FETCH_POLICY_FETCH_FROM_SERVICE_IF_ONLINE;
    policy.expiration_Policy = EXPIRATION_POLICY_EXPIRE_ON_RESTART;
    policy.eviction_Policy = EVICTION_POLICY_EVICT_ON_EXPIRY_AT_STARTUP;
    policy.update_Policy = UPDATE_POLICY_QUEUE_IF_OFFLINE;
    policy.conflictResolution_policy = CONFLICT_RESOLUTION_POLICY_CLIENT_WINS;
    policy.no_cache = false;
    
  3. Set the builder’s synchronization policy.
    [builder setSyncPolicy:policy];
Change a Resource Object’s Synchronization Policy

Sometimes, you’ll need to change the synchronization policy for a mobile resource object (such as a mobile object, mobile collection, or mobile file) before you send an add, update, or delete to the server. This example sets the mobile resource object’s conflict resolution policy to CONFLICT_RESOLUTION_POLICY_CLIENT_WINS.

  1. Get the synchronization policy for the mobile resource object. In this example, anIncident is an OMCMobileObject.
    OMCSyncPolicy* policy = [anIncident getCurrentSyncPolicy];
  2. Set the conflict resolution policy to CONFLICT_RESOLUTION_POLICY_CLIENT_WINS. All other policies remain as is.
    policy.conflictResolution_policy = CONFLICT_RESOLUTION_POLICY_CLIENT_WINS;
  3. Set the mobile resource object’s synchronization policy. This change doesn't take affect until you call saveResource (to perform an add or update). For a delete, you must call reloadResource for the policy change to take affect before you call deleteResource.
    [anIncident setSyncPolicy:policy];
Detect and Handle Conflicts

When the resource's conflict resolution policy is PRESERVE_CONFLICT, the Synchronization library doesn’t overwrite the server’s version with the local version if there’s a conflict. Instead, it keeps an edited version in the offline edits in the local cache, and the mobile app is responsible for handling the conflict, such as programmatically merging the two versions.

A conflict occurs when the object on the server was updated after you retrieved it, and thus is no longer the version that you tried to update. For example, Mary uses her app to change an incident status at 4:00 p.m. However, her device is offline, so the change is stored in the offline edits in the local cache. At 4:30, Tom updates the same incident. At 5:00, Mary’s device reconnects with the internet, and the library automatically sends Mary’s offline edit to the server. The server responds with a 412 Precondition Failed status to indicate the conflict.

When a conflict happens, the library marks the modified object as having conflicts, and the library makes available both the modified object (from the offline edits in the local cache), and the current server version to enable you to handle the conflict in your code.

If the device is online when the library sends an update or delete to the server, then the mobile app can handle the conflict as soon as it receives the response. However, when the user makes edits when the device is offline, there’s no way to know if there are conflicts. You can't check for conflicts until the device reconnects and the library synchronizes the offline edits with the server. There are two ways to detect and handle that occur when a device reconnects:

  • Detect and handle the conflicts after the library finishes synchronizing offline edits with the server.
  • Detect and handle conflicts when the library sends the offline edit to the server (when the device is online).
Detect Conflicts After the Library Completes Synchronization

You can detect and handle conflicts after the library finishes synchronizing offline edits with the server. After the library finishes synchronizing all offline edits, it calls this method for each offline edit that it synchronized.

  • Use the OMCSynchronization offlineResourceSynchronized method as shown here. In this example, the only mobile endpoint that the mobile app accesses is the incidents endpoint. The example shows how to handle both custom and generic MobileObject objects.
        [sync offlineResourceSynchronized:^(NSString *uri, id mobileResource) {
            
            if ( !mobileResource ) {
                NSLog(@"Resource for %@ deleted from cache after offline synchronization ", uri);
                return;
            }
            
            NSString* result = nil;
            if ( ((OMCMobileResource*) mobileResource).hasConflicts ) {
                result = @"with conflicts";
            }
            else if ( ((OMCMobileResource*) mobileResource).hasOfflineCommitError ) {
                result = @"with error";
            }
            else {
                result = @"successfully";
            }
            
            // If you created a custom MobileObject class, you can access properties directly
            if([mobileResource isKindOfClass:[Incident class]]) {
                
                Incident* anIncident = mobileResource;
                
                NSLog(@"Offline edits for %@ finished %@.", anIncident.title, result);
                
                // Incident has been synchronized with the service object. 
                // You can show a pop up or reload the resources in the UI, 
                // such as in the main thread.
    
                // When mobileResource is a custom MobileObject class,
                // and hasConflicts is true, 
                // then both the MobileObject class and its jsonObject property 
                // contain the local edited copy and the
                // jsonObjectPersistentState property contains the server copy
            }
            else {
     
                OMCMobileResource* aMobileResource = mobileResource;
                NSLog(@"Offline edits for resource %@ finished %@", 
                    aMobileResource.uri, result)
    
                // OMCMobileObject or OMCMobileFile has been synchronized 
                // with the service object. 
    
                // You can show a pop up or reload the resources in the UI, 
                // such as in the main thread.
    
                // When mobileResource is an OMCMobileObject,
                // and hasConflicts is true, 
                // then its jsonObject property contains the local edited copy and
                // its jsonObjectPersistentState property contains the server copy
            }
        }];
Detect Conflicts When the Library Updates the Cache

You can detect and handle conflicts at the time that the library sends the offline edit to the server (when the device is online).

  • Use the OMCSynchronization cacheResourceChanged method to listen for online updates and deletes, as shown here. The callback for this method is called for each resource that the library updates or deletes. Typically, you use this method to detect any resource change during a background cache refresh so that you can refresh the UI with the change. However, you also can use this method to detect and handle conflicts when the library synchronizes the offline edits. Note that the callback is not called when the library adds a new resource to the local cache.

    Don’t initialize CachedResourceChanged more than once during the lifetime of the application.

    In this example, the only mobile endpoint that the mobile app accesses is the incidents endpoint. The example shows how to handle both custom and generic MobileObject objects.

        [sync cachedResourceChanged:^(NSString *uri, id mobileResource) {
            
            if ( !mobileResource ) {
                NSLog(@"Resource for %@ deleted from cache ", uri);
                return;
            }
            
            NSString* result = nil;
            if ( ((OMCMobileResource*) mobileResource).hasConflicts ) {
                result = @"with conflicts";
            }
            else if ( ((OMCMobileResource*) mobileResource).hasOfflineUpdates ) {
                result = @"with offline update";
            }
            else if ( ((OMCMobileResource*) mobileResource).hasOfflineCommitError ) {
                result = @"with error";
            }
            else {
                result = @"successfully";
            }
            
            // If you created a custom MobileObject class, you can access properties directly
            if([mobileResource isKindOfClass:[Incident class]]) {
                
                Incident* anIncident = mobileResource;
                
                NSLog(@"Cache changes for %@ finished %@.", anIncident.title, result);
                
                // Custom object changed in local cache. You can show a pop up
                // or reload the resources in the UI, such as in the main thread.
            }
            else {
                
                OMCMobileResource* aMobileResource = mobileResource;
                NSLog(@"Cache changes for %@ finished %@.", 
                    aMobileResource.uri, result);
                // OMCMobileObject, OMCMobileFile, or OMCMobileObjectCollection 
                // object changed in local cache.
                // You can show a pop up or reload the resources in the UI, 
                // such as in the main thread.
    
            }
        }];       
Review and Discard Offline Edits

You might want to enable a mobile user to work offline while they make their changes, and then switch back to working online when the user has completed making changes, is satisfied with the end result, and is ready for the Synchronization library to synchronize with the server. The code examples in this section show how to:

  • Switch the app to work-offline mode and switch back to work-online mode.

  • List the resources that have been changed while offline.

  • Discard all offline edits.

  • Discard a resource’s offline edits.

The OMCSynchronization class provides the methods for working offline, and for reviewing and discarding offline edits. As shown in the following steps, you use its GetNetworkStatus and setOfflineMode methods, along with the SyncNetworkStatus constants to switch the work-offline mode on and off. You use its loadOfflineResourcesOnSuccess method to get all the offline edits that haven’t been synchronized with the server, and its discardOfflineUpdatesOnError method to discard all offline edits. You also can discard a specific resource’s offline updates by calling the resource’s reloadResource method.

  1. Add a button to switch between work-online mode and work-offline mode. Use code like the following to switch modes when the user clicks the button. You use the OMCSynchronization GetNetworkStatus method to determine the current network status, and the setOfflineMode method to switch the work-offline mode on and off. When you call setOfflineMode(false), the library synchronizes all offline edits with the server automatically. Note that calling setOfflineMode when the device isn’t connected to the internet has no effect.
    - (IBAction) switchOfflineMode:(id)sender {
        
        // Get current status
        SyncNetworkStatus networkStatus = [synchronization getNetworkStatus];
        
        if ( networkStatus == SyncOffline) {
            
            UIAlertController *myAlertController = [UIAlertController alertControllerWithTitle:@"Sorry!"
                message:@"You can't switch to Work Offline mode when there isn't an internet connection."
                preferredStyle:UIAlertControllerStyleAlert ];
            UIAlertAction* okBtn = [UIAlertAction
                                          actionWithTitle:@"OK"
                                          style:UIAlertActionStyleDefault
                                          handler:^(UIAlertAction * action)
                                          {
                                              [myAlertController dismissViewControllerAnimated:YES
                                                                                    completion:nil];
                                          }];
            [myAlertController addAction: okBtn];
            [self presentViewController:myAlertController
                               animated:YES
                             completion:nil];
        }
        else {
            
            [omcSynchronization setOfflineMode:(networkStatus == SyncOnline)];
            
            // Get updated status
            networkStatus = [omcSynchronization getNetworkStatus];
            
            if ( networkStatus == SyncOfflineTest ) {
                
                lblNetworkStatus.text = @"Working offline.";
                
            }
            else {
                
                lblNetworkStatus.text = @"";
            }
        }
    }
  2. Add code to display a list of the offline edits. You use the OMCSynchronization LoadOfflineResourcesAsync() method to get the list. In this example, the mobile app accesses only the incidents endpoint and all items in the offline edits list are of type MobileObject.
    [omcSynchronization loadOfflineResourcesOnSuccess:^(NSArray *mobileResources) {
            
        for ( OMCMobileResource* aResource in mobileResources ) {
                // Put your code to add the incident to the display list here
        }
            
    } onError:^(NSError *error) {
            
         // Handle error here.
            
    }];
  3. Add a button to discard all offline edits. Use code like the following to discard the edits.
    // Discard all offline edits only.
    // Resources remain in the cache with their persistent state (that is, the server version).
    [omcSynchronization discardOfflineUpdatesOnError:^(NSError *error) {
        // Handle error here 
    } 
  4. The previous step shows how to discard all offline updates. You also can discard offline updates for a specific resource. You call the resource's reloadResource method with the discardOfflineUpdates parameter set to YES and the reloadFromService parameter set to NO.

    In the following code example, arraySelectedResourcesToDiscardOfflineEdits is a list of resources that were edited while offline and were selected for discarding the edits.

    for ( int index = 0; index <  arraySelectedResourcesToDiscardOfflineEdits.count; index++ ) {
                
        OMCMobileResource* aResource = [arraySelectedResourcesToDiscardOfflineEdits objectAtIndex:index];
                
        [aResource reloadResource:YES
                reloadFromService:NO
                        onSuccess:^(id mobileResource) {
                                    
                            // Offline edits succesfully discarded from a resource.
                        }];
    }

Make Custom APIs Synchronizable

If your mobile app uses the Synchronization library to access a custom API offline, then that API should follow the sync-compatibility guidelines and should return data in a sync-compatible format. You also need to consider whether to configure synchronization policies for some or all of its resources.

The steps to design and implement a synchronization-compatible custom API are summarized in the next sections. For more detailed information, see:

Design a Synchronization-Compatible API

When you use the API Designer to create your custom API, follow these guidelines to ensure that your API is synchronization compatible.

  • The resource name should alternate between plural nouns and singular resource identifiers (rid). For example: /items/{rid}/subitems/{rid}/.

  • For pagination, use the limit and offset query parameters so that the Synchronization library uses paged downloads correctly. If you don’t need to support pagination, then you don’t need to specify these parameters.

  • Use the orderBy query parameter to specify sorting. For example: orderBy=propA,propB:desc,propC:asc.

  • The API must contain all the necessary endpoints to support data synchronization. For example, if you have an endpoint that returns a collection, then you must also have an endpoint that returns a specific item in the collection.

Implement a Synchronization-Compatible API

When you use implement your custom API, follow these guidelines to ensure that your API us synchronization compatible.

  • For GET requests, use the custom code SDK’s setItem and addItem methods in your API’s custom code to return data in a format that enables the Synchronization library to more easily cache and synchronize the data in the client’s local cache. Responses must include the Oracle-Mobile-Sync-Resource-Type header, and, for single items, the ETag header.

  • For PUT and DELETE requests, your code must honor the If-Match header as follows:

    • If the header contains an ETag value, and that value doesn’t match the ETag on the server, then the code must not update or delete the item and must return a 412 HTTP response status (precondition failed) to indicate that the ETag does not match the server-side object’s ETag.

    • If the header contains a value of * (asterisk), then the server-side's object must be replaced by the request object (or deleted for a DELETE request).

  • For PUT requests, responses must include the Oracle-Mobile-Sync-Resource-Type and ETag headers. If the item was added, then it must include the Location header. For example Location: /mobile/custom/incidentreport/incidents/1.

  • For POST requests, responses must include the Oracle-Mobile-Sync-Resource-Type, Location, and ETag headers.

  • When you need to control data caching from the server side, use the Oracle-Mobile-Sync-Evict, Oracle-Mobile-Sync-Expires, and Oracle-Mobile-Sync-No-Store headers to override client side configuration.

Configure Synchronization Policies for the Custom API

When you define the synchronization policies in the configuration file, consider the custom API’s resources that you’ll access, and determine which, if any, need special synchronization policy configuration.

Say, for example, that your default fetch policy is FETCH_FROM_SERVICE_ON_CACHE_MISS. The custom API might have a resource for which the mobile app always needs the most current data. In that case, you can use the configuration file to specify the FETCH_FROM_SERVICE_IF_ONLINE fetch policy for that specific resource. Note that you can define synchronization policies at the default level and the resource level, and that you can override these programmatically.

Synchronization Policies

The Synchronization library uses several types of synchronization policies:

  • Conflict Resolution Policies define how to handle offline edits if the server’s version changed after the initial data was fetched from the server. For example, if another client updated a resource, you might want the app’s updates to overwrite the other client’s update.

  • Eviction Policies designate when to delete expired resources in the local cache. For example, you might want the app to delete all expired resources when the app starts. Expiration and eviction policies work together to keep stale resources from cluttering the cache. You can also use them to prevent users seeing out-of-date data and, by inference, potentially harmful data. Note that these policies apply only to resources in the local cache, not to server-side resources.

  • Expiration Policies define how and when the Synchronization library marks resources stored in the local cache as out-dated or stale. For example, you might want all the resources to expire when the app is restarted so that the app fetches the latest version of a resource from the server the first time the app uses it in that session. The expiration policy only marks data, allowing you the option to display stale data if the app is offline. To delete data, use the eviction policy.

  • Fetch Policies define how the Synchronization library determines whether to retrieve resources from the local cache or from the server. For example, if the resource changes frequently, you might choose to always retrieve it from the server unless the client is offline.

  • Update Policies define what to do if the app modifies resources when the device is offline. For example, you might want the app to put all changes that are made while the device is offline in a queue and then synchronize the changes with the server when the device goes online again.

In addition to configuring the synchronization policies, you also can configure the cache settings for a mobile backend. You can configure the maximum size of the cache and you can specify when and how to perform background cache refreshes. See Synchronization Configuration Elements.

You can specify synchronization policies for custom API resources at several levels:

  • In the app’s configuration file, you can specify default synchronization policies for all custom API endpoints that the library accesses through a specific mobile backend.

  • In the app’s configuration file, you can specify synchronization policies for specific custom API endpoints.

  • In the custom API implementation, you can specify a resource’s synchronization policies in a response header.

  • In the app, you can specify a resource’s synchronization policies when you fetch the data.

  • In the app, you can specify a resource’s synchronization policies when you add, update, or delete the resource.

When the Synchronization library fetches a resource from the server, it sets the resource's synchronization policies according to your configuration, and then saves those policies with the resource. When you configure a policy at more than one level, the library uses precedence rules to determine which policy level to use. For example, a response-header policy setting takes precedence over a fetch builder’s policy setting. If a policy isn’t set at the response header or fetch builder level, then the library uses the policy’s setting from the configuration file. First, the library looks for the policy setting for the path that matches the fetch builder's endpoint. When there isn’t a policy for the endpoint, then it uses the configuration file’s default policy. If a policy isn’t specified at any level, then the Synchronization library’s hard-coded default policy is used. The actual rules are somewhat more complex than summarized here. For complete details see Synchronization Policy Levels and Precedence.

When the library does an automatic refresh, it always uses the FETCH_POLICY_FETCH_FROM_SERVICE fetch policy. For all other policies, the refresh process honors the response header values, if present, and, when not present, it uses the policies that were saved with the resource.

When you fetch a resource and the library uses the resource from the cache instead of from the server, then the resource's policies are not necessarily the policies that you configured for the object's endpoint. For example, if the resource was fetched using a fetch collection builder, then the resource's policies are the collection endpoint’s policies and not the object’s endpoint policies. Thus, you can't be sure what the resource's policies are. A cached resource’s policies depend on whether it was originally fetched from the server as part of a collection, as an object, or as part of a refresh.

Define Synchronization Policies and Cache Settings in the Configuration File shows how to configure default policies for the mobile backend and for endpoints (paths). Define Synchronization Policies and Cache Settings in a Response Header shows how a custom API can use headers to control whether the response is cached, when it should expire in the local cache, and when it should be evicted. The following platform-specific topics show how to get and change a fetch builder’s policies and get and change a mobile resource’s policies programmatically:

Video: Introduction to the Data Offline & Sync Policies

If you want a high-level understanding of how to use synchronization policies to drive data offline and synchronization capabilities, take a look at this video:

Synchronization Policy Options

Here are the Synchronization library’s policy options for each policy type.

Conflict Resolution Policies

Conflict resolution policies define what to do if, when updating a resource, it’s discovered that the server version was updated after it was last requested. Say, for example, that the client app retrieved a resource on startup. Soon after, someone else updated the resource on the server. If the resource is then updated on the client app, you might want the client updates to overwrite the updates made by someone else.

Policy Description

CLIENT_WINS

Instructs the Synchronization library to overwrite the server’s version with the local version regardless of whether there is a conflict.

PRESERVE_CONFLICT

Instructs the Synchronization library to not overwrite the server’s version with the local version if there’s a conflict. The edited version is kept in the offline edits in the local cache, and the mobile app is responsible for handling the conflict, such as programmatically merging the two versions.

SERVER_WINS

Instructs the Synchronization library to not overwrite the server’s version with the local version if there’s a conflict. The edited version is removed from the offline edits in the local cache.

Eviction Policies

Eviction policies designate when expired resources in the local cache will be deleted. For example, you could set the eviction policy to EVICT_ON_EXPIRY_AT_STARTUP so expired items are deleted when the app starts. Keep in mind that if a user didn’t use the app for several days and it’s offline when it starts, the local cache could get cleared.

These policies apply to resources in the local cache only, not to server-side resources.

Policy Description

EVICT_ON_EXPIRY_AT_STARTUP

Instructs the Synchronization library to delete expired resources from the local cache when the client application restarts, and update the local cache with the server copy the next time it's called by the client application. This can result in an empty cache, but this is appropriate if the latest resource is required.

MANUAL_EVICTION

Instructs the Synchronization library that resources can’t be deleted from the local cache automatically. To evict resources manually, use an API.

Expiration Policies

Expiration policies define how and when the Synchronization library marks resources stored in the local cache as out-dated or stale. For example, if your resources change frequently, then you can set the policy to EXPIRE_ON_RESTART to ensure that the local cache gets cleared periodically, and thus does not become too large.

Policy Description

EXPIRE_ON_RESTART

Instructs the Synchronization library to mark a resource as expired when the client application restarts. The Synchronization library updates the local cache with the latest version from the server the next time it's called by the client application.

EXPIRE_AFTER

Instructs the Synchronization library to mark resources as expired after the specified time (in seconds) set for the expireAfter parameter. When you use the EXPIRE_AFTER policy, you must set a value for the expireAfter property.

NEVER_EXPIRE

Instructs the Synchronization library that resources in the local cache can’t be marked as expired.

Fetch Policies

Fetch policies define how the Synchronization library determines whether to retrieve resources from the local cache or from the server. For example:

  • If your data doesn’t change often, like a contact’s photo, then a good choice for the fetch policy is FETCH_FROM_SERVICE_ON_CACHE_MISS_OR_EXPIRY with an EXPIRE_AFTER expiration policy set to a suitable timeout.

  • If data will change very frequently and you always want the most current data, but cached data is acceptable if the user is offline, then use FETCH_FROM_SERVICE_IF_ONLINE.

Note that setting the noCache property to true in the configuration file, as described in Synchronization Configuration Elements, tells the Synchronization library to ignore fetch policies and to not add data to the local cache.

Policy Description

FETCH_FROM_CACHE

Instructs the Synchronization library to fetch resources from the local cache only, not from the server. Because the Synchronization library retrieves resources directly from the cache, this policy can be carried out whether the client application is online or offline.

If a resource is not in the local cache, then the Synchronization library returns null.

FETCH_FROM_SERVICE

Instructs the Synchronization library to always fetch resources directly from the server, not from the local cache. The library can only apply this policy when the client application is online.

If the app is offline, the Synchronization library returns null.

FETCH_FROM_SERVICE_IF_ONLINE

Instructs the Synchronization library to fetch resources from the server when the client application is online, and to fetch them from the local cache when the app is offline.

FETCH_FROM_SERVICE_ON_CACHE_MISS

Instructs the Synchronization library to fetch resources from the local cache if it is present.

If a collection is empty, or if the requested object isn’t in the local cache, then the Synchronization library fetches it from the server. If the app is offline, then the Synchronization library returns null.

FETCH_FROM_SERVICE_ON_CACHE_MISS_OR_EXPIRY

Instructs the Synchronization library to fetch resources from the local cache if they are present and not expired. Make sure to set expireAfter parameter to a suitable time period.

If a collection is empty or has expired, or if the resource isn’t in the local cache or has expired, then the Synchronization library fetches it from the server. If the app is offline, then it returns null.

FETCH_FROM_CACHE_SCHEDULE_REFRESH

Instructs the Synchronization library to fetch resources from the local cache and schedule a background refresh to update the cache with the latest version from the server.

If a resource is not in the local cache, then the Synchronization library returns null.

FETCH_WITH_REFRESH

Instructs the Synchronization library to fetch resources from the local cache if they exist and are not expired, and schedule a background refresh to update the cache with the latest version from the server.

If a resource is not in the local cache or has expired, then the Synchronization library fetches it directly from the server. If the app is offline, then it returns null.

Update Policies

Update policies define what the app should do if a resource is updated when the client app is offline.

Policy Description

UPDATE_IF_ONLINE

If the client app is offline when the update request is sent, then the Synchronization library returns an error.

QUEUE_IF_OFFLINE

If the client app is offline when the update request is sent, then the Synchronization library queues the operation and updates the local cache when the client app is back online.

Video: Deep-Dive into the Data Offline & Sync Policies

If you want an overview of the ways you can configure synchronization policies, which methods take precedence, and the outcomes of the various policies, take a look at this video:

Synchronization Policy Levels and Precedence

As described in Synchronization Policy Options, there are several policy types that you can configure for custom APIs. You can configure these at the following levels, which are listed in order of precedence, from highest to lowest. Note that the order of precedence applies to both fetch and save calls to a mobile endpoint and requestWithURI calls to a synchronization object.

  • Response-level policies: The server can use HTTP response headers to transmit expiration and eviction policies, as described in Define Synchronization Policies and Cache Settings in a Response Header. The server also can use a header to instruct the client to not cache a response. These policies take precedence over policies set for all other levels.

  • Request-level policies: For requests made through an OMCMobileEndpoint, you can call the fetch builder’s setPolicy method to set a policy at the request level. For requests made using the requestWithURI method, you can use the SyncPolicy object to set policies. Request-level policies take precedence over policies set at the resource and mobile-backend levels.

  • Resource-level policies: In the configuration file, you can define a set of policies and associate the set with a resource path (URL). You can associate the set with a specific endpoint, or you can use wildcard characters to associate the set with a resource hierarchy (/* applies to all resources at the same level, and /** applies to all resources at the same level and any nested levels), as described later in this section. These policies take precedence over policies that are set at the mobile-backend level.

    When a policy type is defined for more than one resource level, then the precedence is:
    • A synchronization policy type that is defined for a specific endpoint takes precedence over the same policy type setting for a path that has wildcard characters. For example, if the URL is www.baseuri.com/mobile/custom/incidentreport/incidents, and an eviction policy is set for both /mobile/custom/incidentreport/incidents and /mobile/custom/incidentreport/incidents/*, then the eviction policy for /mobile/custom/incidentreport/incidents takes precedence.

    • Policies that are defined for a path that has the /* wildcard take precedence over policies for a path with the /** wildcard. For example, if the URL is /mobile/custom/incidentreport/incidents/1, and an eviction policy is set for both /mobile/custom/incidentreport/incidents/* and /mobile/custom/incidentreport/incidents/**, then the eviction policy for /mobile/custom/incidentreport/incidents/* takes precedence.

      For information about setting resource-level policies, see Synchronization Configuration Elements.

  • Mobile backend-level default policies. You can override the default policies at the request, response, and resource levels. These settings take precedence over the Synchronization library default settings. For information about setting mobile backend-level default policies, see Synchronization Configuration Elements.

  • Synchronization library default settings: For custom APIs, if a policy is not set at the request, resource, or mobile-backend level, then the Synchronization library default setting is used.

    Here are the default policy settings:

    Setting Synchronization Library Default Value
    conflictResolutionPolicy PRESERVE_CONFLICT
    evictionPolicy MANUAL_EVICTION
    expirationPolicy EXPIRE_ON_RESTART
    expireAfter Maximum integer value
    fetchPolicy FETCH_FROM_SERVICE_IF_ONLINE
    noCache false
    updatePolicy QUEUE_IF_OFFLINE
Define Synchronization Policies and Cache Settings in the Configuration File

You can define the synchronization policies and cache settings programmatically, or you can use a configuration file. You typically define the policies and cache settings in the configuration file for the following reasons:

  • You can change a policy without needing to change code.

  • You can view all your policies in one place.

  • If you access the same resource from several places in your code, you can ensure that all accesses use the same policies.

The name of the configuration file differs by platform:

  • Android: /assets/oracle_mobile_cloud_config.xml

  • iOS: OMC.plist

  • To configure the Synchronization library for the custom API resources that the backend accesses, add the elements described in the the next section to the backend's synchronization element in the configuration file.
Synchronization Configuration Elements

There are two types of backend synchronization elements that you can add to a configuration file: cache settings and synchronization policy settings. You can configure the policy settings at the resource level and the backend level (default level).

Here's an example of the synchronization section from an OMC.plist file for iOS.

Cache Settings

To configure the cache settings for the mobile backend, add these elements in any order directly under the mobile backend’s synchronization element. These settings affect both custom API and storage resources.

Key Description Default
maxStoreSize

The maximum size of the local cache in megabytes. The Synchronization library stops storing resources when it reaches this limit.

100
periodicRefreshPolicy

Names the policy that instructs the Synchronization library when to refresh cached resources. Use this attribute for background refreshes. You can set this to one of the following options:

  • PERIODIC_REFRESH_POLICY_REFRESH_NONE

  • PERIODIC_REFRESH_POLICY_REFRESH_EXPIRED_ITEM_ON_STARTUP

  • PERIODIC_REFRESH_POLICY_PERIODICALLY_REFRESH_EXPIRED_ITEMS

PERIODIC_REFRESH_POLICY_REFRESH_NONE
periodicRefreshInterval

Sets the interval, in seconds, for refreshing cached resources in the background. The interval should be appropriate to the policy named by the periodicRefreshPolicy attribute.

When the periodicRefreshPolicy is PERIODIC_REFRESH_POLICY_PERIODICALLY_REFRESH_EXPIRED_ITEMS, then the default is 120.
Synchronization Policy Settings

You can add the following synchronization policy settings to the configuration file at the resource and backend default levels.

  • conflictResolutionPolicy

  • expirationPolicy

  • expireAfter

  • evictionPolicy

  • fetchPolicy

  • noCache

Resource-Level Configuration

Resource-level synchronization policies affect the resources that match the paths you configure in the policies node (array) for the sychronization element.

You use the path element to identify the resource to associate the policy set with. You can begin your path with or without the forward slash (/). You can use the path to specify a policy set for a specific endpoint, or you can use wildcard characters to associate the policy set with a hierarchy of resources:

  • If there are no wildcard characters, then the request URL must match the string exactly. For example, if <path> is set to /mobile/custom/incidentreport/incident then www.baseuri.com/mobile/custom/incidentreport/incident matches, but www.baseuri.com/mobile/custom/incidentreport/incidents does not.

  • /* matches 0 or more characters after the value in <Path> but does not include lower resources in the hierarchy in the wildcard matching. For example, if <Path> is set to /mobile/custom/incidentreport/incidents/* then both www.baseuri.com/mobile/custom/incidentreport/incidents/report and www.baseuri.com/mobile/custom/incidentreport/incidents/id match, but www.baseuri.com/incidentreport/incidents/id/attachments does not.

  • /** matches 0 or more characters after the value in <Path> including resources lower in the hierarchy. For example, if <Path> is set to /mobile/custom/incidentreport/incidents/**, then the following match:

    • www.baseuri.com/mobile/custom/incidentreport/incidents

    • www.baseuri.com/mobile/custom/incidentreport/incidents/id

    • www.baseuri.com/mobile/custom/incidentreport/incidents/id/attachments

Here’s an example of setting resource-level policies in an OMC.plist file.

<key>synchronization</key>
<dict>
  ...
  <key>policies</key>
  <array>
    <dict>
      <key>path</key>
      <string>/mobile/custom/incidentreport/technicians/**</string>
      <key>fetchPolicy</key>
      <string>FETCH_FROM_SERVICE_IF_ONLINE</string>
      <key>expirationPolicy</key>
      <string>EXPIRE_ON_RESTART</string>
      <key>evictionPolicy</key>
      <string>MANUAL_EVICTION</string>
      <key>conflictResolutionPolicy</key>
      <string>SERVER_WINS</string>
    </dict>
  ...
</dict>
Backend-Level Configuration

Backend-level configurations set the backend's default synchronization policies, and are configured in the defaultPolicy node (array) for the synchronization element. You should configure a default for each policy type.

Android Example Configuration File

This Android example is an excerpt from the oracle_mobile_cloud_config.xml file.

<mobileBackends>
    <mobileBackend>
      ...
      <synchronization>
          <maxStoreSize>100</maxStoreSize>
          <periodicRefreshPolicy>PERIODIC_REFRESH_POLICY_PERIODICALLY_REFRESH_EXPIRED_ITEMS</periodicRefreshPolicy>
          <periodicRefreshInterval>120</periodicRefreshInterval>
          <policies>
              <policy>
                  <path>/mobile/custom/incidentreport/technicians/**</path>
                  <fetchPolicy>FETCH_FROM_SERVICE_IF_ONLINE</fetchPolicy>
                  <expirationPolicy>EXPIRE_ON_RESTART</expirationPolicy>
                  <evictionPolicy>MANUAL_EVICTION</evictionPolicy>
                  <conflictResolutionPolicy>SERVER_WINS</conflictResolutionPolicy>
              </policy>
              <policy>
                  <path>/mobile/custom/incidentreport/incidents</path>
                  <fetchPolicy>FETCH_FROM_SERVICE_ON_CACHE_MISS_OR_EXPIRY</fetchPolicy>
                  <expirationPolicy>EXPIRE_ON_RESTART</expirationPolicy>
                  <evictionPolicy>EVICT_ON_EXPIRY_AT_STARTUP</evictionPolicy>
                  <conflictResolutionPolicy>SERVER_WINS</conflictResolutionPolicy>
                  <updatePolicy>QUEUE_IF_OFFLINE</updatePolicy>
                  <expireAfter>300</expireAfter>
              </policy>
          </policies>
          <defaultPolicy>
              <fetchPolicy>FETCH_FROM_SERVICE_ON_CACHE_MISS</fetchPolicy>
              <evictionPolicy>EVICT_ON_EXPIRY_AT_STARTUP</evictionPolicy>
              <expirationPolicy>EXPIRE_AFTER</expirationPolicy>
              <expireAfter>600</expireAfter>
              <conflictResolutionPolicy>CLIENT_WINS</conflictResolutionPolicy>
              <noCache>false</noCache>
          </defaultPolicy>
      </synchronization>
    </mobileBackend>
</mobileBackends>
iOS Example Configuration File

This iOS example is an excerpt from the OMC.plist file.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>mobileBackends</key>
  <dict>
    <key>myBackend/1.0</key>
    <dict>  
      <key>synchronization</key>
      <dict>
        <key>maxStoreSize</key>
        <integer>100</integer>
        <key>periodicRefreshPolicy</key>
        <string>PERIODIC_REFRESH_POLICY_PERIODICALLY_REFRESH_EXPIRED_ITEMS</string>
        <key>periodicRefreshInterval</key>
        <integer>120</integer>
        <key>policies</key>
        <array>
          <dict>
            <key>path</key>
            <string>/mobile/custom/incidentreport/technicians/**</string>
            <key>fetchPolicy</key>
            <string>FETCH_FROM_SERVICE_IF_ONLINE</string>
            <key>expirationPolicy</key>
            <string>EXPIRE_ON_RESTART</string>
            <key>evictionPolicy</key>
            <string>MANUAL_EVICTION</string>
            <key>conflictResolutionPolicy</key>
            <string>SERVER_WINS</string>
          </dict>
          <dict>
            <key>path</key>
            <string>/mobile/custom/incidentreport/incidents</string>
            <key>fetchPolicy</key>
            <string>FETCH_FROM_SERVICE_ON_CACHE_MISS_OR_EXPIRY</string>
            <key>expirationPolicy</key>
            <string>EXPIRE_ON_RESTART</string>
            <key>evictionPolicy</key>
            <string>EVICT_ON_EXPIRY_AT_STARTUP</string>
            <key>conflictResolutionPolicy</key>
            <string>PRESERVE_CONFLICT</string>
            <key>updatePolicy</key>
            <string>QUEUE_IF_OFFLINE</string>
          </dict>
        </array>
        <key>defaultPolicy</key>
        <dict>
          <key>fetchPolicy</key>
          <string>FETCH_FROM_SERVICE_ON_CACHE_MISS</string>
          <key>evictionPolicy</key>
          <string>EVICT_ON_EXPIRY_AT_STARTUP</string>
          <key>expirationPolicy</key>
          <string>EXPIRE_AFTER</string>
          <key>expireAfter</key>
          <integer>600</integer>
          <key>conflictResolutionPolicy</key>
          <string>CLIENT_WINS</string>
          <key>updatePolicy</key>
          <false/>
        </dict>
      </dict>
      ...
</dict>
</plist>
Define Synchronization Policies and Cache Settings in a Response Header

When you implement a custom API, you can fine tune caching for a response by defining synchronization policies or basic cache settings in response headers.

To specify the basic synchronization and cache settings for a REST resource use the following optional HTTP headers: :

Header Description

Oracle-Mobile-Sync-No-Store

If set to true, the client does not cache the returned resource.

Oracle-Mobile-Sync-Evict

Specifies the date and time after which the expired resource should be deleted from the local cache. Uses RFC 1123 format, for example EEE, dd MMM yyyyy HH:mm:ss z for SimpleDateFormat.

The following synchronization policies are set for the resource object that is created from the response:

  • Eviction policy: EVICT_ON_EXPIRY_AT_STARTUP

  • Expiration policy: EXPIRE_AFTER with the expireAfter property set to date and time provided in the header value

    .

Oracle-Mobile-Sync-Expires

Specifies when the returned resource will be marked as expired. Uses RFC 1123 format, for example EEE, dd MMM yyyyy HH:mm:ss z for SimpleDateFormat.

Get Cache Hits and Misses

The Synchronization library tracks cache hits and detects if the returned result came from the cache. Use these OMCSynchronization methods to get data about cache hits and misses:

  • cacheHitCount: Returns the number of cache hits.

  • cacheMissCount: Returns the number of cache misses.

How Synchronization Works with the Storage APIs

When your mobile app accesses the Storage APIs, the client SDK automatically works with the Storage library to refresh and synchronize the storage objects in the local cache. You don’t need to add any code to enable synchronization with storage.

The client SDK enforces the following synchronization policies for the Storage APIs:

  • Conflict resolution policy: SERVER_WINS

  • Eviction policy: EVICT_ON_EXPIRY_AT_STARTUP

  • Expiration policy: EXPIRE_AFTER 86400 seconds (24 hours).

    You can use the Sync_CollectionTimeToLive environment policy to override the number of seconds after which a Storage object expires. This value is conveyed to the Storage library through the Oracle-Mobile-Sync-Expires response header.

  • Fetch policy: FETCH_FROM_SERVICE_IF_ONLINE

  • Update policy: QUEUE_IF_OFFLINE

Just as with the custom API resources, you can use the configuration file to override the default cache settings for storage resources on a mobile backend basis.

The default cache settings are:

  • Maximum storage size in the local cache: 100 MB

  • Periodic refresh policy: Don’t automatically refresh cached resources periodically