SDK for Android Developer's Guide

Using CLE2 Offline

You can perform search requests online to the server or offline to the local device. To enable offline searches against local data, HERE SDK provides different ways for you to pre-fetch data.

Use offline mode as much as possible since it provides the following advantages:

  • Resilience to network instability.
  • More efficient use of network bandwidth. Instead of sending one request per object you can aggregate requests locally and transmit data in batches.
  • Savings in network bandwidth. Your app can cache and update data that is only near the user's current location or pre-download a layer only when a WiFi network becomes available.
  • Potentially making the application more responsive and improving the user experience and interface interactions since the data is already available locally on the device.
  • Create or modify geometries with HERE SDK and then store them locally, effectively using the HERE SDK as a data source and a storage of information.

The offline CLE2 feature is designed to be simple to use. Since all database synchronization and geospatial-related complexities are handled by the SDK, you can focus on other parts of app development.

Querying the Local Storage

To search using locally stored data, set the connectivity mode to OFFLINE for a desired CLE2Request and perform the request:

CLE2ProximityRequest proximitySearch =
  new CLE2ProximityRequest("HERE_TEST", map.getCenter(), 1000);
proximitySearch.setConnectivityMode(CLE2Request.CLE2ConnectivityMode.OFFLINE);

proximitySearch.execute(new CLE2Request.CLE2ResultListener() {
  @Override
  public void onCompleted(CLE2Result result, String error) {
    if (!error.equals(CLE2Request.CLE2Error.NONE)) {
      // process search results
    } else {
      // handle error
    }
  }
});

You can configure the search request to hybrid or automatic mode indicating that if during an online request the connection drops or there is a network error, then the request automatically falls back to an offline operation. You can see whether the search was performed online or offline by checking the connectivity mode that was used to perform the search. This can be done by calling getConnectivityModeUsed() on the CLE2Result object.

CLE2ProximityRequest proximitySearch
  = new CLE2ProximityRequest("HERE_TEST", map.getCenter(), 1000);
proximitySearch.setConnectivityMode(CLE2Request.CLE2ConnectivityMode.AUTO);

proximitySearch.execute(new CLE2Request.CLE2ResultListener() {
  @Override
  public void onCompleted(CLE2Result result, String error) {
    if (!error.equals(CLE2Request.CLE2Error.NONE)) {
      // check if data came from online or offline search
      boolean isDataFromServer = result.getConnectivityModeUsed() == CLE2Request.CLE2ConnectivityMode.ONLINE;
    } else {
      // handle error
    }
  }
});
      

Ways to Populate the Local Storage

By default, the offline feature is disabled and the local storage contains no data. There are three ways to add geometries to make them available for offline searches:

  1. Caching search results after performing one or more requests such as CLE2ProximityRequest. Note that you cannot cache attribute requests.
  2. Downloading one or more layers.
  3. Direct insertion of data into the local database.

After populating the database, you can query for the data in offline mode as usual by switching the connectivity mode of the respective request to CLE2ConnectivityMode.OFFLINE.

CLE2DataManager and CLE2Task

The CLE2DataManager object is the central interaction point with the local storage. With it, it is possible to:

  • Download all geometries of a specific layer
  • Check how many geometries are currently stored in total, or in a specific layer
  • Delete geometries belonging to a specific layer
  • Purge the local storage by deleting all items
  • Create, update, or delete a local or remote geometry

All the operations relating to data management that CLE2DataManager exposes make use of a CLE2Task that represent a unit of work. Since all of the data management operations involve database access, network communication or both, CLE2Task runs asynchronously. You can obtain a CLE2Task object from CLE2DataManager.

With CLE2Task, you can:

  • Pass it to other parts of your code. CLE2Task is a self-contained unit of work.
  • Subscribe for results of the operation. Multiple subscribers are supported and they are called on the main thread.
  • Start execution of the task. Tasks are reusable. You can run them repeatedly multiple times, which makes retrying a failed operation very easy.
  • Cancel a running task.
  • Check if the task is started.
  • Check if the task has finished.
  • Wait for the task to finish.
  • Retrieve the status of a finished operation directly from the task (check for errors).
  • Retrieve the result of a successfully finished operation directly from the task.

Storing Data by Caching Search Results

When caching is enabled in a CLE2Request, any returned geometries are automatically stored locally. To activate it, call setCachingEnabled(true) before performing the request:

// set query
String query = "CITY=='Berlin'";
request.setQuery(query);

// set Geometry type
request.setGeometry(CLE2GeometryType.LOCAL);

// to cache response
request.setCachingEnabled(true);

request.execute(new CLE2Request.CLE2ResultListener() {
  @Override
  public void onCompleted(CLE2Result result, String error) {
    if (!error.equals(CLE2Request.CLE2Error.NONE)) {
      // request succeeded, which means that the results are now stored locally
    } else {
      // handle error
    }
  }
});

// Now some geometries are in local storage.
// At a later point in time if you'd like to make an offline search,
// switch the connectivity mode to offline.

Storing Data by Downloading Layers

The second option is to use CLE2DataManager to insert data to the local storage using newDownloadLayerTask() method.

The following is an example of how to download a whole layer from the CLE2 server:

CLE2DataManager.getInstance().newDownloadLayerTask("MYLAYER").start(
  new CLE2Task.Callback<CLE2OperationResult>() {
    @Override
    public void onTaskFinished(CLE2OperationResult result, CLE2Error error) {
      if (error.getErrorCode() == CLE2ErrorCode.NONE) {
        // download succeeded
      } else {
        // handle download error
      }
    }
  });

It is also possible to delete individual layers from local storage or completely wipe out all data stored locally:

// fire and forget method of running tasks (no callback)
CLE2DataManager.getInstance().newDeleteLayersTask(Arrays.asList(new String[]{"MYLAYER"}), StorageType.LOCAL).start();

// by specifying StorageType.REMOTE, it is possible to delete the layers from CLE2 server, so be careful

// wipe out all local data
CLE2DataManager.getInstance().newPurgeLocalStorageTask().start(new CLE2Task.Callback<CLE2OperationResult>() {
  @Override
  public void onTaskFinished(CLE2OperationResult result, CLE2Error error) {
    if (error.getErrorCode() == CLE2ErrorCode.NONE) {
      // notify user that all his data is gone
    }
  }
});

Storing Data by Inserting Geometries

You can generate location-based data and persist it locally, remotely, or both by using newGeometryTask() method from CLE2DataManager class. This factory method returns a CLE2Task object that can be used to start, cancel, or fetch results of operations at any given time.

public CLE2Task<CLE2OperationResult> newGeometryTask(
    OperationType operationType,
    String layerId,
    List<CLE2Geometry> geometryData,
    StorageType storageType)
  • The first parameter in this method describes the operation type, which can be one of the following:
    • OperationType.CREATE
    • OperationType.UPDATE
    • OperationType.DELETE
    Note that querying for geometries is accomplished through the respective CLE2Request specialized classes, so there is no "read" opreation here.
  • The second parameter is the layer the operation should be applied to.
  • The third parameter is a list with the geometries themselves.
  • The last parameter defines whether to operate on local storage (StorageType.LOCAL), or remote storage (StorageType.REMOTE) using the HERE CLE2 server.
Note: While this section covers usage of this method for the local option, all operations (create, update, delete) can also be used to change remote layers.

The following is an example on how to create a geometry and store it locally:


final int geometryCount = 100;
Random rand = new Random();
List<CLE2Geometry> geometries = new ArrayList<>(geometryCount);

// generate random point across the globe
for (int i = 0; i < geometryCount; i++) {
  GeoCoordinate newPoint = new GeoCoordinate(
      (rand.nextFloat()) * 180 - 90,
      (rand.nextFloat()) * 360 - 180);

  CLE2PointGeometry point = new CLE2PointGeometry(newPoint);
  point.setAttribute("i", Integer.toString(i));
  point.setAttribute("COLOR", rand.nextInt() % 2 == 0 ? "BLUE" : "RED");
  geometries.add(point);
}

// create task for storing new geometries locally
// if the layer does not exist already, it is created,
// otherwise the geometries are added to existing layer
CLE2Task<CLE2OperationResult> createLocal = CLE2DataManager.getInstance().newGeometryTask(
    OperationType.CREATE, "RED_VS_BLUE", geometries, StorageType.LOCAL);

createLocal.start(new CLE2Task.Callback<CLE2OperationResult>() {
  @Override
  public void onTaskFinished(CLE2OperationResult result, CLE2Error error) {
    if (error.getErrorCode() == CLE2ErrorCode.NONE) {
      // success
    }
  }
});

Uploading a Local Layer

It is possible to upload a locally stored layer to the server. Since this requires two operations (fetch from local storage and upload), it's a good candidate to run individual tasks in synchronous manner to avoid callback hell creeping in. Of course, this needs to be done on its own thread, for example using AsyncTask.


AsyncTask<String, Void, Boolean> localToRemoteAsyncTask = new AsyncTask<String, Void, Boolean>() {
  @Override
  protected Boolean doInBackground(String... strings) {
    CLE2DataManager dataManager = CLE2DataManager.getInstance();
    boolean success = false;
    for (String layerId : strings) {
      // grab data from local storage
      CLE2Task<List<CLE2Geometry>> fetchLocalTask = dataManager
          .newFetchLocalLayersTask(Arrays.asList(new String[] {layerId}))
          .start()
          .waitForResult(5, TimeUnit.SECONDS);

      success = fetchLocalTask.isFinished()
          && fetchLocalTask.getError().getErrorCode() == CLE2ErrorCode.NONE;
      if (!success) {
        break;
      }

      // upload the data to the server creating a new layer or replacing existing one
      CLE2Task<CLE2OperationResult> uploadTask = dataManager
          .newUploadLayerTask(layerId, fetchLocalTask.getResult())
          .start()
          .waitForResult(15, TimeUnit.SECONDS);

      success = uploadTask.isFinished()
          && uploadTask.getError().getErrorCode() == CLE2ErrorCode.NONE;
      if (!success) {
        break;
      }
    }
    return success;
  }
};

Data Management Considerations

The following are a few tips to help with data management when using CLE2 in an offline context.

Local-only Geometries

All CLE2Geometry objects have the following properties:

  1. Geometry ID, accessible with getGeometryId()
  2. Locality flag, accessible with isLocal()

Geometry ID is unique to a layer. If a geometry object has just been created, its geometry ID is null and the locality flag is false.

The locality flag tells whether this geometry belongs to a local context only, meaning it was not retrieved or passed through the CLE2 server. A geometry with a true locality flag has a locally generated unique geometry ID. Otherwise, it contains a server-provided ID. This server-provided ID is not related to the locally generated IDs of geometries stored directly in the database created via newGeometryTask().

Note: The functionality of locally storing geometries without passing through the server is provided so that you do not need to manage data persistence on these objects when a connection is not available.

For simplicity, when saving geometries directly to the local database, keep them using a separate layer name. If at a later desired point in time these geometries should be shared with the server, fetch all local geometries using newFetchLocalLayersTask() method of CLE2DataManager and then upload them either using newUploadLayerTask() or newGeometryTask() with a create operation (OperationType.CREATE). This avoids the requirement to check for the isLocal() property.

By using these concepts you can move geometries to different layers, contexts and use these tools to organize data.

Data Consistency

Use of newUploadLayer() should be primarily restricted to administrative users, because this method deletes all existing geometries in the server and recreates the layer with the provided ones. If the user does not have the latest information for this layer, data loss may occur as it can overwrite another user's upload.

Therefore, for a scenario with continuous or concurrent geometry upload, use newGeometryTask() method with OperationType.CREATE or OperationType.UPDATE. Operating in an "append only" manner or only updating the existing geometries avoids data loss even if users are uploading geometries concurrently to the server.

Current Limitations

Currently, individualized user account management for the CLE2 server is not available. For security reasons, take care to keep your app credentials well hidden. If your application requires a user account access feature, see Service Support to contact us for more details .

Note: Since geospatial queries are the focus of CLE2, HERE SDK does not support attribute searches in offline mode. You can filter the data using one of the geospatial queries (such as proximity) to narrow down the results to a small enough number so that most applications do not suffer performance impact by iterating the geometry’s attributes key-value dictionary to filter results further.