KPDF renders PDF documents through a small set of composables that share a single state holder. To display a PDF you choose a source, build a viewer configuration, create the state with rememberPdfViewerState, and drop KPdfViewer into your layout. The steps below walk through each piece in order.
Choose a KPdfSource
KPdfSource describes where the PDF comes from. Pick the variant that matches your data.// Load from a remote URL (optional auth headers supported)
val source = KPdfSource.Url(
url = "https://example.com/document.pdf",
headers = mapOf("Authorization" to "Bearer token"),
)
// Load from raw bytes already in memory
val source = KPdfSource.Bytes(pdfBytes)
// Load from a Base64-encoded string
val source = KPdfSource.Base64(base64String)
Pass this value down to your composable as a stable parameter so the SDK does not reload the document on every recomposition.Create a KPdfViewerConfig with the builder
KPdfViewerConfig controls runtime behavior — zoom limits, gesture toggles, and cache sizes. Build it once inside a remember block so the reference stays stable.val viewerConfig = remember {
KPdfViewerConfig.builder()
.enableZoom(true)
.enableSwipe(true)
.zoomRange(minZoom = 1f, maxZoom = 5f)
.doubleTapZoom(2f)
.ramCacheSize(6)
.diskCacheSize(24)
.preloadPageCount(2)
.build()
}
| Builder option | Description |
|---|
enableZoom | Allow pinch-to-zoom gestures |
enableSwipe | Allow swipe-to-navigate gestures |
zoomRange | Minimum and maximum zoom factors |
doubleTapZoom | Zoom factor applied on a double tap |
ramCacheSize | Number of pages to keep in RAM |
diskCacheSize | Number of pages to keep on disk |
preloadPageCount | Pages to render ahead of the current page |
Call rememberPdfViewerState
rememberPdfViewerState creates a KPdfViewerState, opens the document, and binds platform save/open effects for you. Call it at the top of your composable.val viewerState = rememberPdfViewerState(
source = stableSource,
config = viewerConfig,
)
The state is keyed on source and config. Passing a new config object on every recomposition recreates the state and resets all transient flows, including openDocumentState and saveState.Add KPdfViewer to your layout
Place KPdfViewer anywhere in your composable tree. It reads the active page and zoom level directly from KPdfViewerState.KPdfViewer(
state = viewerState,
modifier = Modifier.fillMaxSize(),
)
For a production screen that also includes a toolbar and thumbnail strip, use the complete layout below.@Composable
fun FullPdfScreen(source: KPdfSource) {
val viewerState = rememberPdfViewerState(
source = source,
config = KPdfViewerConfig.builder()
.preloadPageCount(1)
.diskCacheSize(50)
.build(),
)
var thumbnailsVisible by remember { mutableStateOf(true) }
val scope = rememberCoroutineScope()
Column(modifier = Modifier.fillMaxSize()) {
KPdfViewerToolbar(
state = viewerState,
isThumbnailStripVisible = thumbnailsVisible,
onThumbnailToggle = { thumbnailsVisible = it },
onShareClick = {
scope.launch {
viewerState.exportPdf().onSuccess { bytes ->
sharePdfBytes(bytes)
}
}
},
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
)
KPdfViewer(
state = viewerState,
modifier = Modifier
.fillMaxWidth()
.weight(1f),
)
if (thumbnailsVisible) {
KPdfThumbnailStrip(
state = viewerState,
onPageClick = { pageIndex ->
viewerState.goToPage(pageIndex)
},
modifier = Modifier
.fillMaxWidth()
.height(172.dp)
.padding(horizontal = 12.dp),
)
}
}
}
Keep source and config stable. If you construct KPdfViewerConfig inline — outside a remember block — Compose recreates it on every recomposition. This causes rememberPdfViewerState to produce a new state instance, which resets openDocumentState, saveState, and other transient flows back to their initial values.