Update Content¶
The UNL SDK allows updating downloaded content to stay synchronized with the latest map data. New map versions are released every few weeks. The update operation supports the RoadMap, ViewStyleLowRes, and ViewStyleHighRes content types. This article focuses on the RoadMap type, as it is the most common use case. Updating styles mostly follows the same process.
The SDK requires all road map content store items to have the same version. It is not possible to have multiple RoadMap items with different versions, meaning partial updates of individual items are not supported.
Based on the client's content version relative to the newest available release, it can be in one of three states, as defined by the EOffboardListenerStatus enum:
| Status | Description |
|---|---|
ExpiredData |
The client version is significantly outdated and no longer supports online operations. All features - such as search, route calculation, and navigation - will function exclusively on the device using downloaded regions, even if an internet connection is available. If an operation like route calculation is attempted in non-downloaded regions, it will fail. An update is mandatory to restore online functionality. Only relevant for EContentType.RoadMap elements. |
OldData |
The client version is outdated but still supports online operations. Features will work online when a connection is available and offline when not. While offline, only downloaded regions are accessible, but online access allows operations across the entire map. An update is recommended as soon as possible. Relevant for all types of content store elements. |
UpToDate |
The client version has the latest map data. Features function online when connected and offline using downloaded regions. No updates are available. Relevant for all types of content store elements. |
The UNL servers support online operations for the most up-to-date version (EOffboardListenerStatus.UpToDate) and the previous version (EOffboardListenerStatus.OldData) of road map data.
📝 INFO
After installation, the app includes a default map version of type
ExpiredData, which contains no available content. Therefore, an update - either manually triggered or performed automatically - is required before the app can be used. Internet access is required for the initial use of the app.
Update process overview¶
- The map update process is initiated by the API user or can be triggered manually.
- The process downloads the newer data in the background while ensuring the full usability of the current (old) map dataset for browsing, search and navigation. The content is downloaded in a close-to-current user position order, i.e. nearby maps are downloaded first.
-
Once all new version data is downloaded:
-
If the update is applied via the
ContentUpdater.apply()method, the update is atomically applied by replacing the files.
If the user's storage size does not allow the existence of the old and new dataset at the same time, the update requires handling:
- The remaining offline maps which did not download because of space constraints can be downloaded by calling
ContentStoreItem.asyncDownload()on each remaining item.
📝 INFO
The auto-update behavior is different between the Navigation SDKs:
- The C++ SDK does not provide an auto-update mechanism
- The Kotlin SDK provides an update mechanism through ContentUpdater
- The iOS SDK does not provide an auto-update mechanism
Check for updates¶
You can listen for map updates by registering a callback with OffboardListener to monitor the status of road map versions.
- Kotlin
- Java
// Kotlin
val contentStore = ContentStore()
val errorCode = contentStore.checkForUpdate(EContentType.RoadMap)
if (errorCode == UnlError.NoError) {
// Check initiated successfully
} else if (errorCode == UnlError.ConnectionRequired) {
Log.e("Update", "No internet connection available")
}
// Java
ContentStore contentStore = new ContentStore();
int errorCode = contentStore.checkForUpdate(EContentType.RoadMap);
if (errorCode == UnlError.NoError) {
// Check initiated successfully
} else if (errorCode == UnlError.ConnectionRequired) {
Log.e("Update", "No internet connection available");
}
The checkForUpdate method returns:
UnlError.NoErrorif the check has been initiatedUnlError.ConnectionRequiredif no internet connection is available- Other error codes for different failure scenarios
You can monitor status changes through the OffboardListener callbacks:
- Kotlin
- Java
// Kotlin
object : OffboardListener() {
override fun onWorldwideRoadMapSupportStatus(state: EOffboardListenerStatus) {
when (state) {
EOffboardListenerStatus.UpToDate -> {
Log.i("Update", "Map version is up-to-date")
}
EOffboardListenerStatus.OldData -> {
Log.i("Update", "New map version available - online operations still supported")
}
EOffboardListenerStatus.ExpiredData -> {
Log.w("Update", "Map version expired - only offline operations available")
}
}
}
}
// Java
new OffboardListener() {
@Override
public void onWorldwideRoadMapSupportStatus(EOffboardListenerStatus state) {
if (state == EOffboardListenerStatus.UpToDate) {
Log.i("Update", "Map version is up-to-date");
} else if (state == EOffboardListenerStatus.OldData) {
Log.i("Update", "New map version available - online operations still supported");
} else if (state == EOffboardListenerStatus.ExpiredData) {
Log.w("Update", "Map version expired - only offline operations available");
}
}
};
Create content updater¶
To update road maps or styles, you must create a ContentUpdater object. This object manages all operations related to the update process:
- Kotlin
- Java
// Kotlin
val contentStore = ContentStore()
val (contentUpdater, creationError) = contentStore.createContentUpdater(EContentType.RoadMap)
?: return
if (creationError == UnlError.NoError) {
// ContentUpdater successfully created
} else if (creationError == UnlError.Exist) {
// A ContentUpdater for this type already exists, but it's still valid
} else {
Log.e("Update", "Failed to create ContentUpdater: ${UnlError.getMessage(creationError)}")
return
}
// Java
ContentStore contentStore = new ContentStore();
Pair<ContentUpdater, Integer> result = contentStore.createContentUpdater(EContentType.RoadMap);
if (result == null) {
return;
}
ContentUpdater contentUpdater = result.first;
int creationError = result.second;
if (creationError == UnlError.NoError) {
// ContentUpdater successfully created
} else if (creationError == UnlError.Exist) {
// A ContentUpdater for this type already exists, but it's still valid
} else {
Log.e("Update", "Failed to create ContentUpdater: " + UnlError.getMessage(creationError));
return;
}
The createContentUpdater method returns a Pair<ContentUpdater?, Int> with:
-
A
ContentUpdaterinstance (null if creation failed completely) -
An error code indicating the status:
UnlError.NoError: Successfully created and ready for useUnlError.Exist: Already exists from a previous operation (still valid)- Other error codes: Creation failed
Start the update¶
In order to start the update process, call the update method from the ContentUpdater object:
- Kotlin
- Java
// Kotlin
val (contentUpdater, creationError) = contentStore.createContentUpdater(EContentType.RoadMap) ?: return
val errorCode = contentUpdater?.update(
allowChargeNetwork = true, // Allow updates on charged networks
listener = UnlProgressListener.create(
onStarted = {
Log.d("Update", "Update process started")
},
onProgress = { progress ->
Log.d("Update", "Update progress: $progress/100")
},
onCompleted = { error, hint ->
when (error) {
UnlError.NoError -> {
Log.i("Update", "Update completed successfully")
}
UnlError.InUse -> {
Log.w("Update", "Update is already running")
}
UnlError.NoDiskSpace -> {
Log.e("Update", "Not enough disk space")
}
else -> {
Log.e("Update", "Update failed: ${UnlError.getMessage(error)}")
}
}
},
onStatusChanged = { status ->
Log.d("Update", "Status changed to: $status")
}
)
)
// Java
Pair<ContentUpdater, Integer> result = contentStore.createContentUpdater(EContentType.RoadMap);
if (result == null) return;
ContentUpdater contentUpdater = result.first;
int errorCode = contentUpdater.update(
true, // Allow updates on charged networks
UnlProgressListener.create(
() -> {
Log.d("Update", "Update process started");
},
(progress) -> {
Log.d("Update", "Update progress: " + progress + "/100");
},
(error, hint) -> {
if (error == UnlError.NoError) {
Log.i("Update", "Update completed successfully");
} else if (error == UnlError.InUse) {
Log.w("Update", "Update is already running");
} else if (error == UnlError.NoDiskSpace) {
Log.e("Update", "Not enough disk space");
} else {
Log.e("Update", "Update failed: " + UnlError.getMessage(error));
}
},
(status) -> {
Log.d("Update", "Status changed to: " + status);
}
)
);
The update method accepts:
allowChargeNetwork: Boolean indicating whether the update can proceed on networks that may incur charges. If false, the update will only run on free networks like Wi-Fi.listener: AUnlProgressListenerto track the operation progress and status
The method returns an Int error code:
UnlError.NoErrorif the update process was successfully startedUnlError.InUseif an update is already runningUnlError.NotSupportedif the update operation is not supported for the content typeUnlError.NoDiskSpaceif there is not enough space on the device- Other error codes for other failure scenarios
Content updater status¶
The EContentUpdaterStatus enum represents the current state of the update operation:
| Enum Value | Description |
|---|---|
Idle |
The update process has not started. It's the default state of the ContentUpdater |
WaitConnection |
Waiting for an internet connection to proceed (Wi-Fi or mobile). |
WaitWIFIConnection |
Waiting for a Wi-Fi connection before continuing. Used when the update was called with allowChargeNetwork = false. |
CheckForUpdate |
Checking for available updates. |
Download |
Downloading the updated content. The overall progress and the per-item progress is available. |
FullyReady |
The update is fully downloaded and ready to be applied. Call apply() to complete the update. |
PartiallyReady |
The update is partially downloaded but can still be applied. Applying now will remove outdated items that weren't downloaded. |
DownloadRemainingContent |
Downloading any remaining content after applying a partial update. |
DownloadPendingContent |
Downloads any pending content that has not yet been retrieved. |
Complete |
The update process has finished successfully. |
Error |
The update process encountered an error. |
You can check the current status and progress:
- Kotlin
- Java
// Kotlin
val contentUpdater: ContentUpdater = // ...
val status = contentUpdater.status
val progress = contentUpdater.progress
val canApply = contentUpdater.canApply()
val isStarted = contentUpdater.isStarted()
Log.d("Update", "Status: $status, Progress: $progress%, CanApply: $canApply")
// Java
ContentUpdater contentUpdater = // ...
EContentUpdaterStatus status = contentUpdater.getStatus();
int progress = contentUpdater.getProgress();
boolean canApply = contentUpdater.canApply();
boolean isStarted = contentUpdater.isStarted();
Log.d("Update", "Status: " + status + ", Progress: " + progress + "%, CanApply: " + canApply);
Apply the update¶
Once the update status reaches FullyReady or PartiallyReady, you can apply it using the apply() method:
- Kotlin
- Java
// Kotlin
val contentUpdater: ContentUpdater = // ...
if (contentUpdater.status == EContentUpdaterStatus.FullyReady ||
contentUpdater.status == EContentUpdaterStatus.PartiallyReady) {
if (!contentUpdater.canApply()) {
Log.e("Update", "Cannot apply update at this time")
return
}
val applyError = contentUpdater.apply()
when (applyError) {
UnlError.NoError -> {
Log.i("Update", "Update applied successfully")
}
UnlError.UpToDate -> {
Log.i("Update", "Already up to date")
}
UnlError.Invalidated -> {
Log.e("Update", "Update was invalidated")
}
UnlError.Io -> {
Log.e("Update", "File system error during apply")
}
else -> {
Log.e("Update", "Failed to apply: ${UnlError.getMessage(applyError)}")
}
}
}
// Java
ContentUpdater contentUpdater = // ...
if (contentUpdater.getStatus() == EContentUpdaterStatus.FullyReady ||
contentUpdater.getStatus() == EContentUpdaterStatus.PartiallyReady) {
if (!contentUpdater.canApply()) {
Log.e("Update", "Cannot apply update at this time");
return;
}
int applyError = contentUpdater.apply();
if (applyError == UnlError.NoError) {
Log.i("Update", "Update applied successfully");
} else if (applyError == UnlError.UpToDate) {
Log.i("Update", "Already up to date");
} else if (applyError == UnlError.Invalidated) {
Log.e("Update", "Update was invalidated");
} else if (applyError == UnlError.Io) {
Log.e("Update", "File system error during apply");
} else {
Log.e("Update", "Failed to apply: " + UnlError.getMessage(applyError));
}
}
When the status is PartiallyReady, applying the update will:
- Remove outdated items that were not fully downloaded
- Restrict offline functionality to the updated content only
- Continue downloading the remaining items automatically
The apply() method returns an Int error code indicating the result of the operation.
Resume a pending update¶
The ContentUpdater supports operation resume between SDK running sessions. To check if there is a pending update:
- Kotlin
- Java
// Kotlin
val contentStore = ContentStore()
val (contentUpdater, creationError) = contentStore.createContentUpdater(EContentType.RoadMap) ?: return
contentUpdater?.let { updater ->
// Check if there's a pending update from a previous session
if (updater.status != EContentUpdaterStatus.Idle) {
Log.i("Update", "Found pending update with status: ${updater.status}")
// Resume the update
updater.update(true, progressListener)
}
}
// Java
ContentStore contentStore = new ContentStore();
Pair<ContentUpdater, Integer> result = contentStore.createContentUpdater(EContentType.RoadMap);
if (result == null) return;
ContentUpdater contentUpdater = result.first;
if (contentUpdater != null) {
// Check if there's a pending update from a previous session
if (contentUpdater.getStatus() != EContentUpdaterStatus.Idle) {
Log.i("Update", "Found pending update with status: " + contentUpdater.getStatus());
// Resume the update
contentUpdater.update(true, progressListener);
}
}
Cancel an update¶
You can cancel an ongoing update operation:
- Kotlin
- Java
// Kotlin
val contentUpdater: ContentUpdater = // ...
contentUpdater.cancel()
// Java
ContentUpdater contentUpdater = // ...
contentUpdater.cancel();
Canceling will stop the update process and return the ContentUpdater to an idle state.