Skip to main content

Download A Map

Last updated: June 16, 2026 | 6 minutes read

This example demonstrates how to use the content store to retrieve the catalog of road maps available on the server, download the first map in that catalog, and display its download progress.

Downloadable Maps list

Initializing the SDK

MainActivity overrides onCreate, which inflates the layout with data binding, sets up the RecyclerView that lists the maps, and registers the SDK listeners. It then calls GemSdk.initSdkWithDefaults(this).

The SDK must be initialized before any of its functionality - such as the content store - can be used. In the examples that display a map, this happens automatically: the GemSurfaceView placed in the activity's layout initializes the SDK as part of setting up the map. This example does not display a map, so there is no GemSurfaceView to initialize the SDK for it; the initialization must therefore be performed explicitly with GemSdk.initSdkWithDefaults(this).

Finally, onCreate checks for an internet connection, since the catalog can only be fetched online.

MainActivity.ktView on Github
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

// Keep status-bar icons light against the dark primary toolbar background.
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars = false

binding.listView.apply {
layoutManager = LinearLayoutManager(this@MainActivity)
addItemDecoration(
DividerItemDecoration(applicationContext, (layoutManager as LinearLayoutManager).orientation),
)
itemAnimator = null
}

registerSdkListeners()

// This step of initialization is mandatory if you want to use the SDK without a map.
val errorCode = GemSdk.initSdkWithDefaults(this)
if (errorCode != GemError.NoError) {
showDialog(
getString(R.string.dialog_sdk_initialization_error, SdkCall.runSynced { GemError.getMessage(errorCode, this) }),
) {
finish()
exitProcess(0)
}
}

if (!Util.isInternetConnected(this)) {
binding.progressBar.visibility = View.GONE
showDialog(getString(R.string.dialog_internet_required))
} else {
showStatusMessage(getString(R.string.status_connecting_magic_lane_servers))
}
}

Verifying Authorization Before Downloading

Before requesting the catalog, the example confirms the app's token is valid. registerSdkListeners() waits for onWorldwideRoadMapSupportStatus to report UpToDate, then verifies the app authorization with SdkSettings.verifyAppAuthorization. It unsubscribes after the first event so verification only runs once. A rejected token (onApiTokenRejected) shows an error and closes the app.

MainActivity.ktView on Github
private fun registerSdkListeners() {
SdkSettings.onWorldwideRoadMapSupportStatus = { status ->
if (status == EOffboardListenerStatus.UpToDate) {
runOnAliveUi { showStatusMessage(getString(R.string.status_checking_token_validity)) }
SdkSettings.appAuthorization?.let {
SdkCall.execute { SdkSettings.verifyAppAuthorization(it, checkAuthorizationListener) }
} ?: run {
runOnAliveUi { showInvalidTokenDialog() }
}
// Unsubscribe after the first UpToDate event to avoid repeated verification.
SdkSettings.onWorldwideRoadMapSupportStatus = {}
}
}

SdkSettings.onApiTokenRejected = {
runOnAliveUi { showInvalidTokenDialog() }
}
}

Once verification finishes successfully, checkAuthorizationListener triggers the catalog request via loadMapsCatalog().

MainActivity.ktView on Github
private val checkAuthorizationListener = ProgressListener.create(onCompleted = { errorCode, _ ->
if (errorCode != GemError.NoError) {
runOnAliveUi { showInvalidTokenDialog() }
} else {
loadMapsCatalog()
}
})

Requesting the Map Catalog

loadMapsCatalog() asks the content store for the list of road maps with contentStore.asyncGetStoreContentList(EContentType.RoadMap, progressListener). The call runs on the SDK thread via SdkCall.execute, and the progressListener is notified as the catalog download starts, progresses and completes.

MainActivity.ktView on Github
private fun loadMapsCatalog() = SdkCall.execute {
val error = contentStore.asyncGetStoreContentList(EContentType.RoadMap, progressListener)
if (error != GemError.NoError) {
runOnAliveUi {
showStatusMessage(
getString(
R.string.status_maps_catalog_download_error,
SdkCall.runSynced { GemError.getMessage(error, this) },
),
)
}
}
}

Handling the Catalog and Starting a Download

When the catalog finishes downloading, progressListener.onCompleted reads the local copy of the list into mapsCatalog with contentStore.getStoreContentList(EContentType.RoadMap)?.first. For this example it automatically picks the first map and starts downloading it with ContentStoreItem.asyncDownload(listener, savePolicy, allowChargedNetworks); in a real app the user would choose which map to download.

The error code returned by asyncDownload is handled in three cases:

  • GemError.NoError - the download started successfully, and the downloadProgressListener (below) reports its progress.
  • GemError.UpToDate - the map has already been downloaded in a previous app session, so it is already available on the device and there is nothing to download; the user is simply informed of this.
  • any other code - a real error occurred (for example a network problem), and its message is shown to the user.

displayList(mapsCatalog) then renders the full catalog.

The downloaded map is stored on the device under a path such as /sdcard/Android/data/<app-id>/files/Data/Maps/.

MainActivity.ktView on Github
private val progressListener = ProgressListener.create(
onStarted = {
runOnAliveUi {
binding.progressBar.visibility = View.VISIBLE
showStatusMessage(getString(R.string.status_downloading_maps_catalog))
}
},
onCompleted = { errorCode, _ ->
runOnAliveUi { binding.progressBar.visibility = View.GONE }

when (errorCode) {
GemError.NoError -> {
SdkCall.execute {
val mapsCatalog = contentStore.getStoreContentList(EContentType.RoadMap)?.first

if (!mapsCatalog.isNullOrEmpty()) {
val mapItem = mapsCatalog[0]
val itemName = mapItem.name

// downloadProgressListener (shown below) reports this item's progress.
val downloadProgressListener = ProgressListener.create(/* ... */)

SdkCall.execute {
val error = mapItem.asyncDownload(
downloadProgressListener,
GemSdk.EDataSavePolicy.UseDefault,
true,
)
when (error) {
GemError.NoError -> { /* Download started. */ }
GemError.UpToDate -> runOnAliveUi {
showStatusMessage(getString(R.string.status_item_already_downloaded, itemName))
}
else -> runOnAliveUi {
showStatusMessage(
getString(
R.string.status_download_item_error,
SdkCall.runSynced { GemError.getMessage(error, this) },
),
)
}
}
}
}

Util.postOnMain { displayList(mapsCatalog) }
}
}

else -> runOnAliveUi {
showStatusMessage(
getString(
R.string.status_maps_catalog_download_error,
SdkCall.runSynced { GemError.getMessage(errorCode, this) },
),
)
}
}
},
)

Tracking the Download Progress

The downloadProgressListener created above updates the UI as the map downloads. onProgress and onCompleted call notifyItemChanged(0) so the first list row re-binds and reflects the latest progress and status, while the status text reports the outcome.

MainActivity.ktView on Github
val downloadProgressListener = ProgressListener.create(
onStarted = {
runOnAliveUi { showStatusMessage(getString(R.string.status_downloading_item, itemName)) }
},
onProgress = {
runOnAliveUi { binding.listView.adapter?.notifyItemChanged(0) }
},
onCompleted = { dlErrorCode, _ ->
runOnAliveUi { binding.listView.adapter?.notifyItemChanged(0) }
if (dlErrorCode == GemError.NoError) {
runOnAliveUi { showStatusMessage(getString(R.string.status_item_downloaded, itemName)) }
} else {
runOnAliveUi {
showStatusMessage(
getString(
R.string.status_item_download_error,
itemName,
SdkCall.runSynced { GemError.getMessage(dlErrorCode, this) },
),
)
}
}
},
)

Displaying the Map List

displayList() assigns a CustomAdapter to the RecyclerView. For each ContentStoreItem, onBindViewHolder shows the map's name, its size formatted with GemUtil.formatSizeAsText(totalSize), and a country flag. The item's status drives the row's trailing widget: a status icon when the map is Completed, or a progress bar bound to downloadProgress while DownloadRunning. All ContentStoreItem access happens inside SdkCall.execute, since these are SDK objects.

MainActivity.ktView on Github
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
viewHolder.apply {
text.text = SdkCall.execute { dataSet[position].name }
description.text = SdkCall.execute { GemUtil.formatSizeAsText(dataSet[position].totalSize) }
imageView.setImageBitmap(SdkCall.execute { getFlagBitmap(dataSet[position]) })

statusImageView.visibility = View.GONE
progressBar.visibility = View.INVISIBLE

when (SdkCall.execute { dataSet[position].status }) {
EContentStoreItemStatus.Completed -> {
statusImageView.visibility = View.VISIBLE
progressBar.visibility = View.INVISIBLE
}

EContentStoreItemStatus.DownloadRunning -> {
progressBar.visibility = View.VISIBLE
progressBar.progress = SdkCall.execute { dataSet[position].downloadProgress } ?: 0
}

else -> return
}
}
}

The country flag is fetched with MapDetails().getCountryFlag(isoCode) from the item's countryCodes and rendered to a Bitmap. Flags are cached by ISO code so each one is rendered only once.

MainActivity.ktView on Github
private fun getFlagBitmap(item: ContentStoreItem): Bitmap? {
item.countryCodes?.let { codes ->
if (codes.isNotEmpty()) {
val isoCode = codes[0]
if (!flagBitmapsMap.containsKey(isoCode)) {
val size = resources.getDimension(R.dimen.icon_size).toInt()
flagBitmapsMap[isoCode] = MapDetails().getCountryFlag(isoCode)?.asBitmap(size, size)
}
return flagBitmapsMap[isoCode]
}
return null
}
return null
}