Skip to main content

Downloading Onboard Map Simulation

Last updated: June 19, 2026 | 5 minutes read

This example downloads an onboard map from the content store and then simulates turn-by-turn navigation on it. On first launch the app needs an internet connection to fetch the map; once the map is stored on the device, the route is calculated locally and the simulation runs without any further online connection. It is the runtime-download counterpart of the Downloaded Onboard Map Simulation example, where the map is pre-bundled with the app instead of fetched on demand.

The example looks for a specific map by name (here, the Luxembourg map). If it is already present and complete, the simulation starts immediately; otherwise the map is downloaded first, with a panel showing the country flag, size and download progress. Once the download completes, the simulated trip runs from Luxembourg to Mersch, which both lie within that map.

Downloading the onboard map from the content store
Simulated navigation on the downloaded map

Checking whether the map is already present

When the SDK finishes initializing, the local content list is scanned for the required map. If a completed copy is already on the device, the simulation can start straight away; otherwise an internet connection is required to download it.

MainActivity.ktView on Github
binding.gemSurfaceView.onSdkInitSucceeded = onSdkInitSucceeded@{
val localMaps = contentStore.getLocalContentList(EContentType.RoadMap)
?: return@onSdkInitSucceeded

for (map in localMaps) {
val mapName = map.name ?: continue
if (mapName.compareTo(this.mapName, true) == 0) {
requiredMapHasBeenDownloaded = map.isCompleted()
break
}
}

if (requiredMapHasBeenDownloaded) {
onOnboardMapReady()
} else {
runOnUiThread {
if (!Util.isInternetConnected(this)) {
showDialog(getString(R.string.internet_required))
}
}
}
}

Fetching the store content list

If the map is not present, the app verifies the app authorization and then asks the content store for the list of available road maps. The store list is fetched asynchronously through contentListener.

MainActivity.ktView on Github
private fun loadMaps() = SdkCall.execute {
contentStore.asyncGetStoreContentList(EContentType.RoadMap, contentListener)
}

Downloading the required map

When the store list arrives, the listener finds the map whose name matches the required one. If it is not yet complete, an asyncDownload is started with its own progress listener that drives the download panel and, on completion, triggers the simulation.

MainActivity.ktView on Github
val contentListPair =
contentStore.getStoreContentList(EContentType.RoadMap)
?: return@execute

for (map in contentListPair.first) {
val mapName = map.name ?: continue
if (mapName.compareTo(this.mapName, true) != 0) continue

if (!map.isCompleted()) {
val downloadProgressListener = ProgressListener.create(
onStarted = {
onDownloadStarted(map)
showStatusMessage(getString(R.string.downloading_map, mapName))
},
onStatusChanged = { status -> onStatusChanged(status) },
onProgress = { progress -> onProgressUpdated(progress) },
onCompleted = { dlError, _ ->
if (dlError == GemError.NoError) {
showStatusMessage(getString(R.string.map_downloaded, mapName))
onOnboardMapReady()
} else {
EspressoIdlingResource.decrementDownloadingResource()
}
},
)
map.asyncDownload(
downloadProgressListener,
GemSdk.EDataSavePolicy.UseDefault,
true,
)
}
break
}

Showing download progress

While the map downloads, the panel displays the country flag, the map name and its total size, and onProgressUpdated advances the progress bar. As soon as the download completes the panel is hidden and the simulation starts.

MainActivity.ktView on Github
private fun onDownloadStarted(map: ContentStoreItem) {
binding.apply {
mapContainer.isVisible = true
// Post so the view has been laid out before we read its dimensions.
root.post { updateFocusViewport() }

var flagBitmap: Bitmap? = null
SdkCall.execute {
map.countryCodes?.let { codes ->
val size = resources.getDimension(R.dimen.icon_size).toInt()
flagBitmap = MapDetails().getCountryFlag(codes[0])?.asBitmap(size, size)
}
}
flagIcon.setImageBitmap(flagBitmap)
countryName.text = SdkCall.execute { map.name }
mapDescription.text = SdkCall.execute { GemUtil.formatSizeAsText(map.totalSize) }
}
EspressoIdlingResource.decrementDownloadingResource()
}

Starting the simulation once the map is ready

Whether the map was already on the device or has just finished downloading, onOnboardMapReady hides the download panel and starts the simulation. startSimulation defines the departure and destination waypoints and calls navigationService.startSimulation; on failure the error is reported in a dialog.

MainActivity.ktView on Github
private fun onOnboardMapReady() {
startSimulation()
binding.mapContainer.isVisible = false
}

private fun startSimulation() = SdkCall.execute {
val waypoints = arrayListOf(
Landmark("Luxembourg", 49.61588784436375, 6.135843869736401),
Landmark("Mersch", 49.74785494642988, 6.103323786692679),
)
val error = navigationService.startSimulation(waypoints, navigationListener, routingProgressListener)
if (error != GemError.NoError) {
val message = GemError.getMessage(error, this)
runOnUiThread {
showDialog(getString(R.string.failed_to_start_simulation, message))
}
}
}

Once the simulation starts, navigation behaves exactly as in the other simulation examples: the route is presented on the map, the camera follows the simulated position, and the top and bottom panels are updated from the stream of navigation events delivered to the NavigationListener.

MainActivity.ktView on Github
private val navigationListener: NavigationListener = NavigationListener.create(
onNavigationStarted = {
SdkCall.execute {
binding.gemSurfaceView.mapView?.let { mapView ->
navRoute?.let { route -> mapView.presentRoute(route) }
enableGPSButton()
mapView.followPosition()
}
}
applyCameraFocus()
setNavigationPanelsVisible(isVisible = true)
EspressoIdlingResource.decrementNavigationResource()
},
onNavigationInstructionUpdated = { instr -> updateNavigationInstruction(instr) },
onDestinationReached = { onNavigationEnded() },
onNotifyStatusChange = { status ->
navigationStatus = status
refreshStatusMessage()
},
onNavigationError = { error -> onNavigationEnded(error) },
onNavigationSound = { sound ->
SdkCall.execute {
SoundPlayingService.play(sound, playingListener, soundPreference)
}
},
canPlayNavigationSound = true,
)