
PDF viewing and handling toolkit with rendering, RAM/disk caching, remote persistence, configurable page preloading, shared zoom and search state, save/open/export flows, toolbar and thumbnail integrations.
KPDF is a Kotlin Multiplatform PDF library for Android and iOS with a Compose Multiplatform viewer layer.
kpdf-core: shared PDF loading, rendering, caching, save/export, and local picker state APIskpdf-compose: Compose viewer and platform integrations for save/open flowsCurrent version: 1.1.0
After publishing to Maven Central, add the library modules to your Gradle dependencies:
implementation("io.github.mahmoud947:kpdf-core:1.1.0")
implementation("io.github.mahmoud947:kpdf-compose:1.1.0")Make sure the consumer project includes mavenCentral() in its repositories.
KPdfViewerState
KPdfViewerState
KPdfViewerState
@Composable
fun PdfScreen(source: KPdfSource) {
val stableSource = remember(source) { source }
val viewerConfig = remember {
KPdfViewerConfig.builder()
.enableSwipe(true)
.ramCacheSize(6)
.diskCacheSize(24)
.preloadPageCount(2)
.build()
}
val viewerState = rememberPdfViewerState(
source = stableSource,
config = viewerConfig,
)
KPdfViewer(state = viewerState)
}Keep source and config stable in Compose. If you rebuild KPdfViewerConfig inline on every recomposition, rememberPdfViewerState(...) will recreate the viewer state and transient flows such as openDocumentState can appear to reset back to Idle.
KPDF also exposes optional connected views that share the same KPdfViewerState.
var thumbnailsVisible by remember { mutableStateOf(true) }
KPdfViewerToolbar(
state = viewerState,
isThumbnailStripVisible = thumbnailsVisible,
onThumbnailToggle = { thumbnailsVisible = it },
onShareClick = { /* custom share flow */ },
)
KPdfViewer(state = viewerState)
KPdfVerticalViewer(state = viewerState)
KPdfViewer(
state = viewerState,
loadingContent = { CircularProgressIndicator() },
errorContent = { message -> Text(message) },
)
if (thumbnailsVisible) {
KPdfThumbnailStrip(
state = viewerState,
onPageClick = { pageIndex ->
viewerState.goToPage(pageIndex)
},
)
}The library returns the selected source through openDocumentState. The app decides whether to replace the current document.
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")
}If openDocumentState never moves to AwaitingSelection or Success, first make sure the same viewerState instance is being retained across recompositions by remembering the source and KPdfViewerConfig.
Button(onClick = { viewerState.requestSave() }) {
Text("Save")
}Button(onClick = { viewerState.openInExternalApp() }) {
Text("Open In External App")
}val searchState by viewerState.searchState.collectAsState()
KPdfViewerToolbar(
state = viewerState,
isThumbnailStripVisible = thumbnailsVisible,
onThumbnailToggle = { thumbnailsVisible = it },
)Type a query into the toolbar search field and tap Search. The toolbar then shows match count, previous/next match controls, and clear search. Search results move the viewer to the active match. Android 15+ and iOS also draw translucent match highlights.
./gradlew \
:kpdf-core:testAndroidHostTest \
:kpdf-core:compileKotlinIosSimulatorArm64 \
:kpdf-compose:compileKotlinIosSimulatorArm64 \
:composeApp:compileDebugKotlinAndroidhttps://github.com/user-attachments/assets/4d590fe5-e503-4954-bd96-0735c9d718c7
KPDF is a Kotlin Multiplatform PDF library for Android and iOS with a Compose Multiplatform viewer layer.
kpdf-core: shared PDF loading, rendering, caching, save/export, and local picker state APIskpdf-compose: Compose viewer and platform integrations for save/open flowsCurrent version: 1.1.0
After publishing to Maven Central, add the library modules to your Gradle dependencies:
implementation("io.github.mahmoud947:kpdf-core:1.1.0")
implementation("io.github.mahmoud947:kpdf-compose:1.1.0")Make sure the consumer project includes mavenCentral() in its repositories.
KPdfViewerState
KPdfViewerState
KPdfViewerState
@Composable
fun PdfScreen(source: KPdfSource) {
val stableSource = remember(source) { source }
val viewerConfig = remember {
KPdfViewerConfig.builder()
.enableSwipe(true)
.ramCacheSize(6)
.diskCacheSize(24)
.preloadPageCount(2)
.build()
}
val viewerState = rememberPdfViewerState(
source = stableSource,
config = viewerConfig,
)
KPdfViewer(state = viewerState)
}Keep source and config stable in Compose. If you rebuild KPdfViewerConfig inline on every recomposition, rememberPdfViewerState(...) will recreate the viewer state and transient flows such as openDocumentState can appear to reset back to Idle.
KPDF also exposes optional connected views that share the same KPdfViewerState.
var thumbnailsVisible by remember { mutableStateOf(true) }
KPdfViewerToolbar(
state = viewerState,
isThumbnailStripVisible = thumbnailsVisible,
onThumbnailToggle = { thumbnailsVisible = it },
onShareClick = { /* custom share flow */ },
)
KPdfViewer(state = viewerState)
KPdfVerticalViewer(state = viewerState)
KPdfViewer(
state = viewerState,
loadingContent = { CircularProgressIndicator() },
errorContent = { message -> Text(message) },
)
if (thumbnailsVisible) {
KPdfThumbnailStrip(
state = viewerState,
onPageClick = { pageIndex ->
viewerState.goToPage(pageIndex)
},
)
}The library returns the selected source through openDocumentState. The app decides whether to replace the current document.
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")
}If openDocumentState never moves to AwaitingSelection or Success, first make sure the same viewerState instance is being retained across recompositions by remembering the source and KPdfViewerConfig.
Button(onClick = { viewerState.requestSave() }) {
Text("Save")
}Button(onClick = { viewerState.openInExternalApp() }) {
Text("Open In External App")
}val searchState by viewerState.searchState.collectAsState()
KPdfViewerToolbar(
state = viewerState,
isThumbnailStripVisible = thumbnailsVisible,
onThumbnailToggle = { thumbnailsVisible = it },
)Type a query into the toolbar search field and tap Search. The toolbar then shows match count, previous/next match controls, and clear search. Search results move the viewer to the active match. Android 15+ and iOS also draw translucent match highlights.
./gradlew \
:kpdf-core:testAndroidHostTest \
:kpdf-core:compileKotlinIosSimulatorArm64 \
:kpdf-compose:compileKotlinIosSimulatorArm64 \
:composeApp:compileDebugKotlinAndroidhttps://github.com/user-attachments/assets/4d590fe5-e503-4954-bd96-0735c9d718c7