PdfViewerFragment is a specialized Fragment that you
can use to display PDF documents within your Android application.
PdfViewerFragment simplifies PDF rendering, enabling you to concentrate on
other aspects of your app's functionality.
Results
Version compatibility
To use PdfViewerFragment, your application must target a minimum of Android S
(API level 31) and SDK extension level 13. If these compatibility requirements
are not satisfied, the library throws an
UnsupportedOperationException.
You can check the SDK extension version at runtime using the SdkExtensions
module. This enables you to conditionally load the fragment and PDF document
only if the device meets the necessary requirements.
if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S) >= 13) {
// Load the fragment and document.
}
Dependencies
To incorporate the PDF viewer into your application, declare the
androidx.pdf dependency in your app's module build.gradle
file. The PDF library is accessible from the Google Maven repository.
dependencies {
val pdfVersion = "1.0.0-alpha0X"
implementation("androidx.pdf:pdf:pdf-viewer-fragment:$pdfVersion")
}
PdfViewerFragment features
PdfViewerFragment presents PDF documents in paginated format, making them easy
to navigate. For efficient loading, the fragment employs a two‑pass
rendering strategy that progressively loads page dimensions.
To optimize memory usage, PdfViewerFragment renders only the currently visible
pages and releases the bitmaps for pages that are off‑screen.
Additionally, PdfViewerFragment includes a floating action button (FAB) that
supports annotations by firing an implicit android.intent.action.ANNOTATE
intent containing the document URI.
Implementation
Adding a PDF viewer to your Android application is a multistep process.
Create the activity layout
Begin by defining the layout XML for the activity that hosts the PDF viewer. The
layout should include a FrameLayout to contain the PdfViewerFragment and
buttons for user interactions, such as searching within the document.
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/pdf_container_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<FrameLayout
android:id="@+id/fragment_container_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/search_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/search_string"
app:strokeWidth="1dp"
android:layout_marginStart="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Set up the activity
The activity that hosts PdfViewerFragment must extend
AppCompatActivity. In the onCreate() method of the
activity, set the content view to the layout you created, and initialize any
necessary UI elements.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val getContentButton: MaterialButton = findViewById(R.id.launch_button)
val searchButton: MaterialButton = findViewById(R.id.search_button)
}
}
Initialize PdfViewerFragment
Create an instance of PdfViewerFragment using a fragment manager obtained from
getSupportFragmentManager(). Check whether an
instance of the fragment already exists before creating a new one, especially
during configuration changes.
In the following example, the initializePdfViewerFragment() function handles
the creation and commitment of the fragment transaction. The function replaces
an existing fragment in a container with an instance of your
PdfViewerFragment.
class MainActivity : AppCompatActivity() {
@RequiresExtension(extension = Build.VERSION_CODES.S, version = 13)
private var pdfViewerFragment: PdfViewerFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
// ...
if (pdfViewerFragment == null) {
pdfViewerFragment =
supportFragmentManager
.findFragmentByTag(PDF_VIEWER_FRAGMENT_TAG) as PdfViewerFragment?
}
}
// Used to instantiate and commit the fragment.
@RequiresExtension(extension = Build.VERSION_CODES.S, version = 13)
private fun initializePdfViewerFragment() {
// This condition can be skipped if you want to create a new fragment every time.
if (pdfViewerFragment == null) {
val fragmentManager: FragmentManager = supportFragmentManager
// Fragment initialization.
pdfViewerFragment = PdfViewerFragmentExtended()
val transaction: FragmentTransaction = fragmentManager.beginTransaction()
// Replace an existing fragment in a container with an instance of a new fragment.
transaction.replace(
R.id.fragment,4_container_view,
pdfViewerFragment!!,
PDF_VIEWER_FRAGMENT_TAG
)
transaction.commitAllowingStateLoss()
fragmentManager.executePendingTransactions()
}
}
companion object {
private const val MIME_TYPE_PDF = "application/pdf"
private const val PDF_VIEWER_FRAGMENT_TAG = "pdf_viewer_fragment_tag"
}
}
Extend PdfViewerFragment functionality
PdfViewerFragment exposes public functions that you can override to extend its
capabilities. Create a new class that inherits from PdfViewerFragment. In your
subclass, override methods such as onLoadDocumentSuccess() and
onLoadDocumentError() to add custom logic, like logging metrics.
@RequiresExtension(extension = Build.VERSION_CODES.S, version = 13)
class PdfViewerFragmentExtended : PdfViewerFragment() {
private val someLogger : SomeLogger = // ... used to log metrics
override fun onLoadDocumentSuccess() {
someLogger.log(/** log document success */)
}
override fun onLoadDocumentError(error: Throwable) {
someLogger.log(/** log document error */, error)
}
}
Enable document search
While PdfViewerFragment does not include a built-in search menu, it supports a
search bar. You control the visibility of the search bar using the
isTextSearchActive API. To enable document search, you set the
isTextSearchActive property of your PdfViewerFragment instance.
Use
WindowCompat.setDecorFitsSystemWindows()
to ensure WindowInsetsCompat is correctly passed to content views, which is
necessary for the proper positioning of the search view.
class MainActivity : AppCompatActivity() {
@RequiresExtension(extension = Build.VERSION_CODES.S, version = 13)
override fun onCreate(savedInstanceState: Bundle?) {
// ...
searchButton.setOnClickListener {
pdfViewerFragment?.isTextSearchActive =
pdfViewerFragment?.isTextSearchActive == false
}
// Ensure WindowInsetsCompat are passed to content views without being
// consumed by the decor view. These insets are used to calculate the
// position of the search view.
WindowCompat.setDecorFitsSystemWindows(window, false)
}
}
Integrate with the file picker
To allow users to select PDF files from their device, integrate
PdfViewerFragment with the Android file picker. First, update your activity's
layout XML to include a button that launches the file picker.
<...>
<FrameLayout
...
app:layout_constraintBottom_toTopOf="@+id/launch_button"/>
// Adding a button to open file picker.
<com.google.android.material.button.MaterialButton
android:id="@+id/launch_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/launch_string"
app:strokeWidth="1dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/search_button"/>
<com.google.android.material.button.MaterialButton
...
app:layout_constraintStart_toEndOf="@id/launch_button" />
</androidx.constraintlayout.widget.ConstraintLayout>
Next, in your activity, launch the file picker using
registerForActivityResult(GetContent()). When
the user selects a file, the callback provides a URI. You then set the
documentUri property of your PdfViewerFragment instance with this URI to
load and display the selected PDF.
class MainActivity : AppCompatActivity() {
// ...
private var filePicker: ActivityResultLauncher<String> =
registerForActivityResult(GetContent()) { uri: Uri? ->
uri?.let {
initializePdfViewerFragment()
pdfViewerFragment?.documentUri = uri
}
}
override fun onCreate(savedInstanceState: Bundle?) {
// ...
getContentButton.setOnClickListener { filePicker.launch(MIME_TYPE_PDF) }
}
private fun initializePdfViewerFragment() {
// ...
}
companion object {
private const val MIME_TYPE_PDF = "application/pdf"
// ...
}
}
Customize the UI
You can customize the user interface of PdfViewerFragment by overriding XML
attributes the library exposes. This enables you to tailor the appearance of
elements like the scrollbar and page indicator to match your app's design.
The customizable attributes include:
fastScrollVerticalThumbDrawable— Sets the drawable for the scrollbar thumb.fastScrollPageIndicatorBackgroundDrawable— Sets the background drawable for the page indicator.fastScrollPageIndicatorMarginEnd— Sets the right margin for the page indicator. Ensure margin values are positive.fastScrollVerticalThumbMarginEnd— Sets the right margin for the vertical scrollbar thumb. Ensure margin values are positive.
To apply these customizations, define a custom style in your XML resources.
<resources>
<style name="pdfContainerStyle">
<item name="fastScrollVerticalThumbDrawable">@drawable/custom_thumb_drawable</item>
<item name="fastScrollPageIndicatorBackgroundDrawable">@drawable/custom_page_indicator_background</item>
<item name="fastScrollVerticalThumbMarginEnd">8dp</item>
</style>
</resources>
Then, provide the custom style resource to PdfViewerFragment using
PdfStylingOptions when you create an instance of the fragment with
PdfViewerFragment.newInstance(stylingOptions).
private fun initializePdfViewerFragment() {
// This condition can be skipped if you want to create a new fragment every time.
if (pdfViewerFragment == null) {
val fragmentManager: FragmentManager = supportFragmentManager
// Create styling options.
val stylingOptions = PdfStylingOptions(R.style.pdfContainerStyle)
// Fragment initialization.
pdfViewerFragment = PdfViewerFragment.newInstance(stylingOptions)
// Execute fragment transaction.
}
}
If you've subclassed PdfViewerFragment, use the protected constructor to
provide the styling options. This ensures your custom styles are applied
correctly to your extended fragment.
class StyledPdfViewerFragment: PdfViewerFragment {
constructor() : super()
private constructor(pdfStylingOptions: PdfStylingOptions) : super(pdfStylingOptions)
companion object {
fun newInstance(): StyledPdfViewerFragment {
val stylingOptions = PdfStylingOptions(R.style.pdfContainerStyle)
return StyledPdfViewerFragment(stylingOptions)
}
}
}
Complete implementation
The following code provides a complete example of how to implement
PdfViewerFragment in your activity, including initialization, file picker
integration, search functionality, and UI customization.
class MainActivity : AppCompatActivity() {
private var pdfViewerFragment: PdfViewerFragment? = null
private var filePicker: ActivityResultLauncher<String> =
registerForActivityResult(GetContent()) { uri: Uri? ->
uri?.let {
initializePdfViewerFragment()
pdfViewerFragment?.documentUri = uri
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (pdfViewerFragment == null) {
pdfViewerFragment =
supportFragmentManager
.findFragmentByTag(PDF_VIEWER_FRAGMENT_TAG) as PdfViewerFragment?
}
val getContentButton: MaterialButton = findViewById(R.id.launch_button)
val searchButton: MaterialButton = findViewById(R.id.search_button)
getContentButton.setOnClickListener { filePicker.launch(MIME_TYPE_PDF) }
searchButton.setOnClickListener {
pdfViewerFragment?.isTextSearchActive = pdfViewerFragment?.isTextSearchActive == false
}
}
private fun initializePdfViewerFragment() {
// This condition can be skipped if you want to create a new fragment every time.
if (pdfViewerFragment == null) {
val fragmentManager: FragmentManager = supportFragmentManager
// Create styling options.
// val stylingOptions = PdfStylingOptions(R.style.pdfContainerStyle)
// Fragment initialization.
// For customization:
// pdfViewerFragment = PdfViewerFragment.newInstance(stylingOptions)
pdfViewerFragment = PdfViewerFragmentExtended()
val transaction: FragmentTransaction = fragmentManager.beginTransaction()
// Replace an existing fragment in a container with an instance of a new fragment.
transaction.replace(
R.id.fragment_container_view,
pdfViewerFragment!!,
PDF_VIEWER_FRAGMENT_TAG
)
transaction.commitAllowingStateLoss()
fragmentManager.executePendingTransactions()
}
}
companion object {
private const val MIME_TYPE_PDF = "application/pdf"
private const val PDF_VIEWER_FRAGMENT_TAG = "pdf_viewer_fragment_tag"
}
}
Key points about the code
- Ensure your project meets the minimum API level and SDK extension requirements.
- The activity hosting
PdfViewerFragmentmust extendAppCompatActivity. - You can extend
PdfViewerFragmentto add custom behaviors. - Customize the UI of
PdfViewerFragmentby overriding XML attributes.