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

# Let users open a local PDF from their device storage

> Trigger the platform file picker with requestOpenFromDevice, observe KPdfOpenDocumentState for results, and load the selected file in the viewer.

KPDF handles the platform file picker for you. Call `requestOpenFromDevice()` to launch the picker, observe `openDocumentState` for the result, and call `viewerState.open(selectedSource)` when the user selects a file. The `kpdf-compose` module binds the platform effects automatically when you use `rememberPdfViewerState`, so no additional setup is needed.

## Full pattern

The example below shows the complete flow: a button that triggers the picker and a `LaunchedEffect` that opens the document when the picker returns a selection.

```kotlin theme={null}
@Composable
fun OpenFromDeviceExample(viewerState: KPdfViewerState) {
    val openState by viewerState.openDocumentState.collectAsState()

    // Open the document as soon as the picker returns a file
    LaunchedEffect(openState) {
        val selectedSource = (openState as? KPdfOpenDocumentState.Success)?.source
            ?: return@LaunchedEffect

        viewerState.open(selectedSource)
    }

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

## KPdfOpenDocumentState variants

`openDocumentState` is a `StateFlow<KPdfOpenDocumentState>`. React to each variant to keep your UI consistent with the picker lifecycle.

| State               | When it occurs                              | Suggested UI reaction                         |
| ------------------- | ------------------------------------------- | --------------------------------------------- |
| `Idle`              | Initial state, no pick in progress          | No indicator                                  |
| `AwaitingSelection` | Picker is open, waiting for the user        | Show a loading or "choosing…" indicator       |
| `Success`           | User selected a file                        | Call `viewerState.open(source)` to load it    |
| `Cancelled`         | User dismissed the picker                   | Dismiss any indicator, restore previous state |
| `Error`             | Picker failed or the file could not be read | Show an error message                         |

```kotlin theme={null}
when (openState) {
    KPdfOpenDocumentState.Idle -> Unit
    is KPdfOpenDocumentState.AwaitingSelection -> {
        CircularProgressIndicator()
    }
    is KPdfOpenDocumentState.Success -> {
        // handled in LaunchedEffect above
    }
    KPdfOpenDocumentState.Cancelled -> {
        Text("No file selected.")
    }
    is KPdfOpenDocumentState.Error -> {
        Text("Could not open file: ${openState.reason}")
    }
}
```

<Note>
  If `openDocumentState` appears stuck at `Idle` after tapping the button, the most common cause is that `viewerState` is being recreated on every recomposition. This happens when you construct `KPdfViewerConfig` inline, outside a `remember` block. Wrap the config in `remember { KPdfViewerConfig.builder()…build() }` to keep the state instance stable.
</Note>
