Android
A complete editor as a Jetpack Compose screen, PdfEditorScreen, or as a classic View, PdfEditorView. Hand it a file path, get the edited bytes back.
Every type and option: Android API reference.
1. Add the dependency
The SDK is a Maven artifact, com.pdfsdk:pdfeditor. It comes with your license as a Maven repository — either a hosted registry or a zip you unpack into your project. Add the repository and one dependency:
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
google(); mavenCentral()
maven { url = uri("${rootDir}/vendor/pdfsdk/maven") } // the unzipped bundle, or your registry URL
}
}// app/build.gradle.kts
dependencies {
implementation("com.pdfsdk:pdfeditor:2.4.1")
implementation("androidx.activity:activity-compose:1.9.3")
}Compose comes across transitively. The native engine is included for arm64-v8a and is aligned for 16 KB memory pages (required by Google Play for apps that target Android 15 or later). Minimum SDK 26.
Prefer the repository over a loose .aar in libs/: a raw AAR has no POM, so its dependencies do not come with it.
2. Set your license key
In Application.onCreate — it runs before any activity, in every process of your app. The key must list your app's package name (application ID).
import android.app.Application
import com.pdfsdk.editor.PdfEditorSdk
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
PdfEditorSdk.setLicenseKey("PE1.eyJ2Ijox…")
}
}<!-- AndroidManifest.xml -->
<application android:name=".MyApp" … >The SDK reads the package name from where Android installed the app (the process name only for unusual installs, such as a system image), so it isn't taken from anything the app can change. A build variant with an applicationIdSuffix (such as .debug) is a different package — ask for it to be added, or for com.acme.app.* alongside com.acme.app (the wildcard doesn't cover the base package itself). See License keys.
3. Show the editor — Compose
import com.pdfsdk.editor.PdfEditorScreen
setContent {
PdfEditorScreen(
pdfPath = file.absolutePath, // a WRITABLE path
onSave = { bytes -> file.writeBytes(bytes) },
onClose = { finish() },
)
}Done hands the document to onSave and the editor stays open; onClose runs when the user leaves (back, or the close button). If Done should also exit, close the editor from onSave.
Declare android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density|uiMode|keyboardHidden|keyboard|navigation" on the activity that hosts the editor. Without it, rotating the device or switching dark mode recreates the activity, and unsaved edits are lost.
Or as a View (Views, Fragments, Java)
class EditorActivity : ComponentActivity() { // not android.app.Activity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(PdfEditorView(this).apply {
pdfPath = file.absolutePath
onSave = PdfBytesListener { bytes -> file.writeBytes(bytes) }
onClose = PdfEditorCloseListener { finish() }
})
}
}PdfEditorView hosts Compose internally, so its window needs the lifecycle and saved-state owners that ComponentActivity, AppCompatActivity and Fragment provide. In a Fragment, set ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed.
Options
| Parameter | Type | What it does |
|---|---|---|
pdfPath | String | the PDF to open — a writable file path |
password | String? | for an encrypted PDF |
initialMode | PdfEditorMode? | VIEWER, EDIT, FORMS, ANNOTATE, FILL_AND_SIGN, INSERT, DRAW |
initialTool | PdfEditorTool? | a tool to pre-arm on open |
configuration | PdfEditorConfiguration | modes, tools, menu items, colour scheme, tool styles, zoom — see Customize |
onSave | (ByteArray) -> Unit | the edited PDF |
onClose | () -> Unit | the user left the editor (back, or the close button) — not called on Done |
onSharePdf, onPrint, onFlattenAndShare | ((ByteArray) -> Unit)? | intercept the export actions |
onDigitallySigned | ((ByteArray) -> Unit)? | the signed PDF after an e-signature |
onUnsavedChangesChanged | ((Boolean) -> Unit)? | tracks unsaved edits |
Customize the editor
Pass a PdfEditorConfiguration as configuration (or set PdfEditorView.configuration, before pdfPath) to choose which modes and tools the editor offers, hide ⋮-menu items, pin light or dark, and set the tools' first-use styles. Leave it out and you get the editor as it ships. These options narrow or restyle; the sections below also rename things and add your own buttons and fonts. Out-of-range values are clamped. It's read once, when the editor opens.
val config = PdfEditorConfiguration().apply {
modes = listOf(PdfEditorMode.VIEWER, PdfEditorMode.ANNOTATE, PdfEditorMode.FILL_AND_SIGN)
hiddenTools = setOf(PdfEditorTool.STAMP, PdfEditorTool.LINK)
hiddenMenuItems = setOf(PdfEditorMenuItem.PRINT, PdfEditorMenuItem.RESET_TO_ORIGINAL)
colorScheme = PdfEditorColorScheme.DARK
toolDefaults.highlight = PdfEditorMarkupStyle(0xFF34C759.toInt(), opacity = 0.4f)
minZoom = 1f; maxZoom = 2f
}
PdfEditorScreen(pdfPath = path, configuration = config, onSave = ::save, onClose = ::finish)| Option | Default | Notes |
|---|---|---|
modes | every mode | Your order; a mode whose tools are all hidden is dropped (not VIEWER). With one mode the header shows its name, no picker. initialMode falls back to the first listed. |
hiddenTools | none | Hidden in every mode that carries the tool. A hidden initialTool is not armed. |
hiddenMenuItems | none | SEARCH, MANAGE_PAGES, SHARE_PDF, PRINT, FLATTEN_AND_SHARE, FLATTEN_DOCUMENT, RESET_FORM, RESET_TO_ORIGINAL. With all hidden, the ⋮ button goes. |
colorScheme | SYSTEM | LIGHT or DARK: chrome, icons and page surround. |
toolDefaults | warm yellow, black pen | First-use styles (ARGB colours) for highlight, underline, strikeout, squiggly and ink. A user's own later choice is remembered and wins. |
minZoom / maxZoom | 0.5f / 3f | Clamped to 0.5–1 and 1–3. 1f/1f disables pinch-zoom. |
haptics, showsUsageHints | true | Turn off haptic feedback, or the one-time "drag across text…" hints. |
signaturePlacement | BOTTOM_RIGHT | Where the e-Sign tab's Placement picker starts: BOTTOM_LEFT, TOP_RIGHT, TOP_LEFT or INVISIBLE. The user can still pick another before signing. |
Names, icons, order and your own buttons
config.modeTitles = mapOf(PdfEditorMode.FILL_AND_SIGN to "Sign")
config.toolTitles = mapOf(PdfEditorTool.SIGNATURE to "Sign here")
config.toolIcons = mapOf(PdfEditorTool.HIGHLIGHT to R.drawable.ic_marker)
config.customTools = mapOf(PdfEditorMode.FILL_AND_SIGN to listOf(
PdfEditorCustomAction("approve", "Approve", R.drawable.ic_approve) { ctx ->
ctx.currentPdf { pdf -> pdf?.let { approve(it, page = ctx.pageIndex) } }
}))
config.toolOrder = mapOf(PdfEditorMode.FILL_AND_SIGN to listOf(
PdfEditorToolbarItem.Custom("approve"), PdfEditorToolbarItem.Tool(PdfEditorTool.ADD_TEXT)))
config.customMenuItems = listOf(PdfEditorCustomAction("crm", "Send to CRM") { ctx ->
ctx.currentPdf { pdf -> pdf?.let(::upload) }
})
config.menuOrder = listOf(PdfEditorMenuEntry.Custom("crm"))Names: toolTitles, modeTitles, menuItemTitles. Icons (drawable resources, drawn as tinted templates): toolIcons, modeIcons, menuItemIcons, headerIcons. Your own buttons: customTools[mode] and customMenuItems, whose handler can read the document with ctx.currentPdf { … } (null if the export failed). Order: toolOrder[mode] and menuOrder put the listed items first, and everything else follows in its usual order.
Fonts
config.interfaceFontFamily = FontFamily(Font(R.font.brand_regular), Font(R.font.brand_medium, FontWeight.Medium))
config.fonts = listOf(PdfEditorFont.fromResource(R.font.brand_regular))
config.fontsTitle = "Brand"
config.allowsFontImport = falseinterfaceFontFamily sets the editor's own text (toolbars, header, sheets, dialogs, menus) in a Compose FontFamily; the document's text is never set in it. fonts lists your fonts first in the text font picker, under fontsTitle: PdfEditorFont.fromResource, fromAsset, fromFile or fromBytes. Each must be a TrueType/OpenType file, and one that isn't is skipped. showsStandardFonts, showsDocumentFonts, showsDeviceFonts and allowsFontImport hide the picker's other sections. newTextFont sets the font of every text box the user places; its size and colour still follow the text around it.
Selection
config.selectionColor = 0xFFE0245E.toInt()
config.handleShape = PdfEditorHandleShape.CIRCLE
config.handleScale = 1.25f
config.selectionBorderWidth = 2f
config.rotateIcon = R.drawable.ic_rotateselectionColor (and selectionColorDark) colours the selection box, its handles, the rotation knob, the text caret, the text-selection band and the page-alignment guide. handleFillColor and handleShape (ROUNDED_SQUARE, CIRCLE, SQUARE) style the handles; handleScale (0.75–1.5) sizes them, and a mouse's reach follows while a finger's stays the same. selectionBorderWidth is 1–3 dp. rotateIcon, a drawable resource, is drawn in the knob as a template, in the selection colour. searchHighlightColor and searchCurrentHighlightColor colour the search highlights; their translucency stays the editor's, so the text under them reads.
Edit documents from code
Everything a user can do in the editor can be done from code, with no UI: read every layer of a page and its properties, replace text in any script (the layer keeps its font, size, colour and position, and the system's fonts fill in letters its font lacks), restyle, move, resize, rotate and restack layers, add text, images and shapes, mark text up, fill and author forms, edit metadata and pages, search, render a page, and save.
A layer is addressed by its page and an id that keeps naming it while other layers change. Geometry is in page points from the page's top-left corner. Each change is one undo step.
PdfDocumentEditor(file).use { doc ->
for (block in doc.textBlocks(0)) {
doc.setText(block, translate(block.text.orEmpty()), shrinkToFit = true)
}
file.writeBytes(doc.save())
}Pass onDocumentReady to PdfEditorScreen (or set it on PdfEditorView) to get the document the editor is showing: changes appear on screen at once and the user can undo them. Failures throw PdfDocumentException; NotFound means the layer is gone, so read the layers again.
Localize the editor
Every word the editor shows has a stable key, the same on every platform, with English as its built-in text. Give text for the keys you want translated; the rest stay English. The editor checks a dictionary first, then a provider function, then your app's own pe_<key> string resources in its current language (the per-app language included), then English. Right-to-left languages mirror the editor; the page navigator stays left to right, like the pages it turns.
configuration.localization = PdfEditorLocalization(
language = "hi",
strings = mapOf("cancel" to "रद्द करें", "done" to "हो गया"),
)Placeholders are positional (Page {1} of {2}), so a translation can reorder them. A count has two keys: <key>_one and <key>. Names you set per tool, mode or menu item win over translations.
Signing without the editor
PdfEditorSigning signs and verifies PDFs headlessly — bytes in, bytes out — with a device identity kept in the Android Keystore, or with your own signer (PdfCmsSigner) for a remote HSM or signing service.
if (!PdfEditorSigning.hasDeviceIdentity) {
PdfEditorSigning.createDeviceIdentity(name = "Ada Lovelace", organization = "Acme")
}
val reports = PdfEditorSigning.verify(pdfBytes) // List<PdfSignatureReport>Public API
| Type | What it is |
|---|---|
PdfEditorScreen | the editor, as a @Composable |
PdfEditorView | the editor, as a View |
PdfEditorSdk | VERSION, setLicenseKey, license, isSupportedDevice, chromeScale, setCjkFallbackFont |
PdfEditorLicense | the license verdict: status, isLicensed, reason, dates |
PdfEditorMode · PdfEditorTool | what to open in, and what to pre-arm |
PdfEditorConfiguration · PdfEditorMenuItem · PdfEditorColorScheme · PdfEditorToolDefaults · PdfEditorMarkupStyle · PdfEditorInkStyle · PdfEditorHeaderButton | customizing the editor |
PdfEditorCustomAction · PdfEditorActionHandler · PdfEditorActionContext · PdfEditorPdfCallback · PdfEditorToolbarItem · PdfEditorMenuEntry | your own buttons, and ordering |
PdfEditorFont | your fonts for the document's text |
PdfEditorHandleShape | the selection handles' shape |
PdfSignaturesScreen | a read-only report on a document's signatures |
PdfEditorSigning · PdfCmsSigner · PdfSignaturePlacement · PdfSignatureReport | headless signing |
PdfBytesListener · PdfEditorCloseListener · UnsavedChangesListener | the PdfEditorView callbacks |
Everything else in the library is internal. The consumer ProGuard/R8 rules the SDK needs travel inside the AAR, so a minified app needs no extra configuration.
Troubleshooting
| Symptom | Fix |
|---|---|
| "PDF-SDK is not licensed for this app" | Read the reason and package shown. Check applicationIdSuffix, and that setLicenseKey runs in Application.onCreate. |
| "The editor can't run on this device" | No arm64 engine on this device or ABI split — the SDK ships arm64-v8a only. PdfEditorSdk.isSupportedDevice tells you before you open it. |
ViewTreeLifecycleOwner not found | Host PdfEditorView in a ComponentActivity, AppCompatActivity or Fragment. |