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 OfflineStorage
creates, tracks, and deletes packs.
Get the offline storage from the map runtime with the
offlineStorage extension. A composable, a view model, a background
worker, or main can hold and use it:
val offlineStorage = DefaultMapRuntime.instance.offlineStorageDownload a region
Section titled “Download a region”A pack is defined by a style URL, a bounding box, and a zoom range.
OfflineStorage.create registers the pack in a paused state; call
OfflineStorage.resume to start the download:
val offlineState by offlineStorage.state.collectAsState()Button( enabled = offlineState is OfflineStorageState.Ready, onClick = { scope.launch { val pack = offlineStorage.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.0, maxZoom = 14.0, ), metadata = "Seattle".encodeToByteArray(), ) offlineStorage.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.
Track download progress
Section titled “Track download progress”OfflineStorage.state and OfflinePack.downloadProgress
are StateFlows. A composition collects them with collectAsState and
recomposes as the download proceeds:
val packs = (offlineState as? OfflineStorageState.Ready)?.packs.orEmpty()when (val state = offlineState) { OfflineStorageState.Loading -> Text("Loading offline packs…") is OfflineStorageState.Failed -> Text(state.cause.message ?: "Could not load offline packs") else -> 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.NotReported -> Text("$name: waiting for status") else -> Text(name) } }}A background worker can collect downloadProgress until the pack reports
DownloadStatus.Complete. Offline storage starts in
OfflineStorageState.Loading while it lists stored packs.
OfflineStorageState.Ready includes the packs from earlier sessions;
OfflineStorageState.Failed reports initialization errors.
Operations suspend until initialization completes.
pause and resume switch a pack between the Paused and Downloading
states. OfflineStorage.invalidate re-checks a pack’s tiles
against the server and updates the ones that changed.
Delete a pack
Section titled “Delete a pack”OfflineStorage.delete unregisters the pack and frees the
resources that no remaining pack requires:
for (pack in packs) { key(pack) { val metadata by pack.metadata.collectAsState() Button(onClick = { scope.launch { offlineStorage.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.