Skip to content

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
val offlineState by offlineManager.state.collectAsState()
Button(
enabled = offlineState is OfflineManagerState.Ready,
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.state and OfflinePack.downloadProgress are StateFlows. A composition collects them with collectAsState and recomposes as the download proceeds:

App.kt
val packs = (offlineState as? OfflineManagerState.Ready)?.packs.orEmpty()
when (val state = offlineState) {
OfflineManagerState.Loading -> Text("Loading offline packs…")
is OfflineManagerState.Failed -> Text(state.cause.message ?: "Could not load offline packs")
is OfflineManagerState.Ready -> Unit
}
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 manager starts in OfflineManagerState.Loading while it lists stored packs. OfflineManagerState.Ready includes the packs from earlier sessions; OfflineManagerState.Failed reports initialization errors. Operations suspend until initialization completes.

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.