Download a Region

This section shows how to start region downloads safely by observing progress before enqueueing download tasks, handling immediate and asynchronous errors, and presenting clear download states to users.

Observe First, Then Enqueue

Collect downloadProgress before calling download(). This allows the page to observe queueing, resource preparation, transfer, pause, and terminal states.

1
private fun startDownload(regionId: Int) {
2
lifecycleScope.launch {
3
RegionalOffline.download(regionId)
4
.onSuccess {
5
// This means only that region details were validated and the task was enqueued.
6
// Continue observing downloadProgress for the final download result.
7
8
showQueued(regionId)
9
}
10
.onFailure { throwable ->
11
renderError(OfflineRegionError.from(throwable))
12
}
13
}
14
}

The first download requires a network connection because the SDK must retrieve the latest region details. If connectivity is lost after the task is enqueued, it enters PAUSED_NETWORK and resumes automatically after a validated network becomes available.

Show the region name, estimated download size, zoom range, network type, and available storage guidance on the application UI, before starting a download. If the preview bundle is still being prepared, show a “Preparing offline resources” state and keep the download action disabled until the SDK can enqueue the task.

The download coordinator waits for the preview bundle to become ready before transferring region packages. The product may temporarily disable the download button until previewBundleReady becomes true for a clearer UI, but the SDK also enforces this prerequisite.

Handle Two Error Paths

The download flow has two independent error paths:

  1. download() returns Result.failure: the task was not enqueued, for example because of a network error while retrieving region details, invalid details, or an in-progress deletion for the same region.

  2. download() succeeds; then downloadProgress may enter FAILED_STORAGE or FAILED: the task was persisted, but transfer, validation, or installation failed.

Therefore, handling only the return value of download() is insufficient. You must also monitor the progress state.

Download States

downloadProgress is a regionId-keyed StateFlow<Map<Int, OfflineDownloadProgress>>. percent ranges from “0..100”; byte counts are aggregated across all compressed packages in the region.

StateMeaningUI recommendation
QUEUEDWaiting for a concurrency slot or the preview bundleShow "Waiting to download"
DOWNLOADING_PACKAGEDownloading or installing region packagesShow percent, byte counts, and currentPackageName
PAUSED_USERPaused by the userShow a “Resume” button
PAUSED_NETWORKNo validated network is currently availableShow "Waiting for network"; resume automatically when connectivity returns
FAILED_STORAGEInsufficient storagePrompt the user to free space, then call RESUME
FAILEDNon-storage failureLog errorMessage and retry after resolving the problem
COMPLETEDInstallation completeRefresh the local list. This terminal state is then removed from the map.

PREPARING_GLYPHS, PREPARING_OFFLINE_DISPLAY and CANCELLED are retained for compatibility with legacy persisted values. The current normal flow does not continuously emit these states.

Java Listener

Java applications can observe download progress through OfflineDownloadProgressListener when Kotlin Flow collection is not used directly:

1
private final OfflineDownloadProgressListener progressListener =
2
progressByRegionId -> renderDownloadProgress(progressByRegionId);
3
4
@Override
5
protected void onCreate(Bundle savedInstanceState) {
6
super.onCreate(savedInstanceState);
7
RegionalOffline.addDownloadProgressListener(this, progressListener);
8
}

Callbacks run on the main thread. The listener immediately receives the current snapshot when registered and is removed automatically when the owner is destroyed. An overload without an owner is also available, but you must call removeDownloadProgressListener() manually. The download, catalog, and detail APIs are Kotlin suspend APIs; pure Java applications should add a Kotlin coroutine adapter.