Best Tutorial for AndroidDevelopers

Android App Development

Stay ahead with the latest tools, trends, and best practices in Android development

How to Show Pdf with Jetpack Compose

How to Show Pdf with Jetpack Compose - Responsive Blogger Template
How to Show Pdf in Jetpack Compose without using any third party library?

What is PDF?

PDF means Portable Document Format developed by Abode in the year 1992. It is a combination of vector and bitmap graphics. It is popular as it is portable and less memory consuming format and the most advance feature is that it can be encrypted for security in which a password is required to open, edit or view the contents.

 How to Show Pdf in Jetpack Compose?

There are many third party external library are available to display PDF files. To display a PDF in Jetpack Compose, you need to use an external library because Jetpack Compose doesn't have built-in support for PDF rendering. One popular library for this purpose is AndroidPdfViewer.
dependencies {

implementation ("com.github.barteksc:android-pdf-viewer:3.2.0-beta.1")

}

But in this tutorial, we are going to use the built-in Android APIs. Specifically, you can use Pdf Renderer to render PDF pages into bitmaps, which can then be displayed using Jetpack Compose's Image composable. 

Why Pdf Renderer is used?

Pdf Renderer is used to render each page of the PDF into a bitmap, which is then displayed using Jetpack Compose's Image composable. So, this approach does not rely on any third-party libraries and uses only Android's built-in PDF rendering capabilities.

Steps:

1. Open Android Studio
2. Create a New Project and choose Empty Compose Activity. 
3. Create assets folder and save pdf files in it.
4. MainAcitivity
5. Create a new file named PdfViewerApp

Create an assets folder

Create an assets folder

Create an assets folder

MainActivity

package com.codingbihar.pdfviewerapp

import ...

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            JetpackComposeSkillTheme {
             val navController = rememberNavController()
                PdfViewerApp(navController)

            }
        }
    }
}

PdfViewerApp

Copy this code →
package com.codingbihar.pdfviewerapp

import ...

@Composable
fun PdfViewerApp(navController: NavHostController) {
    NavHost(navController, startDestination = "list") {
        composable("list") { PdfList(navController) }
        composable("pdfViewer/{fileName}") { backStackEntry ->
            PdfViewer(
                navController,
                fileName = backStackEntry.arguments?.getString("fileName") ?: ""
            )
        }
    }
}
@Composable
fun PdfList(navController: NavHostController) {
    val context = LocalContext.current
    val pdfFiles = remember { mutableStateOf>(emptyList()) }

    LaunchedEffect(Unit) {
        pdfFiles.value = try {
            context.assets.list("")?.filter { it.endsWith(".pdf") } ?: emptyList()
        } catch (e: IOException) {
            emptyList()
        }
    }

    Column(modifier = Modifier.fillMaxSize().statusBarsPadding()) {
        pdfFiles.value.forEach { fileName ->
            Button(onClick = {
                navController.navigate("pdfViewer/$fileName")
            }, modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)) {
                Text(text = fileName)
            }
        }
        if (pdfFiles.value.isEmpty()) {
            Text("No PDFs available", modifier = Modifier.fillMaxWidth().padding(top = 16.dp))
        }
    }
}

@Composable
fun PdfViewer(navController: NavHostController, fileName: String) {
    val context = LocalContext.current
    val zoomState = remember { mutableFloatStateOf(1f) }
    val bitmapState = remember { mutableStateOf(null) }
    val coroutineScope = rememberCoroutineScope()
    val currentPage = remember { mutableIntStateOf(0) }
    val totalPages = remember { mutableIntStateOf(0) }

    // Handle back press to navigate back to the list
    BackHandler {
        navController.popBackStack()
    }

    LaunchedEffect(fileName, currentPage.intValue) {
        coroutineScope.launch {
            try {
                // Copy PDF from assets to cache
                val cacheFile = File(context.cacheDir, fileName)
                if (!cacheFile.exists()) {
                    context.assets.open(fileName).use { inputStream ->
                        FileOutputStream(cacheFile).use { outputStream ->
                            inputStream.copyTo(outputStream)
                        }
                    }
                }

                // Open PDF file and get the current page
                val fileDescriptor = ParcelFileDescriptor.open(cacheFile, ParcelFileDescriptor.MODE_READ_ONLY)
                val pdfRenderer = PdfRenderer(fileDescriptor)
                totalPages.intValue = pdfRenderer.pageCount // Set total pages

                val page = pdfRenderer.openPage(currentPage.intValue)
                val bitmap = Bitmap.createBitmap(page.width, page.height, Bitmap.Config.ARGB_8888)
                page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)

                withContext(Dispatchers.Main) {
                    bitmapState.value = bitmap
                }
                page.close()
                pdfRenderer.close()
                fileDescriptor.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }

    Column(modifier = Modifier.fillMaxSize()) {
        Box(modifier = Modifier
            .weight(1f)
            .fillMaxSize()
            .pointerInput(Unit) {
                detectHorizontalDragGestures { change, dragAmount ->
                    change.consume()
                    if (dragAmount > 0) {
                        // Swipe right to left
                        if (currentPage.intValue < totalPages.intValue - 1) {
                            currentPage.intValue += 1
                        }
                    } else if (dragAmount < 0) {
                        // Swipe left to right
                        if (currentPage.intValue > 0) {
                            currentPage.intValue -= 1
                        }
                    }
                }
            }) {
            bitmapState.value?.let { bmp ->
                Image(
                    bitmap = bmp.asImageBitmap(),
                    contentDescription = "PDF Page",
                    modifier = Modifier
                        .fillMaxSize()
                        .simpleZoomable(zoomState)
                        .graphicsLayer(
                            scaleX = zoomState.floatValue,
                            scaleY = zoomState.floatValue
                        ),
                    contentScale = ContentScale.Fit
                )
            } ?: run {
                CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
            }
        }

        // Navigation Controls (Optional, for testing purposes)
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp),
            horizontalArrangement = Arrangement.SpaceBetween
        ) {
            Button(
                onClick = {
                    if (currentPage.intValue > 0) {
                        currentPage.intValue -= 1
                    }
                }
            ) {
                Text("Previous")
            }
            Button(
                onClick = {
                    if (currentPage.intValue < totalPages.intValue - 1) {
                        currentPage.intValue += 1
                    }
                }
            ) {
                Text("Next")
            }
        }
    }
}

fun Modifier.simpleZoomable(
    zoomState: MutableState
) = this.pointerInput(Unit) {
    detectTransformGestures { _, _, zoomChange, _ ->
        val newZoom = max(1f, zoomState.value * zoomChange)
        zoomState.value = min(2f, newZoom)

    }
}

OUTPUT:

Pdf Viewer App Output 1

FAQ: How to Show PDF with Jetpack Compose

❓ 1. Can Jetpack Compose display PDFs on its own?

No — Jetpack Compose doesn’t include built-in PDF rendering support. You must either use Android’s PdfRenderer API or a third-party library to display PDF content in your app.

❓ 2. What is the PdfRenderer API and why use it?

PdfRenderer is an Android API that renders each page of a PDF file into a Bitmap, which you can then show inside Compose using an Image. It doesn’t require any external libraries.

❓ 3. What dependencies do I need?

If you are only using PdfRenderer, you don’t need extra dependencies — it’s part of Android’s framework.

Alternatively, if you choose a library like AndroidPdfViewer or a Compose-friendly PDF viewer, you might add a dependency such as:
implementation "com.github.barteksc:android-pdf-viewer:3.2.0-beta.1"
—but the tutorial example focuses on PdfRenderer without third-party libraries.

❓ 4. Where should the PDF file be placed?

Place the PDF file inside the assets folder of your Android project. If missing, create an assets/ directory under main/. Then list and load them dynamically.

❓ 5. How does the PDF get shown on screen?
  1. Copy the PDF from assets to cache.
  2. Use ParcelFileDescriptor + PdfRenderer to open the file.
  3. Render the desired page into a Bitmap.
  4. Display that bitmap using Image in Compose.
  5. You can add controls like “Next”/“Prev” to navigate pages.

❓ 6. How can I implement zooming?

The tutorial includes a custom modifier function (simpleZoomable) that uses pointer input and gesture detection to scale the image (e.g., pinch-to-zoom).

❓ 7. Is page navigation (swiping) supported?

Yes — the sample implementation detects horizontal drag gestures to move between pages (left/right).

❓ 8. Do I need third-party libraries for better PDF support?

Not necessarily — PdfRenderer works but it’s limited (shows bitmaps, no text selection, search, forms etc.). If you want richer PDF features (text search, annotations, form filling), consider libraries built for Compose or Android PDF SDKs.

❓ 9. Are there ready-made Compose libraries for PDF viewing?

Yes — open-source libraries like JetPDFVue or bouquet provide Compose-friendly PDF viewers with features like zoom, various sources (URL, URI), and interactive viewers.

❓ 10. Will this work on all Android versions?

PdfRenderer works on Android 5.0+ (API 21+) but if you need official system PDF viewer fragment components, newer AndroidX pdf-viewer APIs (alpha) are emerging which may offer more integrated support.
Sandeep Kumar - Android Developer

About the Author

Sandeep Kumar is an Android developer and educator who writes beginner-friendly Jetpack Compose tutorials on CodingBihar.com. His focus is on clean UI, Material Design 3, and real-world Android apps.

SkillDedication

— Python High Level Programming Language- Expert-Written Tutorials, Projects, and Tools—

Coding Bihar

Welcome To Coding Bihar👨‍🏫