Skip to content
This site is a preview of pull request #1384.

Download maps for offline use

An offline pack holds the resources needed to show a region without a network: map tiles, the style, and other assets. The OfflineManager creates, tracks, and deletes packs.

Get the manager from the map runtime. A composable, a view model, a background worker, or main can hold and use it:

App.kt
val offlineManager = DefaultMapRuntime.instance.offlineManager

A pack is defined by a style URL, a bounding box, and a zoom range. OfflineManager.create registers the pack in a paused state; call OfflineManager.resume to start the download:

App.kt
Button(
onClick = {
scope.launch {
val pack =
offlineManager.create(
definition =
OfflinePackDefinition.TilePyramid(
styleUrl = "https://tiles.openfreemap.org/styles/liberty",
bounds = BoundingBox(west = -123.0, south = 47.0, east = -122.0, north = 48.0),
pixelRatio = pixelRatio,
minZoom = 10,
maxZoom = 14,
),
metadata = "Seattle".encodeToByteArray(),
)
offlineManager.resume(pack)
}
}
) {
Text("Download Seattle")
}

The library does not interpret the metadata bytes. Use them to identify the pack later, for example with a display name.

OfflinePackDefinition.Shape downloads the region that covers a GeoJSON geometry instead of a bounding box.

OfflineManager.packs and OfflinePack.downloadProgress are StateFlows. A composition collects them with collectAsState and recomposes as the download proceeds:

App.kt
val packs by offlineManager.packs.collectAsState()
for (pack in packs) {
key(pack) {
val metadata by pack.metadata.collectAsState()
val progress by pack.downloadProgress.collectAsState()
val name = metadata?.decodeToString() ?: "Unnamed"
when (val current = progress) {
is DownloadProgress.Healthy ->
Text("$name: ${current.completedResourceCount} resources, ${current.status}")
is DownloadProgress.Error -> Text("$name: ${current.message}")
is DownloadProgress.TileLimitExceeded -> Text("$name: tile limit ${current.limit}")
is DownloadProgress.Unknown -> Text("$name: waiting for status")
}
}
}

A background worker can collect downloadProgress until the pack reports DownloadStatus.Complete. The runtime lists stored packs before it becomes available, so the first value of packs includes every pack from earlier sessions.

pause and resume switch a pack between the Paused and Downloading states. OfflineManager.invalidate re-checks a pack’s tiles against the server and updates the ones that changed.

OfflineManager.delete unregisters the pack and frees the resources that no remaining pack requires:

App.kt
for (pack in packs) {
key(pack) {
val metadata by pack.metadata.collectAsState()
Button(onClick = { scope.launch { offlineManager.delete(pack) } }) {
Text("Delete ${metadata?.decodeToString()}")
}
}
}

Once a pack is downloaded, the map uses its resources automatically when the device has no network. No map configuration is required.