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.
<androidx.constraintlayout.widget.ConstraintLayout ... tools:context=".MainActivity"> <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.