> ## Documentation Index
> Fetch the complete documentation index at: https://mahmoud-b28887f9.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Observable state flows: react to PDF lifecycle events

> Observe KPdfLoadState, KPdfSaveState, and KPdfOpenDocumentState to show progress indicators, handle errors, and drive your UI through every lifecycle stage.

KPDF exposes every stage of a document's lifecycle — loading, rendering, saving, and picking from device — as observable `StateFlow` properties on `KPdfViewerState`. Collect these flows in your composables and respond to each state with the appropriate UI. This page covers `KPdfLoadState` in depth and introduces the patterns for `KPdfSaveState`, `KPdfOpenDocumentState`, and `KPdfExternalOpenState`.

## KPdfLoadState

`KPdfLoadState` tracks the primary document load pipeline from the moment you call `open()` until the document is ready to display or an error occurs.

### States

| State                      | Description                                                                                                                            |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `Idle`                     | No load has been initiated, or a previous document was closed.                                                                         |
| `Loading`                  | The SDK is actively fetching, decoding, or processing the source.                                                                      |
| `Ready(pageCount, title?)` | The document is open. `pageCount` is the total number of pages. `title` is the document title from metadata, or `null` if none is set. |
| `Error(reason)`            | The load pipeline failed. `reason` is a `KPdfError` describing what went wrong.                                                        |

### Collecting load state

```kotlin theme={null}
@Composable
fun PdfScreen(source: KPdfSource) {
    val viewerState = rememberPdfViewerState(source = source)
    val loadState by viewerState.loadState.collectAsState()

    Box(modifier = Modifier.fillMaxSize()) {
        KPdfViewer(
            state = viewerState,
            modifier = Modifier.fillMaxSize(),
        )

        when (val state = loadState) {
            KPdfLoadState.Idle -> {
                // Nothing to show yet
            }
            KPdfLoadState.Loading -> {
                CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
            }
            is KPdfLoadState.Ready -> {
                // Document is visible — optionally show page count
                Text(
                    text = "${state.pageCount} pages",
                    modifier = Modifier.align(Alignment.TopEnd).padding(8.dp),
                )
            }
            is KPdfLoadState.Error -> {
                Column(modifier = Modifier.align(Alignment.Center)) {
                    Text("Failed to load document: ${state.reason}")
                    Button(onClick = { viewerState.retry() }) {
                        Text("Retry")
                    }
                }
            }
        }
    }
}
```

### Reading document metadata

Once the state is `Ready`, access `pageCount` and `title` from the state object:

```kotlin theme={null}
val loadState by viewerState.loadState.collectAsState()

val ready = loadState as? KPdfLoadState.Ready
val pageCount = ready?.pageCount ?: 0
val docTitle = ready?.title ?: "Untitled"
```

## KPdfSaveState

`KPdfSaveState` tracks the full lifecycle of a save or export operation initiated by `requestSave()` or `savePdf()`. The platform integration layer included with `rememberPdfViewerState` handles the file-picker handoff automatically, so you normally only need to observe this flow to update your UI.

| State                                                         | Description                                                                              |
| ------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `Idle`                                                        | No save is in progress.                                                                  |
| `Exporting`                                                   | The SDK is serializing the PDF bytes before presenting the destination picker.           |
| `AwaitingDestination(requestId, suggestedFileName, mimeType)` | The platform file picker is open and waiting for the user to choose a save location.     |
| `Success(requestId, suggestedFileName, mimeType, location?)`  | The file was saved. `location` is the resolved path or URI, if the platform provides it. |
| `Cancelled(requestId, suggestedFileName, mimeType)`           | The user dismissed the picker without selecting a destination.                           |
| `Error(reason, suggestedFileName?, mimeType?)`                | The save failed.                                                                         |

```kotlin theme={null}
val saveState by viewerState.saveState.collectAsState()

when (saveState) {
    KPdfSaveState.Idle              -> Unit
    KPdfSaveState.Exporting         -> Text("Preparing PDF...")
    is KPdfSaveState.AwaitingDestination -> Text("Choose where to save...")
    is KPdfSaveState.Success        -> Text("PDF saved successfully.")
    is KPdfSaveState.Cancelled      -> Text("Save cancelled.")
    is KPdfSaveState.Error          -> Text("Save failed: ${saveState.reason}")
}
```

Trigger the save flow from a button:

```kotlin theme={null}
Button(onClick = { viewerState.savePdf(suggestedFileName = "report.pdf") }) {
    Text("Save")
}
```

## KPdfOpenDocumentState

`KPdfOpenDocumentState` tracks the platform file picker flow initiated by `requestOpenFromDevice()`. When the user selects a file, the state moves to `Success` and exposes the selected `KPdfSource`. Call `viewerState.open(source)` to load it.

| State                                     | Description                                                            |
| ----------------------------------------- | ---------------------------------------------------------------------- |
| `Idle`                                    | No picker session is active.                                           |
| `AwaitingSelection(requestId, mimeTypes)` | The platform picker is open and waiting for the user to select a file. |
| `Success(source)`                         | The user selected a file. `source` is the resulting `KPdfSource`.      |
| `Cancelled`                               | The user dismissed the picker without selecting a file.                |
| `Error(reason)`                           | The picker session failed.                                             |

```kotlin theme={null}
val openState by viewerState.openDocumentState.collectAsState()

LaunchedEffect(openState) {
    val selectedSource = (openState as? KPdfOpenDocumentState.Success)?.source
        ?: return@LaunchedEffect
    viewerState.open(selectedSource)
}

Button(onClick = { viewerState.requestOpenFromDevice() }) {
    Text("Open local PDF")
}
```

<Note>
  If `openDocumentState` never leaves `Idle` after you call `requestOpenFromDevice()`, check that your `KPdfViewerConfig` and `KPdfSource` are stable references wrapped in `remember`. Recreating them on every recomposition also recreates `viewerState`, which resets all in-flight flows.
</Note>

## KPdfExternalOpenState

`KPdfExternalOpenState` tracks the lifecycle of handing the current PDF to an external application via `openInExternalApp()`. The states mirror `KPdfSaveState` in structure.

| State                                                         | Description                                  |
| ------------------------------------------------------------- | -------------------------------------------- |
| `Idle`                                                        | No external-open is in progress.             |
| `Exporting`                                                   | The SDK is preparing the PDF bytes to share. |
| `AwaitingExternalApp(requestId, suggestedFileName, mimeType)` | The platform app picker is open.             |
| `Success(requestId, suggestedFileName, mimeType, location?)`  | The external app received the file.          |
| `Cancelled(requestId, suggestedFileName, mimeType)`           | The user dismissed the picker.               |
| `Error(reason, suggestedFileName?, mimeType?)`                | The operation failed.                        |

```kotlin theme={null}
val externalOpenState by viewerState.externalOpenState.collectAsState()

when (externalOpenState) {
    KPdfExternalOpenState.Idle              -> Unit
    KPdfExternalOpenState.Exporting         -> Text("Preparing PDF...")
    is KPdfExternalOpenState.AwaitingExternalApp -> Text("Opening external app...")
    is KPdfExternalOpenState.Success        -> Text("Opened in external app.")
    is KPdfExternalOpenState.Cancelled      -> Text("Cancelled.")
    is KPdfExternalOpenState.Error          -> Text("Failed to open external app.")
}
```
