Skip to main content
This page covers the most common problems you may encounter when integrating KPDF into a Compose Multiplatform application. For each issue you’ll find the root cause and a concrete fix, with code examples where they help.
Cause: KPdfViewerState is being recreated on every recomposition because you are building KPdfViewerConfig inline — without remember. Each new config instance causes rememberPdfViewerState to return a brand-new state object, which resets transient flows like openDocumentState before they can deliver a result.Fix: Wrap both source and config in remember so they remain stable across recompositions.
After this change, openDocumentState will progress through AwaitingSelectionSuccess (or Cancelled / Error) without resetting mid-flight.
Cause: KPdfSource.Url downloads and renders the PDF over the network. By default, KPDF can cache the downloaded bytes to disk so that subsequent opens work without a network connection.If diskCacheSize is 0, disk caching is disabled, and the document will fail to open when the device is offline.Fix: Set a non-zero diskCacheSize in your config. The value is the maximum number of rendered pages to persist to disk.
Once the PDF has been downloaded at least once with a non-zero cache size, KPDF will serve it from the disk cache on subsequent opens — even without a network connection.If you need to keep storage use minimal, set diskCacheSize to a small positive value (for example 10) rather than 0.
Cause: KPDF renders thumbnails asynchronously via renderPage(). Each thumbnail is a separate render call that runs off the main thread. On low-end devices, or when thumbnailWidth/thumbnailHeight are set too large, render times can be long enough that thumbnails appear blank for several seconds.Fix: Keep thumbnail dimensions reasonable. Values around 92.dp × 128.dp give a good balance between visual quality and render speed.
If a thumbnail enters an error state (for example because a render call fails), you can tap it to retry. KPDF will call renderPage() again for that page index.Reducing thumbnailWidth and thumbnailHeight is the single most effective way to speed up the strip on constrained hardware.
Cause: The same root cause as the openDocumentState issue: KPdfViewerState is being recreated because KPdfViewerConfig is rebuilt inline on every recomposition. When the state resets, the saveState flow reverts to Idle before the platform save dialog can return a result.Fix: Wrap source and config in remember so the viewer state is stable.
With a stable viewer state, saveState will progress through the full lifecycle instead of silently resetting.
Cause: Same root cause as the save and open-document issues: a newly constructed KPdfViewerConfig on each recomposition recreates the viewer state, which resets externalOpenState to Idle before the platform flow completes.Fix: Stabilize source and config with remember.
Cause: The zoomIn() and zoomOut() actions disable themselves when currentZoom reaches the bounds defined by zoomRange() in your config. If zoom is also disabled entirely via enableZoom(false), both controls will be inactive regardless of the current zoom level.Fix: Check two things in your config:
  1. Call enableZoom(true) to enable zoom interaction.
  2. Call zoomRange(minZoom, maxZoom) with values that give the user room to zoom. If minZoom equals maxZoom, neither button will ever be enabled.
You can read currentZoom to understand the current zoom level at runtime and decide whether to render your own zoom controls in a disabled state.
Cause: Swipe page navigation is disabled by default. Even when enabled, swiping only triggers at the base zoom level. If the user has zoomed in, swipe gestures pan the page instead of navigating to the next or previous page.Fix: Enable swipe in your config.
If swipe still doesn’t advance pages, check currentZoom. Swipe-to-navigate only activates when the viewer is at the minimum zoom level (base zoom). Ask the user to reset zoom first, or call viewerState.resetZoom() programmatically before expecting swipe navigation to work.
Cause: exportPdf() operates on the currently loaded source. If you call it before the document has finished loading — that is, before loadState reaches Ready — it will return a failure because there is no loaded content to export.Fix: Observe loadState and only call exportPdf() once it signals that the document is ready.
Always handle the onFailure branch. Sources that fail to load, are cancelled mid-flight, or encounter a rendering error will all surface through the failure path.
If your issue isn’t covered here, open an issue or search existing discussions at github.com/mahmoud947/KPDF.