# 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.

```kotlin
private fun startDownload(regionId: Int) {
    lifecycleScope.launch {
        RegionalOffline.download(regionId)
            .onSuccess {
                // This means only that region details were validated and the task was enqueued.
                // Continue observing downloadProgress for the final download result.

                showQueued(regionId)
            }
            .onFailure { throwable ->
                renderError(OfflineRegionError.from(throwable))
            }
    }
}
```

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.

| State | Meaning | UI recommendation |
| --- | --- | --- |
| `QUEUED` | Waiting for a concurrency slot or the preview bundle | Show "Waiting to download" |
| `DOWNLOADING_PACKAGE` | Downloading or installing region packages | Show `percent`, byte counts, and `currentPackageName` |
| `PAUSED_USER` | Paused by the user | Show a “Resume” button |
| `PAUSED_NETWORK` | No validated network is currently available | Show "Waiting for network"; resume automatically when connectivity returns |
| `FAILED_STORAGE` | Insufficient storage | Prompt the user to free space, then call `RESUME` |
| `FAILED` | Non-storage failure | Log _errorMessage_ and retry after resolving the problem |
| `COMPLETED` | Installation complete | Refresh 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:

```java
private final OfflineDownloadProgressListener progressListener =
    progressByRegionId -> renderDownloadProgress(progressByRegionId);

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    RegionalOffline.addDownloadProgressListener(this, progressListener);
}
```

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.
