Update Content
The Magic Lane 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 Magic Lane servers support online operations for the most up-to-date version (EOffboardListenerStatus.UpToDate) and the previous version (EOffboardListenerStatus.OldData) of road map data.
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 update is applied via the
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.
The auto-update behavior is different between the Magic Lane 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
val contentStore = ContentStore()
val errorCode = contentStore.checkForUpdate(EContentType.RoadMap)
if (errorCode == GemError.NoError) {
// Check initiated successfully
} else if (errorCode == GemError.ConnectionRequired) {
Log.e("Update", "No internet connection available")
}
ContentStore contentStore = new ContentStore();
int errorCode = contentStore.checkForUpdate(EContentType.RoadMap);
if (errorCode == GemError.NoError) {
// Check initiated successfully
} else if (errorCode == GemError.ConnectionRequired) {
Log.e("Update", "No internet connection available");
}
The checkForUpdate method returns:
GemError.NoErrorif the check has been initiatedGemError.ConnectionRequiredif no internet connection is available- Other error codes for different failure scenarios
You can monitor status changes through the OffboardListener callbacks:
- Kotlin
- Java
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")
}
}
}
}
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
val contentStore = ContentStore()
val (contentUpdater, creationError) = contentStore.createContentUpdater(EContentType.RoadMap)
?: return
if (creationError == GemError.NoError) {
// ContentUpdater successfully created
} else if (creationError == GemError.Exist) {
// A ContentUpdater for this type already exists, but it's still valid
} else {
Log.e("Update", "Failed to create ContentUpdater: ${GemError.getMessage(creationError)}")
return
}
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 == GemError.NoError) {
// ContentUpdater successfully created
} else if (creationError == GemError.Exist) {
// A ContentUpdater for this type already exists, but it's still valid
} else {
Log.e("Update", "Failed to create ContentUpdater: " + GemError.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:
GemError.NoError: Successfully created and ready for useGemError.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
val (contentUpdater, creationError) = contentStore.createContentUpdater(EContentType.RoadMap) ?: return
val errorCode = contentUpdater?.update(
allowChargeNetwork = true, // Allow updates on charged networks
listener = ProgressListener.create(
onStarted = {
Log.d("Update", "Update process started")
},
onProgress = { progress ->
Log.d("Update", "Update progress: $progress/100")
},
onCompleted = { error, hint ->
when (error) {
GemError.NoError -> {
Log.i("Update", "Update completed successfully")
}
GemError.InUse -> {
Log.w("Update", "Update is already running")
}
GemError.NoDiskSpace -> {
Log.e("Update", "Not enough disk space")
}
else -> {
Log.e("Update", "Update failed: ${GemError.getMessage(error)}")
}
}
},
onStatusChanged = { status ->
Log.d("Update", "Status changed to: $status")
}
)
)
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
ProgressListener.create(
() -> {
Log.d("Update", "Update process started");
},
(progress) -> {
Log.d("Update", "Update progress: " + progress + "/100");
},
(error, hint) -> {
if (error == GemError.NoError) {
Log.i("Update", "Update completed successfully");
} else if (error == GemError.InUse) {
Log.w("Update", "Update is already running");
} else if (error == GemError.NoDiskSpace) {
Log.e("Update", "Not enough disk space");
} else {
Log.e("Update", "Update failed: " + GemError.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: AProgressListenerto track the operation progress and status
The method returns an Int error code:
GemError.NoErrorif the update process was successfully startedGemError.InUseif an update is already runningGemError.NotSupportedif the update operation is not supported for the content typeGemError.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
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")
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
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) {
GemError.NoError -> {
Log.i("Update", "Update applied successfully")
}
GemError.UpToDate -> {
Log.i("Update", "Already up to date")
}
GemError.Invalidated -> {
Log.e("Update", "Update was invalidated")
}
GemError.Io -> {
Log.e("Update", "File system error during apply")
}
else -> {
Log.e("Update", "Failed to apply: ${GemError.getMessage(applyError)}")
}
}
}
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 == GemError.NoError) {
Log.i("Update", "Update applied successfully");
} else if (applyError == GemError.UpToDate) {
Log.i("Update", "Already up to date");
} else if (applyError == GemError.Invalidated) {
Log.e("Update", "Update was invalidated");
} else if (applyError == GemError.Io) {
Log.e("Update", "File system error during apply");
} else {
Log.e("Update", "Failed to apply: " + GemError.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
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)
}
}
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
val contentUpdater: ContentUpdater = // ...
contentUpdater.cancel()
ContentUpdater contentUpdater = // ...
contentUpdater.cancel();
Canceling will stop the update process and return the ContentUpdater to an idle state.